// // TCommonCell.m // TXIMSDK_TUIKit_iOS // // Created by annidyfeng on 2019/5/6. // Copyright © 2023 Tencent. All rights reserved. // #import "TUICommonModel.h" #import #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 *)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 *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 *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 _Nonnull context) { __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf handleSideSlideReturnIfNeeded:context]; }]; } else { [viewController.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(id _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)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