增加换肤功能
This commit is contained in:
807
TUIKit/TUICore/TUICommonModel.m
Normal file
807
TUIKit/TUICore/TUICommonModel.m
Normal file
@@ -0,0 +1,807 @@
|
||||
//
|
||||
// TCommonCell.m
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/6.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUICommonModel.h"
|
||||
#import <objc/runtime.h>
|
||||
#import "NSString+TUIUtil.h"
|
||||
#import "TUIDarkModel.h"
|
||||
#import "TUIGlobalization.h"
|
||||
#import "TUIThemeManager.h"
|
||||
#import "TUITool.h"
|
||||
#import "UIView+TUILayout.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIUserFullInfo
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
@implementation V2TIMUserFullInfo (TUIUserFullInfo)
|
||||
|
||||
- (NSString *)showName {
|
||||
if ([NSString isEmpty:self.nickName]) return self.userID;
|
||||
return self.nickName;
|
||||
}
|
||||
|
||||
- (NSString *)showGender {
|
||||
if (self.gender == V2TIM_GENDER_MALE) return TUIKitLocalizableString(Male);
|
||||
if (self.gender == V2TIM_GENDER_FEMALE) return TUIKitLocalizableString(Female);
|
||||
return TUIKitLocalizableString(Unsetted);
|
||||
}
|
||||
|
||||
- (NSString *)showSignature {
|
||||
if (self.selfSignature == nil) return TUIKitLocalizableString(TUIKitNoSelfSignature);
|
||||
return [NSString stringWithFormat:TUIKitLocalizableString(TUIKitSelfSignatureFormat), self.selfSignature];
|
||||
}
|
||||
|
||||
- (NSString *)showAllowType {
|
||||
if (self.allowType == V2TIM_FRIEND_ALLOW_ANY) {
|
||||
return TUIKitLocalizableString(TUIKitAllowTypeAcceptOne);
|
||||
}
|
||||
if (self.allowType == V2TIM_FRIEND_NEED_CONFIRM) {
|
||||
return TUIKitLocalizableString(TUIKitAllowTypeNeedConfirm);
|
||||
}
|
||||
if (self.allowType == V2TIM_FRIEND_DENY_ANY) {
|
||||
return TUIKitLocalizableString(TUIKitAllowTypeDeclineAll);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIUserModel
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
@implementation TUIUserModel
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
TUIUserModel *model = [[TUIUserModel alloc] init];
|
||||
model.userId = self.userId;
|
||||
model.name = self.name;
|
||||
model.avatar = self.avatar;
|
||||
return model;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIScrollView
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void *gScrollViewBoundsChangeNotificationContext = &gScrollViewBoundsChangeNotificationContext;
|
||||
|
||||
@interface TUIScrollView ()
|
||||
|
||||
@property(nonatomic, readonly) CGFloat imageAspectRatio;
|
||||
@property(nonatomic) CGRect initialImageFrame;
|
||||
@property(strong, nonatomic, readonly) UITapGestureRecognizer *tap;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIScrollView
|
||||
|
||||
@synthesize tap = _tap;
|
||||
|
||||
#pragma mark - Tap to Zoom
|
||||
|
||||
- (UITapGestureRecognizer *)tap {
|
||||
if (_tap == nil) {
|
||||
_tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToZoom:)];
|
||||
_tap.numberOfTapsRequired = 2;
|
||||
}
|
||||
return _tap;
|
||||
}
|
||||
|
||||
- (void)tapToZoom:(UIGestureRecognizer *)gestureRecognizer {
|
||||
if (self.zoomScale > self.minimumZoomScale) {
|
||||
[self setZoomScale:self.minimumZoomScale animated:YES];
|
||||
} else {
|
||||
CGPoint tapLocation = [gestureRecognizer locationInView:self.imageView];
|
||||
CGFloat zoomRectWidth = self.imageView.frame.size.width / self.maximumZoomScale;
|
||||
CGFloat zoomRectHeight = self.imageView.frame.size.height / self.maximumZoomScale;
|
||||
CGFloat zoomRectX = tapLocation.x - zoomRectWidth * 0.5;
|
||||
CGFloat zoomRectY = tapLocation.y - zoomRectHeight * 0.5;
|
||||
CGRect zoomRect = CGRectMake(zoomRectX, zoomRectY, zoomRectWidth, zoomRectHeight);
|
||||
[self zoomToRect:zoomRect animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private Methods
|
||||
|
||||
- (void)configure {
|
||||
self.showsVerticalScrollIndicator = NO;
|
||||
self.showsHorizontalScrollIndicator = NO;
|
||||
[self startObservingBoundsChange];
|
||||
}
|
||||
|
||||
- (void)setImageView:(UIImageView *)imageView {
|
||||
if (_imageView.superview == self) {
|
||||
[_imageView removeGestureRecognizer:self.tap];
|
||||
[_imageView removeFromSuperview];
|
||||
}
|
||||
if (imageView) {
|
||||
_imageView = imageView;
|
||||
_initialImageFrame = CGRectNull;
|
||||
_imageView.userInteractionEnabled = YES;
|
||||
[_imageView addGestureRecognizer:self.tap];
|
||||
[self addSubview:imageView];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)imageAspectRatio {
|
||||
if (self.imageView.image) {
|
||||
return self.imageView.image.size.width / self.imageView.image.size.height;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (CGSize)rectSizeForAspectRatio:(CGFloat)ratio thatFitsSize:(CGSize)size {
|
||||
CGFloat containerWidth = size.width;
|
||||
CGFloat containerHeight = size.height;
|
||||
CGFloat resultWidth = 0;
|
||||
CGFloat resultHeight = 0;
|
||||
|
||||
if ((ratio <= 0) || (containerHeight <= 0)) {
|
||||
return size;
|
||||
}
|
||||
|
||||
if (containerWidth / containerHeight >= ratio) {
|
||||
resultHeight = containerHeight;
|
||||
resultWidth = containerHeight * ratio;
|
||||
} else {
|
||||
resultWidth = containerWidth;
|
||||
resultHeight = containerWidth / ratio;
|
||||
}
|
||||
|
||||
return CGSizeMake(resultWidth, resultHeight);
|
||||
}
|
||||
|
||||
- (void)scaleImageForScrollViewTransitionFromBounds:(CGRect)oldBounds toBounds:(CGRect)newBounds {
|
||||
CGPoint oldContentOffset = CGPointMake(oldBounds.origin.x, oldBounds.origin.y);
|
||||
CGSize oldSize = oldBounds.size;
|
||||
CGSize newSize = newBounds.size;
|
||||
|
||||
CGSize containedImageSizeOld = [self rectSizeForAspectRatio:self.imageAspectRatio thatFitsSize:oldSize];
|
||||
|
||||
CGSize containedImageSizeNew = [self rectSizeForAspectRatio:self.imageAspectRatio thatFitsSize:newSize];
|
||||
|
||||
if (containedImageSizeOld.height <= 0) {
|
||||
containedImageSizeOld = containedImageSizeNew;
|
||||
}
|
||||
|
||||
CGFloat orientationRatio = (containedImageSizeNew.height / containedImageSizeOld.height);
|
||||
|
||||
CGAffineTransform t = CGAffineTransformMakeScale(orientationRatio, orientationRatio);
|
||||
|
||||
self.imageView.frame = CGRectApplyAffineTransform(self.imageView.frame, t);
|
||||
|
||||
self.contentSize = self.imageView.frame.size;
|
||||
|
||||
CGFloat xOffset = (oldContentOffset.x + oldSize.width * 0.5) * orientationRatio - newSize.width * 0.5;
|
||||
CGFloat yOffset = (oldContentOffset.y + oldSize.height * 0.5) * orientationRatio - newSize.height * 0.5;
|
||||
|
||||
xOffset -= MAX(xOffset + newSize.width - self.contentSize.width, 0);
|
||||
yOffset -= MAX(yOffset + newSize.height - self.contentSize.height, 0);
|
||||
xOffset += MAX(-xOffset, 0);
|
||||
yOffset += MAX(-yOffset, 0);
|
||||
|
||||
self.contentOffset = CGPointMake(xOffset, yOffset);
|
||||
}
|
||||
|
||||
- (void)setupInitialImageFrame {
|
||||
if (self.imageView.image && CGRectEqualToRect(self.initialImageFrame, CGRectNull)) {
|
||||
CGSize imageViewSize = [self rectSizeForAspectRatio:self.imageAspectRatio thatFitsSize:self.bounds.size];
|
||||
self.initialImageFrame = CGRectMake(0, 0, imageViewSize.width, imageViewSize.height);
|
||||
self.imageView.frame = self.initialImageFrame;
|
||||
self.contentSize = self.initialImageFrame.size;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - KVO
|
||||
|
||||
- (void)startObservingBoundsChange {
|
||||
[self addObserver:self
|
||||
forKeyPath:@"bounds"
|
||||
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
|
||||
context:gScrollViewBoundsChangeNotificationContext];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
|
||||
if (context == gScrollViewBoundsChangeNotificationContext) {
|
||||
CGRect oldRect = ((NSValue *)change[NSKeyValueChangeOldKey]).CGRectValue;
|
||||
CGRect newRect = ((NSValue *)change[NSKeyValueChangeNewKey]).CGRectValue;
|
||||
if (!CGSizeEqualToSize(oldRect.size, newRect.size)) {
|
||||
[self scaleImageForScrollViewTransitionFromBounds:oldRect toBounds:newRect];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self removeObserver:self forKeyPath:@"bounds"];
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollView
|
||||
|
||||
- (void)setContentOffset:(CGPoint)contentOffset {
|
||||
const CGSize contentSize = self.contentSize;
|
||||
const CGSize scrollViewSize = self.bounds.size;
|
||||
|
||||
if (contentSize.width < scrollViewSize.width) {
|
||||
contentOffset.x = -(scrollViewSize.width - contentSize.width) * 0.5;
|
||||
}
|
||||
|
||||
if (contentSize.height < scrollViewSize.height) {
|
||||
contentOffset.y = -(scrollViewSize.height - contentSize.height) * 0.5;
|
||||
}
|
||||
|
||||
[super setContentOffset:contentOffset];
|
||||
}
|
||||
|
||||
#pragma mark - UIView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self configure];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self configure];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self setupInitialImageFrame];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIGroupAvatar
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
#define groupAvatarWidth (48 * [[UIScreen mainScreen] scale])
|
||||
@implementation TUIGroupAvatar
|
||||
|
||||
+ (void)createGroupAvatar:(NSArray *)group finished:(void (^)(UIImage *groupAvatar))finished {
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
NSInteger avatarCount = group.count > 9 ? 9 : group.count;
|
||||
CGFloat width = groupAvatarWidth / 3 * 0.90;
|
||||
CGFloat space3 = (groupAvatarWidth - width * 3) / 4;
|
||||
CGFloat space2 = (groupAvatarWidth - width * 2 + space3) / 2;
|
||||
CGFloat space1 = (groupAvatarWidth - width) / 2;
|
||||
__block CGFloat y = avatarCount > 6 ? space3 : (avatarCount > 3 ? space2 : space1);
|
||||
__block CGFloat x = avatarCount % 3 == 0 ? space3 : (avatarCount % 3 == 2 ? space2 : space1);
|
||||
width = avatarCount > 4 ? width : (avatarCount > 1 ? (groupAvatarWidth - 3 * space3) / 2 : groupAvatarWidth);
|
||||
|
||||
if (avatarCount == 1) {
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
if (avatarCount == 2) {
|
||||
x = space3;
|
||||
} else if (avatarCount == 3) {
|
||||
x = (groupAvatarWidth - width) / 2;
|
||||
y = space3;
|
||||
} else if (avatarCount == 4) {
|
||||
x = space3;
|
||||
y = space3;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, groupAvatarWidth, groupAvatarWidth)];
|
||||
[view setBackgroundColor:[UIColor colorWithWhite:0.8 alpha:0.6]];
|
||||
view.layer.cornerRadius = 6;
|
||||
__block NSInteger count = 0;
|
||||
for (NSInteger i = avatarCount - 1; i >= 0; i--) {
|
||||
NSString *avatarUrl = [group objectAtIndex:i];
|
||||
|
||||
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, width, width)];
|
||||
[view addSubview:imageView];
|
||||
[imageView sd_setImageWithURL:[NSURL URLWithString:avatarUrl]
|
||||
placeholderImage:DefaultAvatarImage
|
||||
completed:^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) {
|
||||
count++;
|
||||
if (count == avatarCount) {
|
||||
UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 2.0);
|
||||
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndPDFContext();
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
CGImageRef imageRefRect =
|
||||
CGImageCreateWithImageInRect(imageRef, CGRectMake(0, 0, view.frame.size.width * 2, view.frame.size.height * 2));
|
||||
UIImage *ansImage = [[UIImage alloc] initWithCGImage:imageRefRect];
|
||||
CGImageRelease(imageRefRect);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (finished) {
|
||||
finished(ansImage);
|
||||
}
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
if (avatarCount == 3) {
|
||||
if (i == 2) {
|
||||
y = width + space3 * 2;
|
||||
x = space3;
|
||||
} else {
|
||||
x += width + space3;
|
||||
}
|
||||
} else if (avatarCount == 4) {
|
||||
if (i % 2 == 0) {
|
||||
y += width + space3;
|
||||
x = space3;
|
||||
} else {
|
||||
x += width + space3;
|
||||
}
|
||||
} else {
|
||||
if (i % 3 == 0) {
|
||||
y += (width + space3);
|
||||
x = space3;
|
||||
} else {
|
||||
x += (width + space3);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)fetchGroupAvatars:(NSString *)groupID placeholder:(UIImage *)placeholder callback:(void (^)(BOOL success, UIImage *image, NSString *groupID))callback {
|
||||
@tui_weakify(self);
|
||||
[[V2TIMManager sharedInstance] getGroupMemberList:groupID
|
||||
filter:V2TIM_GROUP_MEMBER_FILTER_ALL
|
||||
nextSeq:0
|
||||
succ:^(uint64_t nextSeq, NSArray<V2TIMGroupMemberFullInfo *> *memberList) {
|
||||
@tui_strongify(self);
|
||||
int i = 0;
|
||||
NSMutableArray *groupMemberAvatars = [NSMutableArray arrayWithCapacity:1];
|
||||
for (V2TIMGroupMemberFullInfo *member in memberList) {
|
||||
if (member.faceURL.length > 0) {
|
||||
[groupMemberAvatars addObject:member.faceURL];
|
||||
} else {
|
||||
[groupMemberAvatars addObject:@"http://placeholder"];
|
||||
}
|
||||
if (++i == 9) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i <= 1) {
|
||||
[self asyncClearCacheAvatarForGroup:groupID];
|
||||
if (callback) {
|
||||
callback(NO, placeholder, groupID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"TUIConversationLastGroupMember_%@", groupID];
|
||||
[NSUserDefaults.standardUserDefaults setInteger:groupMemberAvatars.count forKey:key];
|
||||
[NSUserDefaults.standardUserDefaults synchronize];
|
||||
|
||||
[TUIGroupAvatar createGroupAvatar:groupMemberAvatars
|
||||
finished:^(UIImage *groupAvatar) {
|
||||
@tui_strongify(self);
|
||||
UIImage *avatar = groupAvatar;
|
||||
[self cacheGroupAvatar:avatar number:(UInt32)groupMemberAvatars.count groupID:groupID];
|
||||
|
||||
if (callback) {
|
||||
callback(YES, avatar, groupID);
|
||||
}
|
||||
}];
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
if (callback) {
|
||||
callback(NO, placeholder, groupID);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)cacheGroupAvatar:(UIImage *)avatar number:(UInt32)memberNum groupID:(NSString *)groupID {
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
if (groupID == nil || groupID.length == 0) {
|
||||
return;
|
||||
}
|
||||
NSString *tempPath = NSTemporaryDirectory();
|
||||
NSString *filePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%d.png", tempPath, groupID, memberNum];
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
// check to delete old file
|
||||
NSNumber *oldValue = [defaults objectForKey:groupID];
|
||||
if (oldValue != nil) {
|
||||
UInt32 oldMemberNum = [oldValue unsignedIntValue];
|
||||
NSString *oldFilePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%d.png", tempPath, groupID, oldMemberNum];
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
[fileManager removeItemAtPath:oldFilePath error:nil];
|
||||
}
|
||||
|
||||
// Save image.
|
||||
BOOL success = [UIImagePNGRepresentation(avatar) writeToFile:filePath atomically:YES];
|
||||
if (success) {
|
||||
[defaults setObject:@(memberNum) forKey:groupID];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)getCacheGroupAvatar:(NSString *)groupID callback:(void (^)(UIImage *, NSString *groupID))imageCallBack {
|
||||
if (groupID == nil || groupID.length == 0) {
|
||||
if (imageCallBack) {
|
||||
imageCallBack(nil, groupID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
[[V2TIMManager sharedInstance] getGroupsInfo:@[ groupID ]
|
||||
succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
|
||||
V2TIMGroupInfoResult *groupInfo = groupResultList.firstObject;
|
||||
if (!groupInfo) {
|
||||
imageCallBack(nil, groupID);
|
||||
return;
|
||||
}
|
||||
UInt32 memberNum = groupInfo.info.memberCount;
|
||||
memberNum = MAX(1, memberNum);
|
||||
memberNum = MIN(memberNum, 9);
|
||||
;
|
||||
NSString *tempPath = NSTemporaryDirectory();
|
||||
NSString *filePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%u.png", tempPath, groupID, (unsigned int)memberNum];
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
UIImage *avatar = nil;
|
||||
BOOL success = [fileManager fileExistsAtPath:filePath];
|
||||
|
||||
if (success) {
|
||||
avatar = [[UIImage alloc] initWithContentsOfFile:filePath];
|
||||
NSString *key = [NSString stringWithFormat:@"TUIConversationLastGroupMember_%@", groupID];
|
||||
[NSUserDefaults.standardUserDefaults setInteger:memberNum forKey:key];
|
||||
[NSUserDefaults.standardUserDefaults synchronize];
|
||||
}
|
||||
imageCallBack(avatar, groupInfo.info.groupID);
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
imageCallBack(nil, groupID);
|
||||
}];
|
||||
}
|
||||
|
||||
+ (UIImage *)getCacheAvatarForGroup:(NSString *)groupId number:(UInt32)memberNum {
|
||||
memberNum = MAX(1, memberNum);
|
||||
memberNum = MIN(memberNum, 9);
|
||||
;
|
||||
NSString *tempPath = NSTemporaryDirectory();
|
||||
NSString *filePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%u.png", tempPath, groupId, (unsigned int)memberNum];
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
UIImage *avatar = nil;
|
||||
BOOL success = [fileManager fileExistsAtPath:filePath];
|
||||
|
||||
if (success) {
|
||||
avatar = [[UIImage alloc] initWithContentsOfFile:filePath];
|
||||
}
|
||||
return avatar;
|
||||
}
|
||||
|
||||
+ (void)asyncClearCacheAvatarForGroup:(NSString *)groupID {
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
NSString *tempPath = NSTemporaryDirectory();
|
||||
for (int i = 0; i < 9; i++) {
|
||||
NSString *filePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%d.png", tempPath, groupID, (i + 1)];
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) {
|
||||
[NSFileManager.defaultManager removeItemAtPath:filePath error:nil];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIImageCache
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
@interface TUIImageCache ()
|
||||
@property(nonatomic, strong) NSMutableDictionary *resourceCache;
|
||||
@property(nonatomic, strong) NSMutableDictionary *faceCache;
|
||||
@end
|
||||
|
||||
@implementation TUIImageCache
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
static TUIImageCache *instance;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[TUIImageCache alloc] init];
|
||||
[UIImage d_fixResizableImage];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_resourceCache = [NSMutableDictionary dictionary];
|
||||
_faceCache = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addResourceToCache:(NSString *)path {
|
||||
__weak typeof(self) ws = self;
|
||||
[TUITool asyncDecodeImage:path
|
||||
complete:^(NSString *key, UIImage *image) {
|
||||
__strong __typeof(ws) strongSelf = ws;
|
||||
if (key && image) {
|
||||
[strongSelf.resourceCache setValue:image forKey:key];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIImage *)getResourceFromCache:(NSString *)path {
|
||||
if (path.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
UIImage *image = [_resourceCache objectForKey:path];
|
||||
if (!image) {
|
||||
image = [UIImage d_imagePath:path];
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
- (void)addFaceToCache:(NSString *)path {
|
||||
__weak typeof(self) ws = self;
|
||||
[TUITool asyncDecodeImage:path
|
||||
complete:^(NSString *key, UIImage *image) {
|
||||
__strong __typeof(ws) strongSelf = ws;
|
||||
if (key && image) {
|
||||
[strongSelf.faceCache setValue:image forKey:key];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIImage *)getFaceFromCache:(NSString *)path {
|
||||
if (path.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
UIImage *image = [_faceCache objectForKey:path];
|
||||
if (!image) {
|
||||
// gif extion
|
||||
if ([path tui_containsString:@".gif"]) {
|
||||
image = [UIImage sd_imageWithGIFData:[NSData dataWithContentsOfFile:path]];
|
||||
}
|
||||
else {
|
||||
image = [UIImage imageWithContentsOfFile:path];
|
||||
if (!image) {
|
||||
// gif
|
||||
NSString *formatPath = [path stringByAppendingString:@".gif"];
|
||||
image = [UIImage sd_imageWithGIFData:[NSData dataWithContentsOfFile:formatPath]];
|
||||
}
|
||||
}
|
||||
if (!image) {
|
||||
image = [_faceCache objectForKey:TUIChatFaceImagePath(@"ic_unknown_image")];
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
@end
|
||||
@interface IUCoreView : UIView
|
||||
@property(nonatomic, strong) UIView *view;
|
||||
@end
|
||||
|
||||
@implementation IUCoreView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
|
||||
[self addSubview:self.view];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUINavigationController
|
||||
|
||||
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController {
|
||||
if (self = [super initWithRootViewController:rootViewController]) {
|
||||
self.interactivePopGestureRecognizer.delegate = self;
|
||||
self.delegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
|
||||
[appearance configureWithDefaultBackground];
|
||||
appearance.shadowColor = nil;
|
||||
appearance.backgroundEffect = nil;
|
||||
appearance.backgroundColor = self.navigationBackColor;
|
||||
self.navigationBar.backgroundColor = self.navigationBackColor;
|
||||
self.navigationBar.barTintColor = self.navigationBackColor;
|
||||
self.navigationBar.shadowImage = [UIImage new];
|
||||
self.navigationBar.standardAppearance = appearance;
|
||||
self.navigationBar.scrollEdgeAppearance = appearance;
|
||||
|
||||
} else {
|
||||
self.navigationBar.backgroundColor = self.navigationBackColor;
|
||||
self.navigationBar.barTintColor = self.navigationBackColor;
|
||||
self.navigationBar.shadowImage = [UIImage new];
|
||||
}
|
||||
}
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.delegate = self;
|
||||
}
|
||||
|
||||
- (void)back {
|
||||
if ([self.uiNaviDelegate respondsToSelector:@selector(navigationControllerDidClickLeftButton:)]) {
|
||||
[self.uiNaviDelegate navigationControllerDidClickLeftButton:self];
|
||||
}
|
||||
[self popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (UIColor *)navigationBackColor {
|
||||
if (!_navigationBackColor) {
|
||||
_navigationBackColor = [self tintColor];
|
||||
}
|
||||
return _navigationBackColor;
|
||||
}
|
||||
|
||||
- (UIColor *)tintColor {
|
||||
return TUICoreDynamicColor(@"head_bg_gradient_start_color", @"#EBF0F6");
|
||||
}
|
||||
|
||||
|
||||
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
|
||||
if (self.viewControllers.count != 0) {
|
||||
viewController.hidesBottomBarWhenPushed = YES;
|
||||
self.tabBarController.tabBar.hidden = YES;
|
||||
|
||||
UIImage *image = self.navigationItemBackArrowImage;
|
||||
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:@selector(back)];
|
||||
viewController.navigationItem.leftBarButtonItems = @[ back ];
|
||||
viewController.navigationItem.leftItemsSupplementBackButton = NO;
|
||||
}
|
||||
[super pushViewController:viewController animated:animated];
|
||||
}
|
||||
|
||||
- (UIImage *)navigationItemBackArrowImage {
|
||||
if (!_navigationItemBackArrowImage) {
|
||||
_navigationItemBackArrowImage = TUICoreDynamicImage(@"nav_back_img", [UIImage imageNamed:TUICoreImagePath(@"nav_back")]);
|
||||
}
|
||||
return _navigationItemBackArrowImage;
|
||||
}
|
||||
|
||||
// fix: https://developer.apple.com/forums/thread/660750
|
||||
- (NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated {
|
||||
if (@available(iOS 14.0, *)) {
|
||||
for (UIViewController *vc in self.viewControllers) {
|
||||
vc.hidesBottomBarWhenPushed = NO;
|
||||
self.tabBarController.tabBar.hidden = NO;
|
||||
}
|
||||
}
|
||||
return [super popToRootViewControllerAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
|
||||
if (navigationController.viewControllers.count == 1) {
|
||||
/**
|
||||
* If the number of view controllers in the stack is 1, it means only the root controller, clear the currentShowVC, and disable the swipe gesture for
|
||||
* the following method
|
||||
*/
|
||||
self.currentShowVC = Nil;
|
||||
} else {
|
||||
/**
|
||||
* Assign the pushed view controller to currentShowVC
|
||||
*/
|
||||
self.currentShowVC = viewController;
|
||||
}
|
||||
|
||||
if ([navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
|
||||
if (self.viewControllers.count == 1) {
|
||||
/**
|
||||
* Forbid the sliding back of the home page
|
||||
*/
|
||||
navigationController.interactivePopGestureRecognizer.enabled = NO;
|
||||
} else {
|
||||
navigationController.interactivePopGestureRecognizer.enabled = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
|
||||
/**
|
||||
* Listen to the event returned by the side slide
|
||||
*/
|
||||
__weak typeof(self) weakSelf = self;
|
||||
if (@available(iOS 10.0, *)) {
|
||||
[viewController.transitionCoordinator notifyWhenInteractionChangesUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf handleSideSlideReturnIfNeeded:context];
|
||||
}];
|
||||
} else {
|
||||
[viewController.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf handleSideSlideReturnIfNeeded:context];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
|
||||
if (gestureRecognizer == self.interactivePopGestureRecognizer) {
|
||||
if (self.currentShowVC == self.topViewController) {
|
||||
/**
|
||||
* If currentShowVC exists, it means that the number of controllers in the stack is greater than 1, allowing the side slide gesture to be activated
|
||||
*/
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)handleSideSlideReturnIfNeeded:(id<UIViewControllerTransitionCoordinatorContext>)context {
|
||||
if (context.isCancelled) {
|
||||
return;
|
||||
}
|
||||
UIViewController *fromVc = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
|
||||
if ([self.uiNaviDelegate respondsToSelector:@selector(navigationControllerDidSideSlideReturn:fromViewController:)]) {
|
||||
[self.uiNaviDelegate navigationControllerDidSideSlideReturn:self fromViewController:fromVc];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIAlertController (TUITheme)
|
||||
|
||||
- (void)tuitheme_addAction:(UIAlertAction *)action {
|
||||
if (action.style == UIAlertActionStyleDefault || action.style == UIAlertActionStyleCancel) {
|
||||
UIColor *tempColor = TUICoreDynamicColor(@"primary_theme_color", @"#147AFF");
|
||||
[action setValue:tempColor forKey:@"_titleTextColor"];
|
||||
}
|
||||
[self addAction:action];
|
||||
}
|
||||
|
||||
@end
|
||||
static const void *tui_valueCallbackKey = @"tui_valueCallbackKey";
|
||||
static const void *tui_nonValueCallbackKey = @"tui_nonValueCallbackKey";
|
||||
static const void *tui_extValueObjKey = @"tui_extValueObjKey";
|
||||
|
||||
@implementation NSObject (TUIExtValue)
|
||||
|
||||
- (void)setTui_valueCallback:(TUIValueCallbck)tui_valueCallback {
|
||||
objc_setAssociatedObject(self, tui_valueCallbackKey, tui_valueCallback, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
- (TUIValueCallbck)tui_valueCallback {
|
||||
return objc_getAssociatedObject(self, tui_valueCallbackKey);
|
||||
}
|
||||
|
||||
- (void)setTui_nonValueCallback:(TUINonValueCallbck)tui_nonValueCallback {
|
||||
objc_setAssociatedObject(self, tui_nonValueCallbackKey, tui_nonValueCallback, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
- (TUINonValueCallbck)tui_nonValueCallback {
|
||||
return objc_getAssociatedObject(self, tui_nonValueCallbackKey);
|
||||
}
|
||||
|
||||
- (void)setTui_extValueObj:(id)tui_extValueObj {
|
||||
objc_setAssociatedObject(self, tui_extValueObjKey, tui_extValueObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
- (id)tui_extValueObj {
|
||||
return objc_getAssociatedObject(self, tui_extValueObjKey);
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user