diff --git a/QMUIConfigurationTemplate/QMUIConfigurationTemplate.m b/QMUIConfigurationTemplate/QMUIConfigurationTemplate.m index 85688236..fbcacfa4 100644 --- a/QMUIConfigurationTemplate/QMUIConfigurationTemplate.m +++ b/QMUIConfigurationTemplate/QMUIConfigurationTemplate.m @@ -276,21 +276,18 @@ - (void)applyConfigurationTemplate { QMUICMI.automaticCustomNavigationBarTransitionStyle = NO; // AutomaticCustomNavigationBarTransitionStyle : 界面 push/pop 时是否要自动根据两个界面的 barTintColor/backgroundImage/shadowImage 的样式差异来决定是否使用自定义的导航栏效果 QMUICMI.supportedOrientationMask = UIInterfaceOrientationMaskAll; // SupportedOrientationMask : 默认支持的横竖屏方向 QMUICMI.automaticallyRotateDeviceOrientation = NO; // AutomaticallyRotateDeviceOrientation : 是否在界面切换或 viewController.supportedOrientationMask 发生变化时自动旋转屏幕(仅 iOS 15 及以前版本需要,iOS 16 系统会自动处理,该开关无意义。) - QMUICMI.defaultStatusBarStyle = UIStatusBarStyleDefault; // DefaultStatusBarStyle : 默认的状态栏样式,默认值为 UIStatusBarStyleDefault,也即在 iOS 12 及以前是黑色文字,iOS 13 及以后会自动根据当前 App 是否处于 Dark Mode 切换颜色。如果你希望固定为白色,请设置为 UIStatusBarStyleLightContent,固定黑色则设置为 QMUIStatusBarStyleDarkContent。 + QMUICMI.defaultStatusBarStyle = UIStatusBarStyleDefault; // DefaultStatusBarStyle : 默认的状态栏样式,默认值为 UIStatusBarStyleDefault,也即在 iOS 12 及以前是黑色文字,iOS 13 及以后会自动根据当前 App 是否处于 Dark Mode 切换颜色。如果你希望固定为白色,请设置为 UIStatusBarStyleLightContent,固定黑色则设置为 UIStatusBarStyleDarkContent。 QMUICMI.needsBackBarButtonItemTitle = YES; // NeedsBackBarButtonItemTitle : 全局是否需要返回按钮的 title,不需要则只显示一个返回image QMUICMI.hidesBottomBarWhenPushedInitially = NO; // HidesBottomBarWhenPushedInitially : QMUICommonViewController.hidesBottomBarWhenPushed 的初始值,默认为 NO,以保持与系统默认值一致,但通常建议改为 YES,因为一般只有 tabBar 首页那几个界面要求为 NO QMUICMI.preventConcurrentNavigationControllerTransitions = YES; // PreventConcurrentNavigationControllerTransitions : 自动保护 QMUINavigationController 在上一次 push/pop 尚未结束的时候就进行下一次 push/pop 的行为,避免产生 crash QMUICMI.navigationBarHiddenInitially = NO; // NavigationBarHiddenInitially : QMUINavigationControllerDelegate preferredNavigationBarHidden 的初始值,默认为NO - QMUICMI.shouldFixTabBarTransitionBugInIPhoneX = NO; // ShouldFixTabBarTransitionBugInIPhoneX : 是否需要自动修复 iOS 11 下,iPhone X 的设备在 push 界面时,tabBar 会瞬间往上跳的 bug QMUICMI.shouldFixTabBarSafeAreaInsetsBug = NO; // ShouldFixTabBarSafeAreaInsetsBug : 是否要对 iOS 11 及以后的版本修复当存在 UITabBar 时,UIScrollView 的 inset.bottom 可能错误的 bug(issue #218 #934),默认为 YES QMUICMI.shouldFixSearchBarMaskViewLayoutBug = NO; // ShouldFixSearchBarMaskViewLayoutBug : 是否自动修复 UISearchController.searchBar 被当作 tableHeaderView 使用时可能出现的布局 bug(issue #950) QMUICMI.shouldPrintQMUIWarnLogToConsole = IS_DEBUG; // ShouldPrintQMUIWarnLogToConsole : 是否在出现 QMUILogWarn 时自动把这些 log 以 QMUIConsole 的方式显示到设备屏幕上 QMUICMI.sendAnalyticsToQMUITeam = YES; // SendAnalyticsToQMUITeam : 是否允许在 DEBUG 模式下上报 Bundle Identifier 和 Display Name 给 QMUI 统计用 QMUICMI.dynamicPreferredValueForIPad = NO; // DynamicPreferredValueForIPad : 当 iPad 处于 Slide Over 或 Split View 分屏模式下,宏 `PreferredValueForXXX` 是否把 iPad 视为某种屏幕宽度近似的 iPhone 来取值。 - if (@available(iOS 13.0, *)) { - QMUICMI.ignoreKVCAccessProhibited = NO; // IgnoreKVCAccessProhibited : 是否全局忽略 iOS 13 对 KVC 访问 UIKit 私有属性的限制 - QMUICMI.adjustScrollIndicatorInsetsByContentInsetAdjustment = NO; // AdjustScrollIndicatorInsetsByContentInsetAdjustment : 当将 UIScrollView.contentInsetAdjustmentBehavior 设为 UIScrollViewContentInsetAdjustmentNever 时,是否自动将 UIScrollView.automaticallyAdjustsScrollIndicatorInsets 设为 NO,以保证原本在 iOS 12 下的代码不用修改就能在 iOS 13 下正常控制滚动条的位置。 - } + QMUICMI.ignoreKVCAccessProhibited = NO; // IgnoreKVCAccessProhibited : 是否全局忽略 iOS 13 对 KVC 访问 UIKit 私有属性的限制 + QMUICMI.adjustScrollIndicatorInsetsByContentInsetAdjustment = NO; // AdjustScrollIndicatorInsetsByContentInsetAdjustment : 当将 UIScrollView.contentInsetAdjustmentBehavior 设为 UIScrollViewContentInsetAdjustmentNever 时,是否自动将 UIScrollView.automaticallyAdjustsScrollIndicatorInsets 设为 NO,以保证原本在 iOS 12 下的代码不用修改就能在 iOS 13 下正常控制滚动条的位置。 } // QMUI 2.3.0 版本里,配置表新增这个方法,返回 YES 表示在 App 启动时要自动应用这份配置表。仅当你的 App 里存在多份配置表时,才需要把除默认配置表之外的其他配置表的返回值改为 NO。 diff --git a/QMUIKit.podspec b/QMUIKit.podspec index 91d24ba3..0aa820dd 100644 --- a/QMUIKit.podspec +++ b/QMUIKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "QMUIKit" - s.version = "4.6.0" + s.version = "4.6.1" s.summary = "致力于提高项目 UI 开发效率的解决方案" s.description = <<-DESC QMUI iOS 是一个致力于提高项目 UI 开发效率的解决方案,其设计目的是用于辅助快速搭建一个具备基本设计还原效果的 iOS 项目,同时利用自身提供的丰富控件及兼容处理, 让开发者能专注于业务需求而无需耗费精力在基础代码的设计上。不管是新项目的创建,或是已有项目的维护,均可使开发效率和项目质量得到大幅度提升。 @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.documentation_url = 'https://qmuiteam.com/ios/page/document.html' s.screenshot = 'https://cloud.githubusercontent.com/assets/1190261/26751376/63f96538-486a-11e7-81cf-5bc83a945207.png' - s.platform = :ios, '11.0' + s.platform = :ios, '13.0' s.frameworks = 'Foundation', 'UIKit', 'CoreGraphics' s.preserve_paths = 'QMUIConfigurationTemplate/*' s.source_files = 'QMUIKit/QMUIKit.h' diff --git a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m index cc0d5ec6..93c979c4 100644 --- a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m +++ b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m @@ -134,22 +134,6 @@ + (void)load { weakNavigationController.navigationBar.qmuinb_copyStylesToBar = nil; } break; - case QMUINavigationActionDidPop: { - - if (@available(iOS 13.0, *)) { - } else { - // iOS 12 及以下系统,在不使用自定义 titleView 的情况下,在 viewWillAppear 时通过修改 navigationBar.titleTextAttributes 来设置新界面的导航栏标题样式,push 时是生效的,但 pop 时右边界面的样式会覆盖左边界面的样式,所以 pop 时的 titleTextAttributes 改为在 did pop 时处理 - // 如果用自定义 titleView 则没这种问题,只是为了代码简单,时机的选择不区分是否自定义 title - [appearingViewController renderNavigationBarTitleAppearanceAnimated:animated]; - [weakNavigationController qmui_animateAlongsideTransition:nil completion:^(id _Nonnull context) { - // 这里要重新获取 topViewController,因为触发 pop 有两种:1. 普通完整的 pop;2.手势返回又取消。后者在 completion 里拿到的 topViewController 已经不是 completion 外面那个 appearingViewController 了,只有重新获取的 topViewController 才能代表最终可视的那个界面 - // https://github.com/Tencent/QMUI_iOS/issues/1210 - [weakNavigationController.topViewController renderNavigationBarTitleAppearanceAnimated:animated]; - }]; - } - } - break; - default: break; } @@ -404,16 +388,7 @@ - (void)renderNavigationBarAppearanceAnimated:(BOOL)animated { // iOS 13 及以上,title 的更新只在 viewWillAppear 这里进行就可以了,但 iOS 12 及以下还要靠 popViewController 那边 // iOS 12 及以下系统,在不使用自定义 titleView 的情况下,在 viewWillAppear 时通过修改 navigationBar.titleTextAttributes 来设置新界面的导航栏标题样式,push 时是生效的,但 pop 时右边界面的样式会覆盖左边界面的样式,所以 pop 时的 titleTextAttributes 改为在 did pop 时处理 // 如果用自定义 titleView 则没这种问题,只是为了代码简单,时机的选择不区分是否自定义 title - BOOL shouldRenderTitle = YES; - if (@available(iOS 13.0, *)) { - } else { - // push/pop 时如果 animated 为 NO,那么走到这里时 push/pop 已经结束了,action 处于 unknown 状态,所以这里要把 unknown 也包含进去 - // https://github.com/Tencent/QMUI_iOS/issues/1190 - shouldRenderTitle = navigationController.qmui_navigationAction >= QMUINavigationActionUnknow && navigationController.qmui_navigationAction <= QMUINavigationActionPushCompleted; - } - if (shouldRenderTitle) { - [vc renderNavigationBarTitleAppearanceAnimated:animated]; - } + [vc renderNavigationBarTitleAppearanceAnimated:animated]; } // 仅处理导航栏标题 diff --git a/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewController.m b/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewController.m index f8fd2c92..167572ab 100644 --- a/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewController.m +++ b/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewController.m @@ -147,12 +147,7 @@ - (BOOL)prefersStatusBarHidden { if (self.qmui_visibleState < QMUIViewControllerDidAppear || self.qmui_visibleState >= QMUIViewControllerDidDisappear) { // 在 present/dismiss 动画过程中,都使用原界面的状态栏显隐状态 if (self.presentingViewController) { - BOOL statusBarHidden = NO; - if (@available(iOS 13.0, *)) { - statusBarHidden = self.presentingViewController.view.window.windowScene.statusBarManager.statusBarHidden; - } else { - statusBarHidden = UIApplication.sharedApplication.statusBarHidden; - } + BOOL statusBarHidden = self.presentingViewController.view.window.windowScene.statusBarManager.statusBarHidden; self.originalStatusBarHidden = statusBarHidden; return self.originalStatusBarHidden; } diff --git a/QMUIKit/QMUIComponents/QMUIKeyboardManager.m b/QMUIKit/QMUIComponents/QMUIKeyboardManager.m index 116b5615..ad1fb54a 100644 --- a/QMUIKit/QMUIComponents/QMUIKeyboardManager.m +++ b/QMUIKit/QMUIComponents/QMUIKeyboardManager.m @@ -230,14 +230,12 @@ - (void)setNotification:(NSNotification *)notification { CGRect beginFrame = [[self.originUserInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; CGRect endFrame = [[self.originUserInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - if (@available(iOS 13.0, *)) { - // iOS 13 分屏键盘 x 不是 0,不知道是系统 BUG 还是故意这样,先这样保护,再观察一下后面的 beta 版本 - if (IS_SPLIT_SCREEN_IPAD && beginFrame.origin.x > 0) { - beginFrame.origin.x = 0; - } - if (IS_SPLIT_SCREEN_IPAD && endFrame.origin.x > 0) { - endFrame.origin.x = 0; - } + // iOS 13 分屏键盘 x 不是 0,不知道是系统 BUG 还是故意这样,先这样保护,再观察一下后面的 beta 版本 + if (IS_SPLIT_SCREEN_IPAD && beginFrame.origin.x > 0) { + beginFrame.origin.x = 0; + } + if (IS_SPLIT_SCREEN_IPAD && endFrame.origin.x > 0) { + endFrame.origin.x = 0; } _beginFrame = beginFrame; diff --git a/QMUIKit/QMUIComponents/QMUILogManagerViewController.m b/QMUIKit/QMUIComponents/QMUILogManagerViewController.m index b3ce464d..d87e432b 100644 --- a/QMUIKit/QMUIComponents/QMUILogManagerViewController.m +++ b/QMUIKit/QMUIComponents/QMUILogManagerViewController.m @@ -49,7 +49,7 @@ - (void)initTableView { - (void)initSearchController { [super initSearchController]; self.searchController.qmui_preferredStatusBarStyleBlock = ^UIStatusBarStyle{ - return QMUIStatusBarStyleDarkContent; + return UIStatusBarStyleDarkContent; }; } diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m index e4471cfa..5339e995 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m @@ -805,23 +805,6 @@ + (BOOL)hideAllVisibleModalPresentationViewControllerIfCan { @implementation QMUIModalPresentationWindow -- (void)layoutSubviews { - [super layoutSubviews]; - // 避免来电状态时只 modal 的遮罩只盖住一部分的状态栏 - // 但在 iOS 13 及以后,来电状态下状态栏的高度不会再变化了 - // https://github.com/Tencent/QMUI_iOS/issues/375 - if (@available(iOS 13.0, *)) { - } else { - if (self.rootViewController) { - UIView *rootView = self.rootViewController.view; - if (CGRectGetMinY(rootView.frame) > 0 && !UIApplication.sharedApplication.statusBarHidden && StatusBarHeight > CGRectGetMinY(rootView.frame)) { - rootView.frame = self.bounds; - } - } - } - -} - @end @implementation UIViewController (QMUIModalPresentationViewController) diff --git a/QMUIKit/QMUIComponents/QMUISearchController.m b/QMUIKit/QMUIComponents/QMUISearchController.m index e35e53f7..c9fec143 100644 --- a/QMUIKit/QMUIComponents/QMUISearchController.m +++ b/QMUIKit/QMUIComponents/QMUISearchController.m @@ -400,7 +400,7 @@ - (BOOL)shouldShowSearchBar { - (void)initSearchController { if ([self isViewLoaded] && self.shouldShowSearchBar && !self.searchController) { - self.searchController = [[QMUISearchController alloc] initWithContentsViewController:self resultsTableViewStyle:self.tableView.qmui_style]; + self.searchController = [[QMUISearchController alloc] initWithContentsViewController:self resultsTableViewStyle:self.tableView.style]; self.searchController.searchResultsDelegate = self; self.searchController.searchBar.placeholder = @"搜索"; self.searchController.searchBar.qmui_usedAsTableHeaderView = YES;// 以 tableHeaderView 的方式使用 searchBar 的话,将其置为 YES,以辅助兼容一些系统 bug diff --git a/QMUIKit/QMUIComponents/QMUITableViewHeaderFooterView.m b/QMUIKit/QMUIComponents/QMUITableViewHeaderFooterView.m index 624e9519..d0661f25 100644 --- a/QMUIKit/QMUIComponents/QMUITableViewHeaderFooterView.m +++ b/QMUIKit/QMUIComponents/QMUITableViewHeaderFooterView.m @@ -60,7 +60,7 @@ - (UIColor *)backgroundColor { - (void)updateAppearance { if (!QMUICMIActivated || (!self.parentTableView && !self.qmui_tableView) || self.type == QMUITableViewHeaderFooterViewTypeUnknow) return; - UITableViewStyle style = (self.parentTableView ?: self.qmui_tableView).qmui_style; + UITableViewStyle style = (self.parentTableView ?: self.qmui_tableView).style; if (self.type == QMUITableViewHeaderFooterViewTypeHeader) { self.titleLabel.font = PreferredValueForTableViewStyle(style, TableViewSectionHeaderFont, TableViewGroupedSectionHeaderFont, TableViewInsetGroupedSectionHeaderFont); diff --git a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m index efe2c149..d73a5326 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m +++ b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m @@ -43,28 +43,22 @@ - (instancetype)initWithName:(__kindof NSObject *)name { _name = name; self._themeIdentifiers = NSMutableArray.new; self._themes = NSMutableArray.new; - if (@available(iOS 13.0, *)) { - [UITraitCollection qmui_addUserInterfaceStyleWillChangeObserver:self selector:@selector(handleUserInterfaceStyleWillChangeEvent:)]; - } + [UITraitCollection qmui_addUserInterfaceStyleWillChangeObserver:self selector:@selector(handleUserInterfaceStyleWillChangeEvent:)]; } return self; } - (void)handleUserInterfaceStyleWillChangeEvent:(UITraitCollection *)traitCollection { if (!_respondsSystemStyleAutomatically) return; - if (@available(iOS 13.0, *)) { - if (traitCollection && self.identifierForTrait) { - self.currentThemeIdentifier = self.identifierForTrait(traitCollection); - } + if (traitCollection && self.identifierForTrait) { + self.currentThemeIdentifier = self.identifierForTrait(traitCollection); } } - (void)setRespondsSystemStyleAutomatically:(BOOL)respondsSystemStyleAutomatically { _respondsSystemStyleAutomatically = respondsSystemStyleAutomatically; - if (@available(iOS 13.0, *)) { - if (_respondsSystemStyleAutomatically && self.identifierForTrait) { - self.currentThemeIdentifier = self.identifierForTrait([UITraitCollection currentTraitCollection]); - } + if (_respondsSystemStyleAutomatically && self.identifierForTrait) { + self.currentThemeIdentifier = self.identifierForTrait([UITraitCollection currentTraitCollection]); } } diff --git a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m index 2f0968ce..4ffc1e0e 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m +++ b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m @@ -91,21 +91,11 @@ + (void)load { result.copy; }), NSStringFromClass(UIToolbar.class): @[NSStringFromSelector(@selector(barTintColor)),], - NSStringFromClass(UITabBar.class): ({ - NSMutableArray *result = @[ - NSStringFromSelector(@selector(qmui_effect)), - NSStringFromSelector(@selector(qmui_effectForegroundColor)), - ].mutableCopy; - if (@available(iOS 13.0, *)) { - // iOS 13 在 UITabBar (QMUI) 里对所有旧版接口都映射到 standardAppearance,所以重新设置一次 standardAppearance 就可以更新所有样式 - [result addObject:NSStringFromSelector(@selector(standardAppearance))]; - } else { - [result addObjectsFromArray:@[NSStringFromSelector(@selector(barTintColor)), - NSStringFromSelector(@selector(unselectedItemTintColor)), - NSStringFromSelector(@selector(selectedImageTintColor)),]]; - } - result.copy; - }), + NSStringFromClass(UITabBar.class): @[ + NSStringFromSelector(@selector(qmui_effect)), + NSStringFromSelector(@selector(qmui_effectForegroundColor)), + NSStringFromSelector(@selector(standardAppearance)), + ], NSStringFromClass(UISearchBar.class): @[NSStringFromSelector(@selector(barTintColor)), NSStringFromSelector(@selector(qmui_placeholderColor)), NSStringFromSelector(@selector(qmui_textColor)),], @@ -194,18 +184,6 @@ + (void)load { originSelectorIMP(selfObject, originCMD, tintColor); }; }); - - // iOS 12 及以下的版本,[UIView setBackgroundColor:] 并不会保存传进来的 color,所以要自己用个变量保存起来,不然 QMUIThemeColor 对象就会被丢弃 - if (@available(iOS 13.0, *)) { - } else { - ExtendImplementationOfVoidMethodWithSingleArgument([UIView class], @selector(setBackgroundColor:), UIColor *, ^(UIView *selfObject, UIColor *color) { - [selfObject qmui_bindObject:color forKey:@"UIView(QMUIThemeCompatibility).backgroundColor"]; - }); - ExtendImplementationOfNonVoidMethodWithoutArguments([UIView class], @selector(backgroundColor), UIColor *, ^UIColor *(UIView *selfObject, UIColor *originReturnValue) { - UIColor *color = [selfObject qmui_getBoundObjectForKey:@"UIView(QMUIThemeCompatibility).backgroundColor"]; - return color ?: originReturnValue; - }); - } }); } @@ -217,32 +195,29 @@ + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 这里反而是 iOS 13 才需要用 copy 的方式强制触发更新,否则如果某个 UISwitch 处于 off 的状态,此时去更新它的 onTintColor 不会立即生效,而是要等切换到 on 时,才会看到旧的 onTintColor 一闪而过变成新的 onTintColor,所以这里加个强制刷新 - if (@available(iOS 13.0, *)) { - OverrideImplementation([UISwitch class], @selector(setOnTintColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UISwitch *selfObject, UIColor *tintColor) { - - if (tintColor.qmui_isQMUIDynamicColor && tintColor == selfObject.onTintColor) tintColor = tintColor.copy; - - // call super - void (*originSelectorIMP)(id, SEL, UIColor *); - originSelectorIMP = (void (*)(id, SEL, UIColor *))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, tintColor); - }; - }); - - OverrideImplementation([UISwitch class], @selector(setThumbTintColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UISwitch *selfObject, UIColor *tintColor) { - - if (tintColor.qmui_isQMUIDynamicColor && tintColor == selfObject.thumbTintColor) tintColor = tintColor.copy; - - // call super - void (*originSelectorIMP)(id, SEL, UIColor *); - originSelectorIMP = (void (*)(id, SEL, UIColor *))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, tintColor); - }; - }); - } - + OverrideImplementation([UISwitch class], @selector(setOnTintColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UISwitch *selfObject, UIColor *tintColor) { + + if (tintColor.qmui_isQMUIDynamicColor && tintColor == selfObject.onTintColor) tintColor = tintColor.copy; + + // call super + void (*originSelectorIMP)(id, SEL, UIColor *); + originSelectorIMP = (void (*)(id, SEL, UIColor *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, tintColor); + }; + }); + + OverrideImplementation([UISwitch class], @selector(setThumbTintColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UISwitch *selfObject, UIColor *tintColor) { + + if (tintColor.qmui_isQMUIDynamicColor && tintColor == selfObject.thumbTintColor) tintColor = tintColor.copy; + + // call super + void (*originSelectorIMP)(id, SEL, UIColor *); + originSelectorIMP = (void (*)(id, SEL, UIColor *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, tintColor); + }; + }); }); } @@ -390,40 +365,6 @@ + (void)load { @end -@implementation UILabel (QMUIThemeCompatibility) - -+ (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // iOS 10-11 里,UILabel.attributedText 如果整个字符串都是同个颜色,则调用 -[UILabel setNeedsDisplay] 无法刷新文字样式,但如果字符串中存在不同 range 有不同颜色,就可以刷新。iOS 9、12-13 都没这个问题,所以这里做了兼容,给 UIView (QMUITheme) 那边刷新 UILabel 用。 - if (@available(iOS 12.0, *)) { - } else { - OverrideImplementation([UILabel class], NSSelectorFromString(@"_needsContentsFormatUpdate"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^BOOL(UILabel *selfObject) { - - __block BOOL attributedTextContainsDynamicColor = NO; - if (selfObject.attributedText) { - [selfObject.attributedText enumerateAttribute:NSForegroundColorAttributeName inRange:NSMakeRange(0, selfObject.attributedText.length) options:0 usingBlock:^(UIColor *color, NSRange range, BOOL * _Nonnull stop) { - if (color.qmui_isQMUIDynamicColor) { - attributedTextContainsDynamicColor = YES; - *stop = YES; - } - }]; - } - BOOL textColorIsDynamicColor = selfObject.textColor.qmui_isQMUIDynamicColor; - if (attributedTextContainsDynamicColor || textColorIsDynamicColor) return YES; - - BOOL (*originSelectorIMP)(id, SEL); - originSelectorIMP = (BOOL (*)(id, SEL))originalIMPProvider(); - return originSelectorIMP(selfObject, originCMD); - }; - }); - } - }); -} - -@end - @interface CALayer () @property(nonatomic, strong) UIColor *qcl_originalBackgroundColor; @@ -485,11 +426,9 @@ + (void)load { // iOS 13 下,如果系统的主题发生变化,会自动调用每个 view 的 layoutSubviews,所以我们在这里面自动更新样式 // 如果是 QMUIThemeManager 引发的主题变化,会在 theme 那边主动调用 qmui_setNeedsUpdateDynamicStyle,就不依赖这里 - if (@available(iOS 13.0, *)) { - ExtendImplementationOfVoidMethodWithoutArguments([UIView class], @selector(layoutSubviews), ^(UIView *selfObject) { - [selfObject.layer qmui_setNeedsUpdateDynamicStyle]; - }); - } + ExtendImplementationOfVoidMethodWithoutArguments([UIView class], @selector(layoutSubviews), ^(UIView *selfObject) { + [selfObject.layer qmui_setNeedsUpdateDynamicStyle]; + }); }); } @@ -543,8 +482,8 @@ + (void)load { // if (UITextFieldBorderView._image == image) return // 由于 QMUIDynamicImage 随时可能发生图片的改变,这里要绕过这个判断:必须先清空一下 image,并马上调用 layoutIfNeeded 触发 -[UITextFieldBorderView setImage:] 使得 UITextFieldBorderView 内部的 image 清空,这样再设置新的才会生效。 originSelectorIMP(selfObject, originCMD, UIImage.new, state); - [selfObject.qmui_textField setNeedsLayout]; - [selfObject.qmui_textField layoutIfNeeded]; + [selfObject.searchTextField setNeedsLayout]; + [selfObject.searchTextField layoutIfNeeded]; } originSelectorIMP(selfObject, originCMD, image, state); diff --git a/QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m b/QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m index 7852f0b9..8b18738a 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m +++ b/QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m @@ -28,32 +28,30 @@ + (void)load { // 随着 iOS 版本的迭代,需要不断检查 UIDynamicColor 对比 UIColor 多出来的方法是哪些,然后在 QMUIThemeColor 里补齐,否则可能出现”unrecognized selector sent to instance“的 crash // https://github.com/Tencent/QMUI_iOS/issues/791 #ifdef DEBUG - if (@available(iOS 13.0, *)) { - Class dynamicColorClass = NSClassFromString(@"UIDynamicColor"); - NSMutableSet *unrecognizedSelectors = NSMutableSet.new; - NSDictionary *> *methods = @{ - NSStringFromClass(UIColor.class): NSMutableSet.new, - NSStringFromClass(dynamicColorClass): NSMutableSet.new, - NSStringFromClass(self): NSMutableSet.new - }; - [methods enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull classString, NSMutableSet * _Nonnull methods, BOOL * _Nonnull stop) { - [NSObject qmui_enumrateInstanceMethodsOfClass:NSClassFromString(classString) includingInherited:NO usingBlock:^(Method _Nonnull method, SEL _Nonnull selector) { - [methods addObject:NSStringFromSelector(selector)]; - }]; + Class dynamicColorClass = NSClassFromString(@"UIDynamicColor"); + NSMutableSet *unrecognizedSelectors = NSMutableSet.new; + NSDictionary *> *methods = @{ + NSStringFromClass(UIColor.class): NSMutableSet.new, + NSStringFromClass(dynamicColorClass): NSMutableSet.new, + NSStringFromClass(self): NSMutableSet.new + }; + [methods enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull classString, NSMutableSet * _Nonnull methods, BOOL * _Nonnull stop) { + [NSObject qmui_enumrateInstanceMethodsOfClass:NSClassFromString(classString) includingInherited:NO usingBlock:^(Method _Nonnull method, SEL _Nonnull selector) { + [methods addObject:NSStringFromSelector(selector)]; }]; - [methods[NSStringFromClass(UIColor.class)] enumerateObjectsUsingBlock:^(NSString * _Nonnull selectorString, BOOL * _Nonnull stop) { - if ([methods[NSStringFromClass(dynamicColorClass)] containsObject:selectorString]) { - [methods[NSStringFromClass(dynamicColorClass)] removeObject:selectorString]; - } - }]; - [methods[NSStringFromClass(dynamicColorClass)] enumerateObjectsUsingBlock:^(NSString * _Nonnull selectorString, BOOL * _Nonnull stop) { - if (![methods[NSStringFromClass(self)] containsObject:selectorString]) { - [unrecognizedSelectors addObject:selectorString]; - } - }]; - if (unrecognizedSelectors.count > 0) { - QMUILogWarn(NSStringFromClass(self), @"%@ 还需要实现以下方法:%@", NSStringFromClass(self), unrecognizedSelectors); + }]; + [methods[NSStringFromClass(UIColor.class)] enumerateObjectsUsingBlock:^(NSString * _Nonnull selectorString, BOOL * _Nonnull stop) { + if ([methods[NSStringFromClass(dynamicColorClass)] containsObject:selectorString]) { + [methods[NSStringFromClass(dynamicColorClass)] removeObject:selectorString]; + } + }]; + [methods[NSStringFromClass(dynamicColorClass)] enumerateObjectsUsingBlock:^(NSString * _Nonnull selectorString, BOOL * _Nonnull stop) { + if (![methods[NSStringFromClass(self)] containsObject:selectorString]) { + [unrecognizedSelectors addObject:selectorString]; } + }]; + if (unrecognizedSelectors.count > 0) { + QMUILogWarn(NSStringFromClass(self), @"%@ 还需要实现以下方法:%@", NSStringFromClass(self), unrecognizedSelectors); } #endif }); diff --git a/QMUIKit/QMUIComponents/QMUITheme/UIImage+QMUITheme.m b/QMUIKit/QMUIComponents/QMUITheme/UIImage+QMUITheme.m index ab2419b3..c7a94bee 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/UIImage+QMUITheme.m +++ b/QMUIKit/QMUIComponents/QMUITheme/UIImage+QMUITheme.m @@ -462,42 +462,41 @@ + (void)load { return result; }; }); - if (@available(iOS 13.0, *)) { - // 如果一个静态的 UIImage 通过 imageWithTintColor: 传入一个动态的颜色,那么这个 UIImage 也会变成动态的,但这个动态图片是 iOS 13 系统原生的动态图片,无法响应 QMUITheme,所以这里需要为 QMUIThemeImage 做特殊处理。 - // 注意,系统的 imageWithTintColor: 不会调用 imageWithTintColor:renderingMode:,所以要分开重写两个方法 - OverrideImplementation([UIImage class], @selector(imageWithTintColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^UIImage *(UIImage *selfObject, UIColor *tintColor) { - - UIImage *result = [UIImage qmui_dynamicImageWithOriginalImage:selfObject tintColor:tintColor originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) { - aImage.qmui_shouldUseSystemIMP = YES; - return [aImage imageWithTintColor:aTintColor]; - }]; - if (!result) { - // call super - UIImage *(*originSelectorIMP)(id, SEL, UIColor *); - originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *))originalIMPProvider(); - result = originSelectorIMP(selfObject, originCMD, tintColor); - } - return result; - }; - }); - OverrideImplementation([UIImage class], @selector(imageWithTintColor:renderingMode:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^UIImage *(UIImage *selfObject, UIColor *tintColor, UIImageRenderingMode renderingMode) { - - UIImage *result = [UIImage qmui_dynamicImageWithOriginalImage:selfObject tintColor:tintColor originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) { - aImage.qmui_shouldUseSystemIMP = YES; - return [aImage imageWithTintColor:aTintColor renderingMode:renderingMode]; - }]; - if (!result) { - // call super - UIImage *(*originSelectorIMP)(id, SEL, UIColor *, UIImageRenderingMode); - originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *, UIImageRenderingMode))originalIMPProvider(); - result = originSelectorIMP(selfObject, originCMD, tintColor, renderingMode); - } - return result; - }; - }); - } + + // 如果一个静态的 UIImage 通过 imageWithTintColor: 传入一个动态的颜色,那么这个 UIImage 也会变成动态的,但这个动态图片是 iOS 13 系统原生的动态图片,无法响应 QMUITheme,所以这里需要为 QMUIThemeImage 做特殊处理。 + // 注意,系统的 imageWithTintColor: 不会调用 imageWithTintColor:renderingMode:,所以要分开重写两个方法 + OverrideImplementation([UIImage class], @selector(imageWithTintColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^UIImage *(UIImage *selfObject, UIColor *tintColor) { + + UIImage *result = [UIImage qmui_dynamicImageWithOriginalImage:selfObject tintColor:tintColor originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) { + aImage.qmui_shouldUseSystemIMP = YES; + return [aImage imageWithTintColor:aTintColor]; + }]; + if (!result) { + // call super + UIImage *(*originSelectorIMP)(id, SEL, UIColor *); + originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *))originalIMPProvider(); + result = originSelectorIMP(selfObject, originCMD, tintColor); + } + return result; + }; + }); + OverrideImplementation([UIImage class], @selector(imageWithTintColor:renderingMode:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^UIImage *(UIImage *selfObject, UIColor *tintColor, UIImageRenderingMode renderingMode) { + + UIImage *result = [UIImage qmui_dynamicImageWithOriginalImage:selfObject tintColor:tintColor originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) { + aImage.qmui_shouldUseSystemIMP = YES; + return [aImage imageWithTintColor:aTintColor renderingMode:renderingMode]; + }]; + if (!result) { + // call super + UIImage *(*originSelectorIMP)(id, SEL, UIColor *, UIImageRenderingMode); + originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *, UIImageRenderingMode))originalIMPProvider(); + result = originSelectorIMP(selfObject, originCMD, tintColor, renderingMode); + } + return result; + }; + }); generatorSupportsDynamicColor = YES; }); diff --git a/QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m b/QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m index 9c3f3791..b258ee7d 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m +++ b/QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m @@ -140,19 +140,6 @@ - (void)qmui_themeDidChangeByManager:(QMUIThemeManager *)manager identifier:(__k BOOL isValidatedEffect = [value isKindOfClass:QMUIThemeVisualEffect.class] && (!manager || [((QMUIThemeVisualEffect *)value).managerName isEqual:manager.name]); BOOL isOtherObject = ![value isKindOfClass:UIColor.class] && ![value isKindOfClass:UIImage.class] && ![value isKindOfClass:UIVisualEffect.class];// 支持所有非 color、image、effect 的其他对象,例如 NSAttributedString if (isOtherObject || isValidatedColor || isValidatedImage || isValidatedEffect) { - - // 修复 iOS 12 及以下版本,QMUIThemeImage 在搭配 resizable 使用的情况下可能无法跟随主题刷新的 bug - // https://github.com/Tencent/QMUI_iOS/issues/971 - if (@available(iOS 13.0, *)) { - } else { - if (isValidatedImage) { - QMUIThemeImage *image = (QMUIThemeImage *)value; - if (image.qmui_resizable) { - value = image.copy; - } - } - } - [self performSelector:setter withObject:value]; } EndIgnorePerformSelectorLeaksWarning @@ -173,13 +160,7 @@ - (void)qmui_themeDidChangeByManager:(QMUIThemeManager *)manager identifier:(__k textView.textColor = textView.textColor; } } else { - if (@available(iOS 12.0, *)) { - [self setNeedsDisplay]; - } else { - // 系统 UITextView 在 iOS 12 及以上重写了 -[UIView setNeedsDisplay],在里面会去刷新文字样式,但 iOS 11 及以下没有重写,所以这里对此作了兼容。实现思路是参考高版本系统的实现。 - UIView *textContainerView = [self qmui_valueForKey:@"_containerView"]; - if (textContainerView) [textContainerView setNeedsDisplay]; - } + [self setNeedsDisplay]; } } diff --git a/QMUIKit/QMUICore/QMUICommonDefines.h b/QMUIKit/QMUICore/QMUICommonDefines.h index 384b1485..17b9e739 100644 --- a/QMUIKit/QMUICore/QMUICommonDefines.h +++ b/QMUIKit/QMUICore/QMUICommonDefines.h @@ -92,8 +92,8 @@ #pragma mark - 忽略 iOS 13 KVC 访问私有属性限制 /// 将 KVC 代码包裹在这个宏中,可忽略系统的 KVC 访问限制 -#define BeginIgnoreUIKVCAccessProhibited if (@available(iOS 13.0, *)) NSThread.currentThread.qmui_shouldIgnoreUIKVCAccessProhibited = YES; -#define EndIgnoreUIKVCAccessProhibited if (@available(iOS 13.0, *)) NSThread.currentThread.qmui_shouldIgnoreUIKVCAccessProhibited = NO; +#define BeginIgnoreUIKVCAccessProhibited NSThread.currentThread.qmui_shouldIgnoreUIKVCAccessProhibited = YES; +#define EndIgnoreUIKVCAccessProhibited NSThread.currentThread.qmui_shouldIgnoreUIKVCAccessProhibited = NO; #pragma mark - 变量-设备相关 @@ -179,10 +179,10 @@ #define ScreenNativeScale ([[UIScreen mainScreen] nativeScale]) /// toolBar相关frame -#define ToolBarHeight (IS_IPAD ? (IS_NOTCHED_SCREEN ? 70 : (IOS_VERSION >= 12.0 ? 50 : 44)) : (IS_LANDSCAPE ? PreferredValueForVisualDevice(44, 32) : 44) + SafeAreaInsetsConstantForDeviceWithNotch.bottom) +#define ToolBarHeight (IS_IPAD ? (IS_NOTCHED_SCREEN ? 70 : 50) : (IS_LANDSCAPE ? PreferredValueForVisualDevice(44, 32) : 44) + SafeAreaInsetsConstantForDeviceWithNotch.bottom) /// tabBar相关frame -#define TabBarHeight (IS_IPAD ? (IS_NOTCHED_SCREEN ? 65 : (IOS_VERSION >= 12.0 ? 50 : 49)) : (IS_LANDSCAPE ? PreferredValueForVisualDevice(49, 32) : 49) + SafeAreaInsetsConstantForDeviceWithNotch.bottom) +#define TabBarHeight (IS_IPAD ? (IS_NOTCHED_SCREEN ? 65 : 50) : (IS_LANDSCAPE ? PreferredValueForVisualDevice(49, 32) : 49) + SafeAreaInsetsConstantForDeviceWithNotch.bottom) /// 状态栏高度(来电等情况下,状态栏高度会发生变化,所以应该实时计算,iOS 13 起,来电等情况下状态栏高度不会改变) #define StatusBarHeight (UIApplication.sharedApplication.statusBarHidden ? 0 : UIApplication.sharedApplication.statusBarFrame.size.height) @@ -191,7 +191,7 @@ #define StatusBarHeightConstant [QMUIHelper statusBarHeightConstant] /// navigationBar 的静态高度 -#define NavigationBarHeight (IS_IPAD ? (IOS_VERSION >= 12.0 ? 50 : 44) : (IS_LANDSCAPE ? PreferredValueForVisualDevice(44, 32) : 44)) +#define NavigationBarHeight (IS_IPAD ? 50 : (IS_LANDSCAPE ? PreferredValueForVisualDevice(44, 32) : 44)) /// 代表(导航栏+状态栏),这里用于获取其高度 /// @warn 如果是用于 viewController,请使用 UIViewController(QMUI) qmui_navigationBarMaxYInViewCoordinator 代替 @@ -297,9 +297,6 @@ AddAccessibilityHint(NSObject *obj, NSString *hint) { #pragma mark - 其他 -// 固定黑色的 StatusBarStyle,用于亮色背景,作为 -preferredStatusBarStyle 方法的 return 值使用。 -#define QMUIStatusBarStyleDarkContent [QMUIHelper statusBarStyleDarkContent] - #define StringFromBOOL(_flag) (_flag ? @"YES" : @"NO") /// 代替 NSAssert 使用,在触发 assert 之前会用 QMUILogWarn 输出日志,当你开启了配置表的 ShouldPrintQMUIWarnLogToConsole 时,会用 QMUIConsole 代替 NSAssert,避免中断当前程序的运行 diff --git a/QMUIKit/QMUICore/QMUIConfiguration.h b/QMUIKit/QMUICore/QMUIConfiguration.h index 289434ec..7ad84adb 100644 --- a/QMUIKit/QMUICore/QMUIConfiguration.h +++ b/QMUIKit/QMUICore/QMUIConfiguration.h @@ -281,7 +281,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, assign) BOOL hidesBottomBarWhenPushedInitially; @property(nonatomic, assign) BOOL preventConcurrentNavigationControllerTransitions; @property(nonatomic, assign) BOOL navigationBarHiddenInitially; -@property(nonatomic, assign) BOOL shouldFixTabBarTransitionBugInIPhoneX; @property(nonatomic, assign) BOOL shouldFixTabBarSafeAreaInsetsBug; @property(nonatomic, assign) BOOL shouldFixSearchBarMaskViewLayoutBug; @property(nonatomic, assign) BOOL sendAnalyticsToQMUITeam; diff --git a/QMUIKit/QMUICore/QMUIConfiguration.m b/QMUIKit/QMUICore/QMUIConfiguration.m index 80134a92..8a320046 100644 --- a/QMUIKit/QMUICore/QMUIConfiguration.m +++ b/QMUIKit/QMUICore/QMUIConfiguration.m @@ -868,247 +868,126 @@ - (UITabBarAppearance *)tabBarAppearance { } - (void)updateTabBarAppearance { - if (@available(iOS 13.0, *)) { - if (QMUIHelper.canUpdateAppearance) { - UITabBar.qmui_appearanceConfigured.standardAppearance = self.tabBarAppearance; + if (QMUIHelper.canUpdateAppearance) { + UITabBar.qmui_appearanceConfigured.standardAppearance = self.tabBarAppearance; #ifdef IOS15_SDK_ALLOWED - if (@available(iOS 15.0, *)) { - if (QMUICMIActivated && TabBarUsesStandardAppearanceOnly) { - UITabBar.qmui_appearanceConfigured.scrollEdgeAppearance = self.tabBarAppearance; - } + if (@available(iOS 15.0, *)) { + if (QMUICMIActivated && TabBarUsesStandardAppearanceOnly) { + UITabBar.qmui_appearanceConfigured.scrollEdgeAppearance = self.tabBarAppearance; } -#endif } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - tabBarController.tabBar.standardAppearance = self.tabBarAppearance; +#endif + } + [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { + tabBarController.tabBar.standardAppearance = self.tabBarAppearance; #ifdef IOS15_SDK_ALLOWED - if (@available(iOS 15.0, *)) { - if (QMUICMIActivated && TabBarUsesStandardAppearanceOnly) { - tabBarController.tabBar.scrollEdgeAppearance = self.tabBarAppearance; - } + if (@available(iOS 15.0, *)) { + if (QMUICMIActivated && TabBarUsesStandardAppearanceOnly) { + tabBarController.tabBar.scrollEdgeAppearance = self.tabBarAppearance; } + } #endif - [tabBarController.tabBar setNeedsLayout];// theme 不跟随系统的情况下切换 Light/Dark,tabBarAppearance.backgroundEffect 虽然值被更新了,但样式被刷新,这里手动触发一下 - }]; - } + [tabBarController.tabBar setNeedsLayout];// theme 不跟随系统的情况下切换 Light/Dark,tabBarAppearance.backgroundEffect 虽然值被更新了,但样式被刷新,这里手动触发一下 + }]; } - (void)setTabBarBarTintColor:(UIColor *)tabBarBarTintColor { [QMUIConfiguration performAction:^{ _tabBarBarTintColor = tabBarBarTintColor; - - if (@available(iOS 13.0, *)) { - self.tabBarAppearance.backgroundColor = tabBarBarTintColor; - [self updateTabBarAppearance]; - } else { - if (QMUIHelper.canUpdateAppearance) { - UITabBar.qmui_appearanceConfigured.barTintColor = _tabBarBarTintColor; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - tabBarController.tabBar.barTintColor = _tabBarBarTintColor; - }]; - } + self.tabBarAppearance.backgroundColor = tabBarBarTintColor; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarBarTintColor newValue:tabBarBarTintColor]; } - (void)setTabBarStyle:(UIBarStyle)tabBarStyle { [QMUIConfiguration performAction:^{ _tabBarStyle = tabBarStyle; - if (@available(iOS 13.0, *)) { - self.tabBarAppearance.backgroundEffect = [UIBlurEffect effectWithStyle:tabBarStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark]; - [self updateTabBarAppearance]; - } else { - if (QMUIHelper.canUpdateAppearance) { - UITabBar.qmui_appearanceConfigured.barStyle = tabBarStyle; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - tabBarController.tabBar.barStyle = tabBarStyle; - }]; - } + self.tabBarAppearance.backgroundEffect = [UIBlurEffect effectWithStyle:tabBarStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark]; + [self updateTabBarAppearance]; } ifValueChanged:@(_tabBarStyle) newValue:@(tabBarStyle)]; } - (void)setTabBarBackgroundImage:(UIImage *)tabBarBackgroundImage { [QMUIConfiguration performAction:^{ _tabBarBackgroundImage = tabBarBackgroundImage; - - if (@available(iOS 13.0, *)) { - self.tabBarAppearance.backgroundImage = tabBarBackgroundImage; - [self updateTabBarAppearance]; - } else { - if (QMUIHelper.canUpdateAppearance) { - UITabBar.qmui_appearanceConfigured.backgroundImage = tabBarBackgroundImage; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - tabBarController.tabBar.backgroundImage = tabBarBackgroundImage; - }]; - } + self.tabBarAppearance.backgroundImage = tabBarBackgroundImage; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarBackgroundImage newValue:tabBarBackgroundImage]; } - (void)setTabBarShadowImageColor:(UIColor *)tabBarShadowImageColor { [QMUIConfiguration performAction:^{ _tabBarShadowImageColor = tabBarShadowImageColor; - - if (@available(iOS 13.0, *)) { - self.tabBarAppearance.shadowColor = tabBarShadowImageColor; - [self updateTabBarAppearance]; - } else { - UIImage *shadowImage = [UIImage qmui_imageWithColor:_tabBarShadowImageColor size:CGSizeMake(1, PixelOne) cornerRadius:0]; - if (QMUIHelper.canUpdateAppearance) { - [UITabBar.qmui_appearanceConfigured setShadowImage:shadowImage]; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - tabBarController.tabBar.shadowImage = shadowImage; - }]; - } + self.tabBarAppearance.shadowColor = tabBarShadowImageColor; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarShadowImageColor newValue:tabBarShadowImageColor]; } - (void)setTabBarItemTitleFont:(UIFont *)tabBarItemTitleFont { [QMUIConfiguration performAction:^{ _tabBarItemTitleFont = tabBarItemTitleFont; - - if (@available(iOS 13.0, *)) { - [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { - NSMutableDictionary *attributes = itemAppearance.normal.titleTextAttributes.mutableCopy; - attributes[NSFontAttributeName] = tabBarItemTitleFont; - itemAppearance.normal.titleTextAttributes = attributes.copy; - }]; - [self updateTabBarAppearance]; - } else { - NSMutableDictionary *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateNormal]]; - if (_tabBarItemTitleFont) { - textAttributes[NSFontAttributeName] = _tabBarItemTitleFont; - } - if (QMUIHelper.canUpdateAppearance) { - [UITabBarItem.qmui_appearanceConfigured setTitleTextAttributes:textAttributes forState:UIControlStateNormal]; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - [tabBarController.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [obj setTitleTextAttributes:textAttributes forState:UIControlStateNormal]; - }]; - }]; - } + [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { + NSMutableDictionary *attributes = itemAppearance.normal.titleTextAttributes.mutableCopy; + attributes[NSFontAttributeName] = tabBarItemTitleFont; + itemAppearance.normal.titleTextAttributes = attributes.copy; + }]; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarItemTitleFont newValue:tabBarItemTitleFont]; } - (void)setTabBarItemTitleFontSelected:(UIFont *)tabBarItemTitleFontSelected { [QMUIConfiguration performAction:^{ _tabBarItemTitleFontSelected = tabBarItemTitleFontSelected; - - if (@available(iOS 13.0, *)) { - [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { - NSMutableDictionary *attributes = itemAppearance.selected.titleTextAttributes.mutableCopy; - attributes[NSFontAttributeName] = tabBarItemTitleFontSelected; - itemAppearance.selected.titleTextAttributes = attributes.copy; - }]; - [self updateTabBarAppearance]; - } else { - NSMutableDictionary *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateSelected]]; - if (tabBarItemTitleFontSelected) { - textAttributes[NSFontAttributeName] = tabBarItemTitleFontSelected; - } - if (QMUIHelper.canUpdateAppearance) { - [UITabBarItem.qmui_appearanceConfigured setTitleTextAttributes:textAttributes forState:UIControlStateSelected]; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - [tabBarController.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [obj setTitleTextAttributes:textAttributes forState:UIControlStateSelected]; - }]; - }]; - } + [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { + NSMutableDictionary *attributes = itemAppearance.selected.titleTextAttributes.mutableCopy; + attributes[NSFontAttributeName] = tabBarItemTitleFontSelected; + itemAppearance.selected.titleTextAttributes = attributes.copy; + }]; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarItemTitleFontSelected newValue:tabBarItemTitleFontSelected]; } - (void)setTabBarItemTitleColor:(UIColor *)tabBarItemTitleColor { [QMUIConfiguration performAction:^{ _tabBarItemTitleColor = tabBarItemTitleColor; - - if (@available(iOS 13.0, *)) { - [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { - NSMutableDictionary *attributes = itemAppearance.normal.titleTextAttributes.mutableCopy; - attributes[NSForegroundColorAttributeName] = tabBarItemTitleColor; - itemAppearance.normal.titleTextAttributes = attributes.copy; - }]; - [self updateTabBarAppearance]; - } else { - NSMutableDictionary *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateNormal]]; - textAttributes[NSForegroundColorAttributeName] = _tabBarItemTitleColor; - if (QMUIHelper.canUpdateAppearance) { - [UITabBarItem.qmui_appearanceConfigured setTitleTextAttributes:textAttributes forState:UIControlStateNormal]; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - [tabBarController.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [obj setTitleTextAttributes:textAttributes forState:UIControlStateNormal]; - }]; - }]; - } + [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { + NSMutableDictionary *attributes = itemAppearance.normal.titleTextAttributes.mutableCopy; + attributes[NSForegroundColorAttributeName] = tabBarItemTitleColor; + itemAppearance.normal.titleTextAttributes = attributes.copy; + }]; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarItemTitleColor newValue:tabBarItemTitleColor]; } - (void)setTabBarItemTitleColorSelected:(UIColor *)tabBarItemTitleColorSelected { [QMUIConfiguration performAction:^{ _tabBarItemTitleColorSelected = tabBarItemTitleColorSelected; - - if (@available(iOS 13.0, *)) { - [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { - NSMutableDictionary *attributes = itemAppearance.selected.titleTextAttributes.mutableCopy; - attributes[NSForegroundColorAttributeName] = tabBarItemTitleColorSelected; - itemAppearance.selected.titleTextAttributes = attributes.copy; - }]; - [self updateTabBarAppearance]; - } else { - NSMutableDictionary *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateSelected]]; - textAttributes[NSForegroundColorAttributeName] = _tabBarItemTitleColorSelected; - if (QMUIHelper.canUpdateAppearance) { - [UITabBarItem.qmui_appearanceConfigured setTitleTextAttributes:textAttributes forState:UIControlStateSelected]; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - [tabBarController.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [obj setTitleTextAttributes:textAttributes forState:UIControlStateSelected]; - }]; - }]; - } + [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { + NSMutableDictionary *attributes = itemAppearance.selected.titleTextAttributes.mutableCopy; + attributes[NSForegroundColorAttributeName] = tabBarItemTitleColorSelected; + itemAppearance.selected.titleTextAttributes = attributes.copy; + }]; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarItemTitleColorSelected newValue:tabBarItemTitleColorSelected]; } - (void)setTabBarItemImageColor:(UIColor *)tabBarItemImageColor { [QMUIConfiguration performAction:^{ _tabBarItemImageColor = tabBarItemImageColor; - - if (@available(iOS 13.0, *)) { - [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { - itemAppearance.normal.iconColor = tabBarItemImageColor; - }]; - [self updateTabBarAppearance]; - } else { - if (QMUIHelper.canUpdateAppearance) { - UITabBar.qmui_appearanceConfigured.unselectedItemTintColor = tabBarItemImageColor; - } - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - tabBarController.tabBar.unselectedItemTintColor = tabBarItemImageColor; - }]; - } + [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { + itemAppearance.normal.iconColor = tabBarItemImageColor; + }]; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarItemImageColor newValue:tabBarItemImageColor]; } - (void)setTabBarItemImageColorSelected:(UIColor *)tabBarItemImageColorSelected { [QMUIConfiguration performAction:^{ _tabBarItemImageColorSelected = tabBarItemImageColorSelected; - - if (@available(iOS 13.0, *)) { - [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { - itemAppearance.selected.iconColor = tabBarItemImageColorSelected; - }]; - [self updateTabBarAppearance]; - } else { - // iOS 12 及以下使用 tintColor 实现,但 tintColor 并没有声明 UI_APPEARANCE_SELECTOR,所以暂不使用 appearance 的方式去修改(虽然 appearance 方式实测是生效的) - // UITabBar.qmui_appearanceConfigured.tintColor = tabBarItemImageColorSelected; - [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) { - tabBarController.tabBar.tintColor = tabBarItemImageColorSelected; - }]; - } + [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) { + itemAppearance.selected.iconColor = tabBarItemImageColorSelected; + }]; + [self updateTabBarAppearance]; } ifValueChanged:_tabBarItemImageColorSelected newValue:tabBarItemImageColorSelected]; } diff --git a/QMUIKit/QMUICore/QMUIConfigurationMacros.h b/QMUIKit/QMUICore/QMUIConfigurationMacros.h index c9230ce3..7fc368a3 100644 --- a/QMUIKit/QMUICore/QMUIConfigurationMacros.h +++ b/QMUIKit/QMUICore/QMUIConfigurationMacros.h @@ -265,12 +265,11 @@ #define AutomaticCustomNavigationBarTransitionStyle [QMUICMI automaticCustomNavigationBarTransitionStyle] // 界面 push/pop 时是否要自动根据两个界面的 barTintColor/backgroundImage/shadowImage 的样式差异来决定是否使用自定义的导航栏效果 #define SupportedOrientationMask [QMUICMI supportedOrientationMask] // 默认支持的横竖屏方向 #define AutomaticallyRotateDeviceOrientation [QMUICMI automaticallyRotateDeviceOrientation] // 是否在界面切换或 viewController.supportedOrientationMask 发生变化时自动旋转屏幕,默认为 NO(仅 iOS 15 及以前版本需要,iOS 16 系统会自动处理,该开关无意义)。 -#define DefaultStatusBarStyle [QMUICMI defaultStatusBarStyle] // 默认的状态栏样式,默认值为 UIStatusBarStyleDefault,也即在 iOS 12 及以前是黑色文字,iOS 13 及以后会自动根据当前 App 是否处于 Dark Mode 切换颜色。如果你希望固定为白色,请设置为 UIStatusBarStyleLightContent,固定黑色则设置为 QMUIStatusBarStyleDarkContent。 +#define DefaultStatusBarStyle [QMUICMI defaultStatusBarStyle] // 默认的状态栏样式,默认值为 UIStatusBarStyleDefault,也即在 iOS 12 及以前是黑色文字,iOS 13 及以后会自动根据当前 App 是否处于 Dark Mode 切换颜色。如果你希望固定为白色,请设置为 UIStatusBarStyleLightContent,固定黑色则设置为 UIStatusBarStyleDarkContent。 #define NeedsBackBarButtonItemTitle [QMUICMI needsBackBarButtonItemTitle] // 全局是否需要返回按钮的title,不需要则只显示一个返回image #define HidesBottomBarWhenPushedInitially [QMUICMI hidesBottomBarWhenPushedInitially] // QMUICommonViewController.hidesBottomBarWhenPushed 的初始值,默认为 NO,以保持与系统默认值一致,但通常建议改为 YES,因为一般只有 tabBar 首页那几个界面要求为 NO #define PreventConcurrentNavigationControllerTransitions [QMUICMI preventConcurrentNavigationControllerTransitions] // PreventConcurrentNavigationControllerTransitions : 自动保护 QMUINavigationController 在上一次 push/pop 尚未结束的时候就进行下一次 push/pop 的行为,避免产生 crash #define NavigationBarHiddenInitially [QMUICMI navigationBarHiddenInitially] // preferredNavigationBarHidden 的初始值,默认为NO -#define ShouldFixTabBarTransitionBugInIPhoneX [QMUICMI shouldFixTabBarTransitionBugInIPhoneX] // 是否需要自动修复 iOS 11 下,iPhone X 的设备在 push 界面时,tabBar 会瞬间往上跳的 bug #define ShouldFixTabBarSafeAreaInsetsBug [QMUICMI shouldFixTabBarSafeAreaInsetsBug] // 是否要对 iOS 11 及以后的版本修复当存在 UITabBar 时,UIScrollView 的 inset.bottom 可能错误的 bug(issue #218 #934),默认为 YES #define ShouldFixSearchBarMaskViewLayoutBug [QMUICMI shouldFixSearchBarMaskViewLayoutBug] // 是否自动修复 UISearchController.searchBar 被当作 tableHeaderView 使用时可能出现的布局 bug(issue #950) #define SendAnalyticsToQMUITeam [QMUICMI sendAnalyticsToQMUITeam] // 是否允许在 DEBUG 模式下上报 Bundle Identifier 和 Display Name 给 QMUI 统计用 diff --git a/QMUIKit/QMUICore/QMUIHelper.h b/QMUIKit/QMUICore/QMUIHelper.h index 9c5b0aa2..3bafb08c 100644 --- a/QMUIKit/QMUICore/QMUIHelper.h +++ b/QMUIKit/QMUICore/QMUIHelper.h @@ -249,13 +249,6 @@ extern const CGPoint QMUIBadgeInvalidateOffset; */ + (void)resetDimmedApplicationWindow; -/** - * 黑色的 StatusBarStyle,用于亮色背景 - * @note 在 iOS 13 以前 UIStatusBarStyleDefault 状态栏内容的颜色固定是黑色的,而在 iOS 13 UIStatusBarStyleDefault 会根据 user interface style 来决定状态栏的颜色,如果你需要一直黑色可以用 QMUIStatusBarStyleDarkContent 来代替以前 UIStatusBarStyleDefault 的写法 - * @return 在 iOS 13 以上返回 UIStatusBarStyleDarkContent,在 iOS 12 及以下返回 UIStatusBarStyleDefault -*/ -@property(class, nonatomic, readonly) UIStatusBarStyle statusBarStyleDarkContent; - /** 在非 UIApplicationStateActive 的时机去设置 UIAppearance 可能引发第三方输入法 crash,因此提供这个方法判断当前是否可以更新 UIAppearance。 详情请见 https://github.com/Tencent/QMUI_iOS/issues/1281 diff --git a/QMUIKit/QMUICore/QMUIHelper.m b/QMUIKit/QMUICore/QMUIHelper.m index 32f939da..3d721513 100644 --- a/QMUIKit/QMUICore/QMUIHelper.m +++ b/QMUIKit/QMUICore/QMUIHelper.m @@ -145,11 +145,9 @@ + (CGFloat)keyboardHeightWithNotification:(NSNotification *)notification { + (CGFloat)keyboardHeightWithNotification:(nullable NSNotification *)notification inView:(nullable UIView *)view { CGRect keyboardRect = [self keyboardRectWithNotification:notification]; - if (@available(iOS 13.0, *)) { - // iOS 13 分屏键盘 x 不是 0,不知道是系统 BUG 还是故意这样,先这样保护,再观察一下后面的 beta 版本 - if (IS_SPLIT_SCREEN_IPAD && keyboardRect.origin.x > 0) { - keyboardRect.origin.x = 0; - } + // iOS 13 分屏键盘 x 不是 0,不知道是系统 BUG 还是故意这样,先这样保护,再观察一下后面的 beta 版本 + if (IS_SPLIT_SCREEN_IPAD && keyboardRect.origin.x > 0) { + keyboardRect.origin.x = 0; } if (!view) { return CGRectGetHeight(keyboardRect); } CGRect keyboardRectInView = [view convertRect:keyboardRect fromCoordinateSpace:UIScreen.mainScreen.coordinateSpace]; @@ -487,44 +485,37 @@ + (BOOL)isMac { if (@available(iOS 14.0, *)) { return [NSProcessInfo processInfo].isiOSAppOnMac || [NSProcessInfo processInfo].isMacCatalystApp; } - if (@available(iOS 13.0, *)) { - return [NSProcessInfo processInfo].isMacCatalystApp; - } - return NO; + return [NSProcessInfo processInfo].isMacCatalystApp; } static NSInteger isNotchedScreen = -1; + (BOOL)isNotchedScreen { if (isNotchedScreen < 0) { - if (@available(iOS 12.0, *)) { - /* - 检测方式解释/测试要点: - 1. iOS 11 与 iOS 12 可能行为不同,所以要分别测试。 - 2. 与触发 [QMUIHelper isNotchedScreen] 方法时的进程有关,例如 https://github.com/Tencent/QMUI_iOS/issues/482#issuecomment-456051738 里提到的 [NSObject performSelectorOnMainThread:withObject:waitUntilDone:NO] 就会导致较多的异常。 - 3. iOS 12 下,在非第2点里提到的情况下,iPhone、iPad 均可通过 UIScreen -_peripheryInsets 方法的返回值区分,但如果满足了第2点,则 iPad 无法使用这个方法,这种情况下要依赖第4点。 - 4. iOS 12 下,不管是否满足第2点,不管是什么设备类型,均可以通过一个满屏的 UIWindow 的 rootViewController.view.frame.origin.y 的值来区分,如果是非全面屏,这个值必定为20,如果是全面屏,则可能是24或44等不同的值。但由于创建 UIWindow、UIViewController 等均属于较大消耗,所以只在前面的步骤无法区分的情况下才会使用第4点。 - 5. 对于第4点,经测试与当前设备的方向、是否有勾选 project 里的 General - Hide status bar、当前是否处于来电模式的状态栏这些都没关系。 - */ - SEL peripheryInsetsSelector = NSSelectorFromString([NSString stringWithFormat:@"_%@%@", @"periphery", @"Insets"]); - UIEdgeInsets peripheryInsets = UIEdgeInsetsZero; - [[UIScreen mainScreen] qmui_performSelector:peripheryInsetsSelector withPrimitiveReturnValue:&peripheryInsets]; + /* + 检测方式解释/测试要点: + 1. iOS 11 与 iOS 12 可能行为不同,所以要分别测试。 + 2. 与触发 [QMUIHelper isNotchedScreen] 方法时的进程有关,例如 https://github.com/Tencent/QMUI_iOS/issues/482#issuecomment-456051738 里提到的 [NSObject performSelectorOnMainThread:withObject:waitUntilDone:NO] 就会导致较多的异常。 + 3. iOS 12 下,在非第2点里提到的情况下,iPhone、iPad 均可通过 UIScreen -_peripheryInsets 方法的返回值区分,但如果满足了第2点,则 iPad 无法使用这个方法,这种情况下要依赖第4点。 + 4. iOS 12 下,不管是否满足第2点,不管是什么设备类型,均可以通过一个满屏的 UIWindow 的 rootViewController.view.frame.origin.y 的值来区分,如果是非全面屏,这个值必定为20,如果是全面屏,则可能是24或44等不同的值。但由于创建 UIWindow、UIViewController 等均属于较大消耗,所以只在前面的步骤无法区分的情况下才会使用第4点。 + 5. 对于第4点,经测试与当前设备的方向、是否有勾选 project 里的 General - Hide status bar、当前是否处于来电模式的状态栏这些都没关系。 + */ + SEL peripheryInsetsSelector = NSSelectorFromString([NSString stringWithFormat:@"_%@%@", @"periphery", @"Insets"]); + UIEdgeInsets peripheryInsets = UIEdgeInsetsZero; + [[UIScreen mainScreen] qmui_performSelector:peripheryInsetsSelector withPrimitiveReturnValue:&peripheryInsets]; + if (peripheryInsets.bottom <= 0) { + UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + peripheryInsets = window.safeAreaInsets; if (peripheryInsets.bottom <= 0) { - UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; - peripheryInsets = window.safeAreaInsets; - if (peripheryInsets.bottom <= 0) { - // 使用一个强制竖屏的 rootViewController,避免一个仅支持竖屏的 App 在横屏启动时会受这里创建的 window 的影响,导致状态栏、safeAreaInsets 等错乱 - // https://github.com/Tencent/QMUI_iOS/issues/1263 - _QMUIPortraitViewController *viewController = [_QMUIPortraitViewController new]; - window.rootViewController = viewController; - if (CGRectGetMinY(viewController.view.frame) > 20) { - peripheryInsets.bottom = 1; - } + // 使用一个强制竖屏的 rootViewController,避免一个仅支持竖屏的 App 在横屏启动时会受这里创建的 window 的影响,导致状态栏、safeAreaInsets 等错乱 + // https://github.com/Tencent/QMUI_iOS/issues/1263 + _QMUIPortraitViewController *viewController = [_QMUIPortraitViewController new]; + window.rootViewController = viewController; + if (CGRectGetMinY(viewController.view.frame) > 20) { + peripheryInsets.bottom = 1; } } - isNotchedScreen = peripheryInsets.bottom > 0 ? 1 : 0; - } else { - isNotchedScreen = [QMUIHelper is58InchScreen] ? 1 : 0; } + isNotchedScreen = peripheryInsets.bottom > 0 ? 1 : 0; } return isNotchedScreen > 0; } @@ -973,13 +964,6 @@ + (void)resetDimmedApplicationWindow { [window tintColorDidChange]; } -+ (UIStatusBarStyle)statusBarStyleDarkContent { - if (@available(iOS 13.0, *)) - return UIStatusBarStyleDarkContent; - else - return UIStatusBarStyleDefault; -} - - (void)handleAppWillEnterForeground:(NSNotification *)notification { QMUIHelper.sharedInstance.shouldPreventAppearanceUpdating = NO; } diff --git a/QMUIKit/QMUIKit.h b/QMUIKit/QMUIKit.h index b4e8a1ad..2d032c54 100644 --- a/QMUIKit/QMUIKit.h +++ b/QMUIKit/QMUIKit.h @@ -13,7 +13,7 @@ #ifndef QMUIKit_h #define QMUIKit_h -static NSString * const QMUI_VERSION = @"4.6.0"; +static NSString * const QMUI_VERSION = @"4.6.1"; #if __has_include("CAAnimation+QMUI.h") #import "CAAnimation+QMUI.h" diff --git a/QMUIKit/QMUIMainFrame/QMUICommonTableViewController.m b/QMUIKit/QMUIMainFrame/QMUICommonTableViewController.m index e5481da6..ae17a186 100644 --- a/QMUIKit/QMUIMainFrame/QMUICommonTableViewController.m +++ b/QMUIKit/QMUIMainFrame/QMUICommonTableViewController.m @@ -245,7 +245,7 @@ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSIntege } } // 分别测试过 iOS 13 及以下的所有版本,最终总结,对于 Plain 类型的 tableView 而言,要去掉 header / footer 请使用 0,对于 Grouped 类型的 tableView 而言,要去掉 header / footer 请使用 CGFLOAT_MIN - return PreferredValueForTableViewStyle(tableView.qmui_style, 0, TableViewGroupedSectionHeaderDefaultHeight, TableViewInsetGroupedSectionHeaderDefaultHeight); + return PreferredValueForTableViewStyle(tableView.style, 0, TableViewGroupedSectionHeaderDefaultHeight, TableViewInsetGroupedSectionHeaderDefaultHeight); } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { @@ -257,7 +257,7 @@ - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSIntege } } // 分别测试过 iOS 13 及以下的所有版本,最终总结,对于 Plain 类型的 tableView 而言,要去掉 header / footer 请使用 0,对于 Grouped 类型的 tableView 而言,要去掉 header / footer 请使用 CGFLOAT_MIN - return PreferredValueForTableViewStyle(tableView.qmui_style, 0, TableViewGroupedSectionFooterDefaultHeight, TableViewInsetGroupedSectionFooterDefaultHeight); + return PreferredValueForTableViewStyle(tableView.style, 0, TableViewGroupedSectionFooterDefaultHeight, TableViewInsetGroupedSectionFooterDefaultHeight); } // 是否有定义某个section的header title diff --git a/QMUIKit/QMUIMainFrame/QMUINavigationController.m b/QMUIKit/QMUIMainFrame/QMUINavigationController.m index 2e77a4df..c3b62ed7 100644 --- a/QMUIKit/QMUIMainFrame/QMUINavigationController.m +++ b/QMUIKit/QMUIMainFrame/QMUINavigationController.m @@ -457,14 +457,6 @@ - (UIViewController *)childViewControllerForStatusBarWithCustomBlock:(BOOL (^)(U // 1. 有 modal present 则优先交给 modal present 的 vc 控制(例如进入搜索状态且没指定 definesPresentationContext 的 UISearchController) UIViewController *childViewController = self.visibleViewController; - // 修复在 root controller 实现了 preferredStatusBarStyle 方法并且在其中调用 childViewControllerForStatusBarStyle 方法的情况下,iOS 12 present 起 AVPlayerViewController 在 dismiss 时会触发 preferredStatusBarStyle 死循环的 bug:因为 AVPlayerViewController 内部的 preferredStatusBarStyle 会转向 presentingViewController 的 preferredStatusBarStyle,而后者又会 return AVPlayerViewController,于是死循环 - if (@available(iOS 13.0, *)) { - } else { - if ([childViewController isKindOfClass:AVPlayerViewController.class]) { - return nil; - } - } - // 2. 如果 modal present 是一个 UINavigationController,则 self.visibleViewController 拿到的是该 UINavigationController.topViewController,而不是该 UINavigationController 本身,所以这里要特殊处理一下,才能让下文的 beingDismissed 判断生效 if (childViewController.navigationController && (self.presentedViewController == childViewController.navigationController)) { childViewController = childViewController.navigationController; diff --git a/QMUIKit/UIKitExtensions/NSObject+QMUI.m b/QMUIKit/UIKitExtensions/NSObject+QMUI.m index 93d3624b..9fd21b90 100644 --- a/QMUIKit/UIKitExtensions/NSObject+QMUI.m +++ b/QMUIKit/UIKitExtensions/NSObject+QMUI.m @@ -260,25 +260,21 @@ + (void)qmui_enumerateProtocolMethods:(Protocol *)protocol usingBlock:(void (^)( @implementation NSObject (QMUI_KeyValueCoding) - (id)qmui_valueForKey:(NSString *)key { - if (@available(iOS 13.0, *)) { - if ([self isKindOfClass:[UIView class]] && QMUICMIActivated && !IgnoreKVCAccessProhibited) { - BeginIgnoreUIKVCAccessProhibited - id value = [self valueForKey:key]; - EndIgnoreUIKVCAccessProhibited - return value; - } + if ([self isKindOfClass:[UIView class]] && QMUICMIActivated && !IgnoreKVCAccessProhibited) { + BeginIgnoreUIKVCAccessProhibited + id value = [self valueForKey:key]; + EndIgnoreUIKVCAccessProhibited + return value; } return [self valueForKey:key]; } - (void)qmui_setValue:(id)value forKey:(NSString *)key { - if (@available(iOS 13.0, *)) { - if ([self isKindOfClass:[UIView class]] && QMUICMIActivated && !IgnoreKVCAccessProhibited) { - BeginIgnoreUIKVCAccessProhibited - [self setValue:value forKey:key]; - EndIgnoreUIKVCAccessProhibited - return; - } + if ([self isKindOfClass:[UIView class]] && QMUICMIActivated && !IgnoreKVCAccessProhibited) { + BeginIgnoreUIKVCAccessProhibited + [self setValue:value forKey:key]; + EndIgnoreUIKVCAccessProhibited + return; } [self setValue:value forKey:key]; @@ -508,30 +504,28 @@ @interface NSException (QMUI_KVC) @implementation NSException (QMUI_KVC) + (void)load { - if (@available(iOS 13.0, *)) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - OverrideImplementation(object_getClass([NSException class]), @selector(raise:format:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(NSObject *selfObject, NSExceptionName raise, NSString *format, ...) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + OverrideImplementation(object_getClass([NSException class]), @selector(raise:format:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(NSObject *selfObject, NSExceptionName raise, NSString *format, ...) { + + if (raise == NSGenericException && [format isEqualToString:@"Access to %@'s %@ ivar is prohibited. This is an application bug"]) { + BOOL shouldIgnoreUIKVCAccessProhibited = ((QMUICMIActivated && IgnoreKVCAccessProhibited) || NSThread.currentThread.qmui_shouldIgnoreUIKVCAccessProhibited); + if (shouldIgnoreUIKVCAccessProhibited) return; - if (raise == NSGenericException && [format isEqualToString:@"Access to %@'s %@ ivar is prohibited. This is an application bug"]) { - BOOL shouldIgnoreUIKVCAccessProhibited = ((QMUICMIActivated && IgnoreKVCAccessProhibited) || NSThread.currentThread.qmui_shouldIgnoreUIKVCAccessProhibited); - if (shouldIgnoreUIKVCAccessProhibited) return; - - QMUILogWarn(@"NSObject (QMUI)", @"使用 KVC 访问了 UIKit 的私有属性,会触发系统的 NSException,建议尽量避免此类操作,仍需访问可使用 BeginIgnoreUIKVCAccessProhibited 和 EndIgnoreUIKVCAccessProhibited 把相关代码包裹起来,或者直接使用 qmui_valueForKey: 、qmui_setValue:forKey:"); - } - - id (*originSelectorIMP)(id, SEL, NSExceptionName name, NSString *, ...); - originSelectorIMP = (id (*)(id, SEL, NSExceptionName name, NSString *, ...))originalIMPProvider(); - va_list args; - va_start(args, format); - NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; - originSelectorIMP(selfObject, originCMD, raise, reason); - va_end(args); - }; - }); + QMUILogWarn(@"NSObject (QMUI)", @"使用 KVC 访问了 UIKit 的私有属性,会触发系统的 NSException,建议尽量避免此类操作,仍需访问可使用 BeginIgnoreUIKVCAccessProhibited 和 EndIgnoreUIKVCAccessProhibited 把相关代码包裹起来,或者直接使用 qmui_valueForKey: 、qmui_setValue:forKey:"); + } + + id (*originSelectorIMP)(id, SEL, NSExceptionName name, NSString *, ...); + originSelectorIMP = (id (*)(id, SEL, NSExceptionName name, NSString *, ...))originalIMPProvider(); + va_list args; + va_start(args, format); + NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; + originSelectorIMP(selfObject, originCMD, raise, reason); + va_end(args); + }; }); - } + }); } @end diff --git a/QMUIKit/UIKitExtensions/QMUIBarProtocol/UINavigationBar+QMUIBarProtocol.m b/QMUIKit/UIKitExtensions/QMUIBarProtocol/UINavigationBar+QMUIBarProtocol.m index 21d88d20..a5948dc1 100644 --- a/QMUIKit/UIKitExtensions/QMUIBarProtocol/UINavigationBar+QMUIBarProtocol.m +++ b/QMUIKit/UIKitExtensions/QMUIBarProtocol/UINavigationBar+QMUIBarProtocol.m @@ -57,11 +57,7 @@ - (UIView *)qmui_backgroundView { - (UIImageView *)qmui_shadowImageView { // bar 在 init 完就可以获取到 backgroundView 和 shadowView,无需关心调用时机的问题 - if (@available(iOS 13, *)) { - return [self.qmui_backgroundView qmui_valueForKey:@"_shadowView1"]; - } - // iOS 10 及以后,在 bar 初始化之后就能获取到 backgroundView 和 shadowView 了 - return [self.qmui_backgroundView qmui_valueForKey:@"_shadowView"]; + return [self.qmui_backgroundView qmui_valueForKey:@"_shadowView1"]; } - (UIVisualEffectView *)qmui_effectView { @@ -74,20 +70,13 @@ - (UIVisualEffectView *)qmui_effectView { - (NSArray *)qmui_effectViews { UIView *backgroundView = self.qmui_backgroundView; NSMutableArray *result = NSMutableArray.new; - if (@available(iOS 13.0, *)) { - UIVisualEffectView *backgroundEffectView1 = [backgroundView valueForKey:@"_effectView1"]; - UIVisualEffectView *backgroundEffectView2 = [backgroundView valueForKey:@"_effectView2"]; - if (backgroundEffectView1) { - [result addObject:backgroundEffectView1]; - } - if (backgroundEffectView2) { - [result addObject:backgroundEffectView2]; - } - } else { - UIVisualEffectView *backgroundEffectView = [backgroundView qmui_valueForKey:@"_backgroundEffectView"]; - if (backgroundEffectView) { - [result addObject:backgroundEffectView]; - } + UIVisualEffectView *backgroundEffectView1 = [backgroundView valueForKey:@"_effectView1"]; + UIVisualEffectView *backgroundEffectView2 = [backgroundView valueForKey:@"_effectView2"]; + if (backgroundEffectView1) { + [result addObject:backgroundEffectView1]; + } + if (backgroundEffectView2) { + [result addObject:backgroundEffectView2]; } return result.count > 0 ? result : nil; } diff --git a/QMUIKit/UIKitExtensions/QMUIBarProtocol/UITabBar+QMUIBarProtocol.m b/QMUIKit/UIKitExtensions/QMUIBarProtocol/UITabBar+QMUIBarProtocol.m index d9cb9c41..78f24c3f 100644 --- a/QMUIKit/UIKitExtensions/QMUIBarProtocol/UITabBar+QMUIBarProtocol.m +++ b/QMUIKit/UIKitExtensions/QMUIBarProtocol/UITabBar+QMUIBarProtocol.m @@ -57,11 +57,7 @@ - (UIView *)qmui_backgroundView { - (UIImageView *)qmui_shadowImageView { // bar 在 init 完就可以获取到 backgroundView 和 shadowView,无需关心调用时机的问题 - if (@available(iOS 13, *)) { - return [self.qmui_backgroundView qmui_valueForKey:@"_shadowView1"]; - } - // iOS 10 及以后,在 bar 初始化之后就能获取到 backgroundView 和 shadowView 了 - return [self.qmui_backgroundView qmui_valueForKey:@"_shadowView"]; + return [self.qmui_backgroundView qmui_valueForKey:@"_shadowView1"]; } - (UIVisualEffectView *)qmui_effectView { @@ -74,20 +70,13 @@ - (UIVisualEffectView *)qmui_effectView { - (NSArray *)qmui_effectViews { UIView *backgroundView = self.qmui_backgroundView; NSMutableArray *result = NSMutableArray.new; - if (@available(iOS 13.0, *)) { - UIVisualEffectView *backgroundEffectView1 = [backgroundView valueForKey:@"_effectView1"]; - UIVisualEffectView *backgroundEffectView2 = [backgroundView valueForKey:@"_effectView2"]; - if (backgroundEffectView1) { - [result addObject:backgroundEffectView1]; - } - if (backgroundEffectView2) { - [result addObject:backgroundEffectView2]; - } - } else { - UIVisualEffectView *backgroundEffectView = [backgroundView qmui_valueForKey:@"_backgroundEffectView"]; - if (backgroundEffectView) { - [result addObject:backgroundEffectView]; - } + UIVisualEffectView *backgroundEffectView1 = [backgroundView valueForKey:@"_effectView1"]; + UIVisualEffectView *backgroundEffectView2 = [backgroundView valueForKey:@"_effectView2"]; + if (backgroundEffectView1) { + [result addObject:backgroundEffectView1]; + } + if (backgroundEffectView2) { + [result addObject:backgroundEffectView2]; } return result.count > 0 ? result : nil; } diff --git a/QMUIKit/UIKitExtensions/UIButton+QMUI.m b/QMUIKit/UIKitExtensions/UIButton+QMUI.m index 2eb0153e..0f81e8fe 100644 --- a/QMUIKit/UIKitExtensions/UIButton+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIButton+QMUI.m @@ -68,14 +68,12 @@ + (void)load { }; }); - if (@available(iOS 13, *)) { - ExtendImplementationOfVoidMethodWithoutArguments([UIButton class], @selector(layoutSubviews), ^(UIButton *selfObject) { - // 临时解决 iOS 13 开启了粗体文本(Bold Text)导致 UIButton Title 显示不完整 https://github.com/Tencent/QMUI_iOS/issues/620 - if (UIAccessibilityIsBoldTextEnabled()) { - [selfObject.titleLabel sizeToFit]; - } - }); - } + ExtendImplementationOfVoidMethodWithoutArguments([UIButton class], @selector(layoutSubviews), ^(UIButton *selfObject) { + // 临时解决 iOS 13 开启了粗体文本(Bold Text)导致 UIButton Title 显示不完整 https://github.com/Tencent/QMUI_iOS/issues/620 + if (UIAccessibilityIsBoldTextEnabled()) { + [selfObject.titleLabel sizeToFit]; + } + }); }); } diff --git a/QMUIKit/UIKitExtensions/UIColor+QMUI.m b/QMUIKit/UIKitExtensions/UIColor+QMUI.m index bf1911e0..5bf4ee87 100644 --- a/QMUIKit/UIKitExtensions/UIColor+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIColor+QMUI.m @@ -320,32 +320,30 @@ @implementation UIColor (QMUI_DynamicColor) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(iOS 13.0, *)) { - OverrideImplementation([UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trait) { - return [UIColor clearColor]; - }].class, @selector(CGColor), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^CGColorRef(UIColor *selfObject) { - // call super - CGColorRef (*originSelectorIMP)(id, SEL); - originSelectorIMP = (CGColorRef (*)(id, SEL))originalIMPProvider(); - CGColorRef result = originSelectorIMP(selfObject, originCMD); + OverrideImplementation([UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trait) { + return [UIColor clearColor]; + }].class, @selector(CGColor), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^CGColorRef(UIColor *selfObject) { + // call super + CGColorRef (*originSelectorIMP)(id, SEL); + originSelectorIMP = (CGColorRef (*)(id, SEL))originalIMPProvider(); + CGColorRef result = originSelectorIMP(selfObject, originCMD); + + if (selfObject.qmui_isDynamicColor) { - if (selfObject.qmui_isDynamicColor) { - - // copy - UIColor *color = [UIColor colorWithCGColor:result]; - - // CGColor 必须通过 CGColorCreate 创建。UIColor.CGColor 返回的是一个多对象复用的 CGColor 值(例如,如果 QMUIThemeA.light 值和 UIColorB 的值刚好相同,那么他们的 CGColor 可能也是同一个对象,所以 UIColorB.CGColor 可能会错误地使用了原本仅属于 QMUIThemeColorA 的 bindObject) - // 经测试,qmui_red 系列接口适用于不同的 ColorSpace,应该是能放心使用的😜 - // https://github.com/Tencent/QMUI_iOS/issues/1463 - result = CGColorCreate(CGColorSpaceCreateDeviceRGB(), (CGFloat[]){color.qmui_red, color.qmui_green, color.qmui_blue, color.qmui_alpha}); - - [(__bridge id)(result) qmui_bindObject:selfObject forKey:QMUICGColorOriginalColorBindKey]; - } - return result; - }; - }); - } + // copy + UIColor *color = [UIColor colorWithCGColor:result]; + + // CGColor 必须通过 CGColorCreate 创建。UIColor.CGColor 返回的是一个多对象复用的 CGColor 值(例如,如果 QMUIThemeA.light 值和 UIColorB 的值刚好相同,那么他们的 CGColor 可能也是同一个对象,所以 UIColorB.CGColor 可能会错误地使用了原本仅属于 QMUIThemeColorA 的 bindObject) + // 经测试,qmui_red 系列接口适用于不同的 ColorSpace,应该是能放心使用的😜 + // https://github.com/Tencent/QMUI_iOS/issues/1463 + result = CGColorCreate(CGColorSpaceCreateDeviceRGB(), (CGFloat[]){color.qmui_red, color.qmui_green, color.qmui_blue, color.qmui_alpha}); + + [(__bridge id)(result) qmui_bindObject:selfObject forKey:QMUICGColorOriginalColorBindKey]; + } + return result; + }; + }); }); } @@ -365,13 +363,9 @@ - (NSString *)qmui_name { } - (UIColor *)qmui_rawColor { - if (self.qmui_isDynamicColor) { - if (@available(iOS 13.0, *)) { - if ([self respondsToSelector:@selector(resolvedColorWithTraitCollection:)]) { - UIColor *color = [self resolvedColorWithTraitCollection:UITraitCollection.currentTraitCollection]; - return color.qmui_rawColor; - } - } + if (self.qmui_isDynamicColor && [self respondsToSelector:@selector(resolvedColorWithTraitCollection:)]) { + UIColor *color = [self resolvedColorWithTraitCollection:UITraitCollection.currentTraitCollection]; + return color.qmui_rawColor; } return self; } diff --git a/QMUIKit/UIKitExtensions/UIMenuController+QMUI.m b/QMUIKit/UIKitExtensions/UIMenuController+QMUI.m index 893f4fbd..3f9d426a 100644 --- a/QMUIKit/UIKitExtensions/UIMenuController+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIMenuController+QMUI.m @@ -42,12 +42,10 @@ + (void)load { UIMenuController *menuController = originSelectorIMP(selfObject, originCMD); /// 修复 issue:https://github.com/Tencent/QMUI_iOS/issues/659 - if (@available(iOS 13.0, *)) { - if (!kHasAddedMenuControllerNotification) { - kHasAddedMenuControllerNotification = YES; - [[NSNotificationCenter defaultCenter] addObserver:menuController selector:@selector(handleMenuWillShowNotification:) name:UIMenuControllerWillShowMenuNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:menuController selector:@selector(handleMenuWillHideNotification:) name:UIMenuControllerWillHideMenuNotification object:nil]; - } + if (!kHasAddedMenuControllerNotification) { + kHasAddedMenuControllerNotification = YES; + [[NSNotificationCenter defaultCenter] addObserver:menuController selector:@selector(handleMenuWillShowNotification:) name:UIMenuControllerWillShowMenuNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:menuController selector:@selector(handleMenuWillHideNotification:) name:UIMenuControllerWillHideMenuNotification object:nil]; } return menuController; diff --git a/QMUIKit/UIKitExtensions/UINavigationBar+QMUI.m b/QMUIKit/UIKitExtensions/UINavigationBar+QMUI.m index 6c41ff00..b0bde16f 100644 --- a/QMUIKit/UIKitExtensions/UINavigationBar+QMUI.m +++ b/QMUIKit/UIKitExtensions/UINavigationBar+QMUI.m @@ -84,81 +84,78 @@ + (void)load { // [UIKit Bug] iOS 12 及以上的系统,如果设置了自己的 leftBarButtonItem,且 title 很长时,则当 pop 的时候,title 会瞬间跳到左边,与 leftBarButtonItem 重叠 // https://github.com/Tencent/QMUI_iOS/issues/1217 - if (@available(iOS 12.0, *)) { + // _UITAMICAdaptorView + Class adaptorClass = NSClassFromString([NSString qmui_stringByConcat:@"_", @"UITAMIC", @"Adaptor", @"View", nil]); + + // -[_UINavigationBarContentView didAddSubview:] + OverrideImplementation(NSClassFromString([NSString qmui_stringByConcat:@"_", @"UINavigationBar", @"ContentView", nil]), @selector(didAddSubview:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIView *selfObject, UIView *firstArgv) { + + // call super + void (*originSelectorIMP)(id, SEL, UIView *); + originSelectorIMP = (void (*)(id, SEL, UIView *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, firstArgv); + + if ([firstArgv isKindOfClass:adaptorClass] || [firstArgv isKindOfClass:UILabel.class]) { + firstArgv.qmui_frameWillChangeBlock = ^CGRect(__kindof UIView * _Nonnull view, CGRect followingFrame) { + if ([view qmui_getBoundObjectForKey:kShouldFixTitleViewBugKey]) { + followingFrame = [[view qmui_getBoundObjectForKey:kShouldFixTitleViewBugKey] CGRectValue]; + } + return followingFrame; + }; + } + }; + }); + + void (^boundTitleViewMinXBlock)(UINavigationBar *, BOOL) = ^void(UINavigationBar *navigationBar, BOOL cleanup) { - // _UITAMICAdaptorView - Class adaptorClass = NSClassFromString([NSString qmui_stringByConcat:@"_", @"UITAMIC", @"Adaptor", @"View", nil]); + if (!navigationBar.topItem.leftBarButtonItem) return; - // -[_UINavigationBarContentView didAddSubview:] - OverrideImplementation(NSClassFromString([NSString qmui_stringByConcat:@"_", @"UINavigationBar", @"ContentView", nil]), @selector(didAddSubview:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIView *selfObject, UIView *firstArgv) { - - // call super - void (*originSelectorIMP)(id, SEL, UIView *); - originSelectorIMP = (void (*)(id, SEL, UIView *))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv); - - if ([firstArgv isKindOfClass:adaptorClass] || [firstArgv isKindOfClass:UILabel.class]) { - firstArgv.qmui_frameWillChangeBlock = ^CGRect(__kindof UIView * _Nonnull view, CGRect followingFrame) { - if ([view qmui_getBoundObjectForKey:kShouldFixTitleViewBugKey]) { - followingFrame = [[view qmui_getBoundObjectForKey:kShouldFixTitleViewBugKey] CGRectValue]; - } - return followingFrame; - }; - } - }; - }); + UIView *titleView = nil; + UIView *adapterView = navigationBar.topItem.titleView.superview; + if ([adapterView isKindOfClass:adaptorClass]) { + titleView = adapterView; + } else { + titleView = [navigationBar.qmui_contentView.subviews qmui_filterWithBlock:^BOOL(__kindof UIView * _Nonnull item) { + return [item isKindOfClass:UILabel.class]; + }].firstObject; + } + if (!titleView) return; - void (^boundTitleViewMinXBlock)(UINavigationBar *, BOOL) = ^void(UINavigationBar *navigationBar, BOOL cleanup) { + if (cleanup) { + [titleView qmui_bindObject:nil forKey:kShouldFixTitleViewBugKey]; + } else if (CGRectGetWidth(titleView.frame) > CGRectGetWidth(navigationBar.bounds) / 2) { + [titleView qmui_bindObject:[NSValue valueWithCGRect:titleView.frame] forKey:kShouldFixTitleViewBugKey]; + } + }; + + // // - [UINavigationBar _popNavigationItemWithTransition:] + // - (id) _popNavigationItemWithTransition:(int)arg1; (0x1a15513a0) + OverrideImplementation([UINavigationBar class], NSSelectorFromString([NSString qmui_stringByConcat:@"_", @"popNavigationItem", @"With", @"Transition:", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^id(UINavigationBar *selfObject, NSInteger firstArgv) { - if (!navigationBar.topItem.leftBarButtonItem) return; + boundTitleViewMinXBlock(selfObject, NO); - UIView *titleView = nil; - UIView *adapterView = navigationBar.topItem.titleView.superview; - if ([adapterView isKindOfClass:adaptorClass]) { - titleView = adapterView; - } else { - titleView = [navigationBar.qmui_contentView.subviews qmui_filterWithBlock:^BOOL(__kindof UIView * _Nonnull item) { - return [item isKindOfClass:UILabel.class]; - }].firstObject; - } - if (!titleView) return; + // call super + id (*originSelectorIMP)(id, SEL, NSInteger); + originSelectorIMP = (id (*)(id, SEL, NSInteger))originalIMPProvider(); + id result = originSelectorIMP(selfObject, originCMD, firstArgv); + return result; + }; + }); + + // - (void) _completePopOperationAnimated:(BOOL)arg1 transitionAssistant:(id)arg2; (0x1a1551668) + OverrideImplementation([UINavigationBar class], NSSelectorFromString([NSString qmui_stringByConcat:@"_", @"complete", @"PopOperationAnimated:", @"transitionAssistant:", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UINavigationBar *selfObject, BOOL firstArgv, id secondArgv) { - if (cleanup) { - [titleView qmui_bindObject:nil forKey:kShouldFixTitleViewBugKey]; - } else if (CGRectGetWidth(titleView.frame) > CGRectGetWidth(navigationBar.bounds) / 2) { - [titleView qmui_bindObject:[NSValue valueWithCGRect:titleView.frame] forKey:kShouldFixTitleViewBugKey]; - } + // call super + void (*originSelectorIMP)(id, SEL, BOOL, id); + originSelectorIMP = (void (*)(id, SEL, BOOL, id))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, firstArgv, secondArgv); + + boundTitleViewMinXBlock(selfObject, YES); }; - - // // - [UINavigationBar _popNavigationItemWithTransition:] - // - (id) _popNavigationItemWithTransition:(int)arg1; (0x1a15513a0) - OverrideImplementation([UINavigationBar class], NSSelectorFromString([NSString qmui_stringByConcat:@"_", @"popNavigationItem", @"With", @"Transition:", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^id(UINavigationBar *selfObject, NSInteger firstArgv) { - - boundTitleViewMinXBlock(selfObject, NO); - - // call super - id (*originSelectorIMP)(id, SEL, NSInteger); - originSelectorIMP = (id (*)(id, SEL, NSInteger))originalIMPProvider(); - id result = originSelectorIMP(selfObject, originCMD, firstArgv); - return result; - }; - }); - - // - (void) _completePopOperationAnimated:(BOOL)arg1 transitionAssistant:(id)arg2; (0x1a1551668) - OverrideImplementation([UINavigationBar class], NSSelectorFromString([NSString qmui_stringByConcat:@"_", @"complete", @"PopOperationAnimated:", @"transitionAssistant:", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UINavigationBar *selfObject, BOOL firstArgv, id secondArgv) { - - // call super - void (*originSelectorIMP)(id, SEL, BOOL, id); - originSelectorIMP = (void (*)(id, SEL, BOOL, id))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv, secondArgv); - - boundTitleViewMinXBlock(selfObject, YES); - }; - }); - } + }); // 以下是将 iOS 12 修改 UINavigationBar 样式的接口转换成用 iOS 13 的新接口去设置(因为新旧方法是互斥的,所以统一在新系统都用新方法) // 虽然系统的新接口是 iOS 13 就已经存在,但由于 iOS 13、14 都没必要用新接口,所以 QMUI 里在 iOS 15 才开始使用新接口,所以下方的 @available 填的是 iOS 15 而非 iOS 13(与 QMUIConfiguration.m 对应)。 diff --git a/QMUIKit/UIKitExtensions/UINavigationController+QMUI.m b/QMUIKit/UIKitExtensions/UINavigationController+QMUI.m index be7ab4ca..f530efa7 100644 --- a/QMUIKit/UIKitExtensions/UINavigationController+QMUI.m +++ b/QMUIKit/UIKitExtensions/UINavigationController+QMUI.m @@ -72,35 +72,33 @@ + (void)load { }); // iOS 12 及以前,initWithNavigationBarClass:toolbarClass:、initWithRootViewController: 会调用 initWithNibName:bundle:,所以这两个方法在 iOS 12 下不需要再次调用 qmui_didInitialize 了。 - if (@available(iOS 13.0, *)) { - OverrideImplementation([UINavigationController class], @selector(initWithNavigationBarClass:toolbarClass:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^UINavigationController *(UINavigationController *selfObject, Class firstArgv, Class secondArgv) { - - // call super - UINavigationController *(*originSelectorIMP)(id, SEL, Class, Class); - originSelectorIMP = (UINavigationController *(*)(id, SEL, Class, Class))originalIMPProvider(); - UINavigationController *result = originSelectorIMP(selfObject, originCMD, firstArgv, secondArgv); - - [selfObject qmui_didInitialize]; - - return result; - }; - }); - - OverrideImplementation([UINavigationController class], @selector(initWithRootViewController:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^UINavigationController *(UINavigationController *selfObject, UIViewController *firstArgv) { - - // call super - UINavigationController *(*originSelectorIMP)(id, SEL, UIViewController *); - originSelectorIMP = (UINavigationController *(*)(id, SEL, UIViewController *))originalIMPProvider(); - UINavigationController *result = originSelectorIMP(selfObject, originCMD, firstArgv); - - [selfObject qmui_didInitialize]; - - return result; - }; - }); - } + OverrideImplementation([UINavigationController class], @selector(initWithNavigationBarClass:toolbarClass:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^UINavigationController *(UINavigationController *selfObject, Class firstArgv, Class secondArgv) { + + // call super + UINavigationController *(*originSelectorIMP)(id, SEL, Class, Class); + originSelectorIMP = (UINavigationController *(*)(id, SEL, Class, Class))originalIMPProvider(); + UINavigationController *result = originSelectorIMP(selfObject, originCMD, firstArgv, secondArgv); + + [selfObject qmui_didInitialize]; + + return result; + }; + }); + + OverrideImplementation([UINavigationController class], @selector(initWithRootViewController:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^UINavigationController *(UINavigationController *selfObject, UIViewController *firstArgv) { + + // call super + UINavigationController *(*originSelectorIMP)(id, SEL, UIViewController *); + originSelectorIMP = (UINavigationController *(*)(id, SEL, UIViewController *))originalIMPProvider(); + UINavigationController *result = originSelectorIMP(selfObject, originCMD, firstArgv); + + [selfObject qmui_didInitialize]; + + return result; + }; + }); ExtendImplementationOfVoidMethodWithoutArguments([UINavigationController class], @selector(viewDidLoad), ^(UINavigationController *selfObject) { diff --git a/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m b/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m index d5e761ae..f438dd12 100644 --- a/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m @@ -42,33 +42,28 @@ + (void)load { NSString *result = originSelectorIMP(selfObject, originCMD); if (NSThread.isMainThread) { - result = ([NSString stringWithFormat:@"%@, contentInset = %@", result, NSStringFromUIEdgeInsets(selfObject.contentInset)]); - if (@available(iOS 13.0, *)) { - result = result.mutableCopy; - } + result = ([NSString stringWithFormat:@"%@, contentInset = %@", result, NSStringFromUIEdgeInsets(selfObject.contentInset)]).mutableCopy; } return result; }; }); - if (@available(iOS 13.0, *)) { - if (QMUICMIActivated && AdjustScrollIndicatorInsetsByContentInsetAdjustment) { - OverrideImplementation([UIScrollView class], @selector(setContentInsetAdjustmentBehavior:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIScrollView *selfObject, UIScrollViewContentInsetAdjustmentBehavior firstArgv) { - - // call super - void (*originSelectorIMP)(id, SEL, UIScrollViewContentInsetAdjustmentBehavior); - originSelectorIMP = (void (*)(id, SEL, UIScrollViewContentInsetAdjustmentBehavior))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv); - - if (firstArgv == UIScrollViewContentInsetAdjustmentNever) { - selfObject.automaticallyAdjustsScrollIndicatorInsets = NO; - } else { - selfObject.automaticallyAdjustsScrollIndicatorInsets = YES; - } - }; - }); - } + if (QMUICMIActivated && AdjustScrollIndicatorInsetsByContentInsetAdjustment) { + OverrideImplementation([UIScrollView class], @selector(setContentInsetAdjustmentBehavior:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIScrollView *selfObject, UIScrollViewContentInsetAdjustmentBehavior firstArgv) { + + // call super + void (*originSelectorIMP)(id, SEL, UIScrollViewContentInsetAdjustmentBehavior); + originSelectorIMP = (void (*)(id, SEL, UIScrollViewContentInsetAdjustmentBehavior))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, firstArgv); + + if (firstArgv == UIScrollViewContentInsetAdjustmentNever) { + selfObject.automaticallyAdjustsScrollIndicatorInsets = NO; + } else { + selfObject.automaticallyAdjustsScrollIndicatorInsets = YES; + } + }; + }); } }); } diff --git a/QMUIKit/UIKitExtensions/UISearchBar+QMUI.h b/QMUIKit/UIKitExtensions/UISearchBar+QMUI.h index 4c938ccb..612ec3f6 100644 --- a/QMUIKit/UIKitExtensions/UISearchBar+QMUI.h +++ b/QMUIKit/UIKitExtensions/UISearchBar+QMUI.h @@ -51,9 +51,6 @@ NS_ASSUME_NONNULL_BEGIN /// 支持根据 active 的值的不同来设置不一样的输入框位置偏移,当使用这个 block 后 @c qmui_textFieldMargins 无效。 @property(nonatomic, copy) UIEdgeInsets (^qmui_textFieldMarginsBlock)(__kindof UISearchBar *searchBar, BOOL active); -/// 获取 searchBar 内部的输入框的引用,在 searchBar 初始化完即可被获取 -@property(nullable, nonatomic, weak, readonly) UITextField *qmui_textField; - /// 获取 searchBar 的背景 view,为一个 UIImageView 的子类 UISearchBarBackground,在 searchBar 初始化完即可被获取 @property(nullable, nonatomic, weak, readonly) UIView *qmui_backgroundView; diff --git a/QMUIKit/UIKitExtensions/UISearchBar+QMUI.m b/QMUIKit/UIKitExtensions/UISearchBar+QMUI.m index 3dda5636..3ed6068d 100644 --- a/QMUIKit/UIKitExtensions/UISearchBar+QMUI.m +++ b/QMUIKit/UIKitExtensions/UISearchBar+QMUI.m @@ -55,30 +55,18 @@ + (void)load { } }; - if (@available(iOS 13.0, *)) { - // iOS 13 开始 UISearchBar 内部的输入框、取消按钮等 subviews 都由这个 class 创建、管理 - ExtendImplementationOfVoidMethodWithoutArguments(NSClassFromString(@"_UISearchBarVisualProviderIOS"), NSSelectorFromString(@"setUpCancelButton"), ^(NSObject *selfObject) { - UIButton *cancelButton = [selfObject qmui_valueForKey:@"cancelButton"]; - UISearchBar *searchBar = (UISearchBar *)cancelButton.superview.superview.superview; - QMUIAssert([searchBar isKindOfClass:UISearchBar.class], @"UISearchBar (QMUI)", @"Can not find UISearchBar from cancelButton"); - setupCancelButtonBlock(searchBar, cancelButton); - }); - } else { - ExtendImplementationOfVoidMethodWithoutArguments([UISearchBar class], NSSelectorFromString(@"_setupCancelButton"), ^(UISearchBar *selfObject) { - setupCancelButtonBlock(selfObject, selfObject.qmui_cancelButton); - }); - } + // iOS 13 开始 UISearchBar 内部的输入框、取消按钮等 subviews 都由这个 class 创建、管理 + ExtendImplementationOfVoidMethodWithoutArguments(NSClassFromString(@"_UISearchBarVisualProviderIOS"), NSSelectorFromString(@"setUpCancelButton"), ^(NSObject *selfObject) { + UIButton *cancelButton = [selfObject qmui_valueForKey:@"cancelButton"]; + UISearchBar *searchBar = (UISearchBar *)cancelButton.superview.superview.superview; + QMUIAssert([searchBar isKindOfClass:UISearchBar.class], @"UISearchBar (QMUI)", @"Can not find UISearchBar from cancelButton"); + setupCancelButtonBlock(searchBar, cancelButton); + }); OverrideImplementation(NSClassFromString(@"UINavigationButton"), @selector(setEnabled:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^(UIButton *selfObject, BOOL firstArgv) { - UISearchBar *searchBar = nil; - if (@available(iOS 13.0, *)) { - searchBar = (UISearchBar *)selfObject.superview.superview.superview; - } else { - searchBar = (UISearchBar *)selfObject.superview.superview; - } - + UISearchBar *searchBar = (UISearchBar *)selfObject.superview.superview.superview;; if ([searchBar isKindOfClass:UISearchBar.class] && searchBar.qmui_alwaysEnableCancelButton && !searchBar.qmui_searchController) { firstArgv = YES; } @@ -92,7 +80,7 @@ + (void)load { ExtendImplementationOfVoidMethodWithSingleArgument([UISearchBar class], @selector(setPlaceholder:), NSString *, (^(UISearchBar *selfObject, NSString *placeholder) { if (selfObject.qmui_placeholderColor || selfObject.qmui_font) { - NSMutableAttributedString *string = selfObject.qmui_textField.attributedPlaceholder.mutableCopy; + NSMutableAttributedString *string = selfObject.searchTextField.attributedPlaceholder.mutableCopy; if (selfObject.qmui_placeholderColor) { [string addAttribute:NSForegroundColorAttributeName value:selfObject.qmui_placeholderColor range:NSMakeRange(0, string.length)]; } @@ -101,85 +89,75 @@ + (void)load { } // 默认移除文字阴影 [string removeAttribute:NSShadowAttributeName range:NSMakeRange(0, string.length)]; - selfObject.qmui_textField.attributedPlaceholder = string.copy; + selfObject.searchTextField.attributedPlaceholder = string.copy; } })); // iOS 13 下,UISearchBar 内的 UITextField 的 _placeholderLabel 会在 didMoveToWindow 时被重新设置 textColor,导致我们在 searchBar 添加到界面之前设置的 placeholderColor 失效,所以在这里重新设置一遍 // https://github.com/Tencent/QMUI_iOS/issues/830 - if (@available(iOS 13.0, *)) { - ExtendImplementationOfVoidMethodWithoutArguments([UISearchBar class], @selector(didMoveToWindow), ^(UISearchBar *selfObject) { - if (selfObject.qmui_placeholderColor) { - selfObject.placeholder = selfObject.placeholder; - } - }); - } + ExtendImplementationOfVoidMethodWithoutArguments([UISearchBar class], @selector(didMoveToWindow), ^(UISearchBar *selfObject) { + if (selfObject.qmui_placeholderColor) { + selfObject.placeholder = selfObject.placeholder; + } + }); - if (@available(iOS 13.0, *)) { - // -[_UISearchBarLayout applyLayout] 是 iOS 13 系统新增的方法,该方法可能会在 -[UISearchBar layoutSubviews] 后调用,作进一步的布局调整。 - Class _UISearchBarLayoutClass = NSClassFromString([NSString stringWithFormat:@"_%@%@",@"UISearchBar", @"Layout"]); - OverrideImplementation(_UISearchBarLayoutClass, NSSelectorFromString(@"applyLayout"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIView *selfObject) { - - // call super - void (^callSuperBlock)(void) = ^{ - void (*originSelectorIMP)(id, SEL); - originSelectorIMP = (void (*)(id, SEL))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD); - }; - - UISearchBar *searchBar = (UISearchBar *)((UIView *)[selfObject qmui_valueForKey:[NSString stringWithFormat:@"_%@",@"searchBarBackground"]]).superview.superview; - - QMUIAssert(searchBar == nil || [searchBar isKindOfClass:[UISearchBar class]], @"UISearchBar (QMUI)", @"not a searchBar"); - - if (searchBar && searchBar.qmui_searchController.isBeingDismissed && searchBar.qmui_usedAsTableHeaderView) { - CGRect previousRect = searchBar.qmui_backgroundView.frame; - callSuperBlock(); - // applyLayout 方法中会修改 _searchBarBackground 的 frame ,从而覆盖掉 qmui_usedAsTableHeaderView 做出的调整,所以这里还原本次修改。 - searchBar.qmui_backgroundView.frame = previousRect; - } else { - callSuperBlock(); - } + // -[_UISearchBarLayout applyLayout] 是 iOS 13 系统新增的方法,该方法可能会在 -[UISearchBar layoutSubviews] 后调用,作进一步的布局调整。 + Class _UISearchBarLayoutClass = NSClassFromString([NSString stringWithFormat:@"_%@%@",@"UISearchBar", @"Layout"]); + OverrideImplementation(_UISearchBarLayoutClass, NSSelectorFromString(@"applyLayout"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIView *selfObject) { + + // call super + void (^callSuperBlock)(void) = ^{ + void (*originSelectorIMP)(id, SEL); + originSelectorIMP = (void (*)(id, SEL))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD); }; + + UISearchBar *searchBar = (UISearchBar *)((UIView *)[selfObject qmui_valueForKey:[NSString stringWithFormat:@"_%@",@"searchBarBackground"]]).superview.superview; - }); + QMUIAssert(searchBar == nil || [searchBar isKindOfClass:[UISearchBar class]], @"UISearchBar (QMUI)", @"not a searchBar"); + + if (searchBar && searchBar.qmui_searchController.isBeingDismissed && searchBar.qmui_usedAsTableHeaderView) { + CGRect previousRect = searchBar.qmui_backgroundView.frame; + callSuperBlock(); + // applyLayout 方法中会修改 _searchBarBackground 的 frame ,从而覆盖掉 qmui_usedAsTableHeaderView 做出的调整,所以这里还原本次修改。 + searchBar.qmui_backgroundView.frame = previousRect; + } else { + callSuperBlock(); + } + }; - if (@available(iOS 14.0, *)) { - // iOS 14 beta 1 修改了 searchTextField 的 font 属性会导致 TextField 高度异常,从而导致 searchBarContainerView 的高度异常,临时修复一下 - Class _UISearchBarContainerViewClass = NSClassFromString([NSString stringWithFormat:@"_%@%@",@"UISearchBar", @"ContainerView"]); - OverrideImplementation(_UISearchBarContainerViewClass, @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIView *selfObject, CGRect frame) { - UISearchBar *searchBar = selfObject.subviews.firstObject; - if ([searchBar isKindOfClass:[UISearchBar class]]) { - if (searchBar.qmuisb_shouldFixLayoutWhenUsedAsTableHeaderView && searchBar.qmui_isActive) { - // 刘海屏即使隐藏了 statusBar 也不会影响 containerView 的高度,要把 statusBar 计算在内 - CGFloat currentStatusBarHeight = IS_NOTCHED_SCREEN ? StatusBarHeightConstant : StatusBarHeight; - if (frame.origin.y < currentStatusBarHeight + NavigationBarHeight) { - // 非刘海屏在隐藏了 statusBar 后,如果只计算激活时的高度则为 50,这种情况下应该取 56 - frame.size.height = MAX(UISearchBar.qmuisb_seachBarDefaultActiveHeight + currentStatusBarHeight, 56); - frame.origin.y = 0; - } + }); + + if (@available(iOS 14.0, *)) { + // iOS 14 beta 1 修改了 searchTextField 的 font 属性会导致 TextField 高度异常,从而导致 searchBarContainerView 的高度异常,临时修复一下 + Class _UISearchBarContainerViewClass = NSClassFromString([NSString stringWithFormat:@"_%@%@",@"UISearchBar", @"ContainerView"]); + OverrideImplementation(_UISearchBarContainerViewClass, @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIView *selfObject, CGRect frame) { + UISearchBar *searchBar = selfObject.subviews.firstObject; + if ([searchBar isKindOfClass:[UISearchBar class]]) { + if (searchBar.qmuisb_shouldFixLayoutWhenUsedAsTableHeaderView && searchBar.qmui_isActive) { + // 刘海屏即使隐藏了 statusBar 也不会影响 containerView 的高度,要把 statusBar 计算在内 + CGFloat currentStatusBarHeight = IS_NOTCHED_SCREEN ? StatusBarHeightConstant : StatusBarHeight; + if (frame.origin.y < currentStatusBarHeight + NavigationBarHeight) { + // 非刘海屏在隐藏了 statusBar 后,如果只计算激活时的高度则为 50,这种情况下应该取 56 + frame.size.height = MAX(UISearchBar.qmuisb_seachBarDefaultActiveHeight + currentStatusBarHeight, 56); + frame.origin.y = 0; } } - void (*originSelectorIMP)(id, SEL, CGRect); - originSelectorIMP = (void (*)(id, SEL, CGRect))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, frame); - }; - }); - } + } + void (*originSelectorIMP)(id, SEL, CGRect); + originSelectorIMP = (void (*)(id, SEL, CGRect))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, frame); + }; + }); } + // -[UISearchBarTextField setFrame:] OverrideImplementation(NSClassFromString([NSString stringWithFormat:@"%@%@",@"UISearchBarText", @"Field"]), @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^(UITextField *textField, CGRect frame) { - UISearchBar *searchBar = nil; - if (@available(iOS 13.0, *)) { - searchBar = (UISearchBar *)textField.superview.superview.superview; - } else { - searchBar = (UISearchBar *)textField.superview.superview; - } - + UISearchBar *searchBar = (UISearchBar *)textField.superview.superview.superview;; QMUIAssert(searchBar == nil || [searchBar isKindOfClass:[UISearchBar class]], @"UISearchBar (QMUI)", @"not a searchBar"); - if (searchBar) { frame = [searchBar qmuisb_adjustedSearchTextFieldFrameByOriginalFrame:frame]; } @@ -275,7 +253,7 @@ - (void)setQmui_centerPlaceholder:(BOOL)qmui_centerPlaceholder { __weak __typeof(self)weakSelf = self; if (qmui_centerPlaceholder) { - self.qmui_textField.qmui_layoutSubviewsBlock = ^(UITextField * _Nonnull textField) { + self.searchTextField.qmui_layoutSubviewsBlock = ^(UITextField * _Nonnull textField) { // 某些中间状态 textField 的宽度会出现负值,但由于 CGRectGetWidth() 一定是返回正值的,所以这里必须用 bounds.size.width 的方式取值,而不是用 CGRectGetWidth() if (textField.bounds.size.width <= 0) return; @@ -301,9 +279,9 @@ - (void)setQmui_centerPlaceholder:(BOOL)qmui_centerPlaceholder { } } }; - [self.qmui_textField setNeedsLayout]; + [self.searchTextField setNeedsLayout]; } else { - self.qmui_textField.qmui_layoutSubviewsBlock = nil; + self.searchTextField.qmui_layoutSubviewsBlock = nil; self.qmuisb_centerPlaceholderCachedWidth1 = 0; self.qmuisb_centerPlaceholderCachedWidth2 = 0; [self setPositionAdjustment:UIOffsetZero forSearchBarIcon:UISearchBarIconSearch]; @@ -330,7 +308,7 @@ - (UIColor *)qmui_placeholderColor { static char kAssociatedObjectKey_TextColor; - (void)setQmui_textColor:(UIColor *)qmui_textColor { objc_setAssociatedObject(self, &kAssociatedObjectKey_TextColor, qmui_textColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - self.qmui_textField.textColor = qmui_textColor; + self.searchTextField.textColor = qmui_textColor; } - (UIColor *)qmui_textColor { @@ -346,21 +324,13 @@ - (void)setQmui_font:(UIFont *)qmui_font { } // 更新输入框的文字样式 - self.qmui_textField.font = qmui_font; + self.searchTextField.font = qmui_font; } - (UIFont *)qmui_font { return (UIFont *)objc_getAssociatedObject(self, &kAssociatedObjectKey_font); } -- (UITextField *)qmui_textField { - if (@available(iOS 13.0, *)) { - return self.searchTextField; - } - UITextField *textField = [self qmui_valueForKey:@"searchField"]; - return textField; -} - - (UIButton *)qmui_cancelButton { UIButton *cancelButton = [self qmui_valueForKey:@"cancelButton"]; return cancelButton; @@ -471,8 +441,8 @@ - (void)qmui_styledAsQMUISearchBar { // 输入框边框 UIColor *textFieldBorderColor = SearchBarTextFieldBorderColor; if (textFieldBorderColor) { - self.qmui_textField.layer.borderWidth = PixelOne; - self.qmui_textField.layer.borderColor = textFieldBorderColor.CGColor; + self.searchTextField.layer.borderWidth = PixelOne; + self.searchTextField.layer.borderColor = textFieldBorderColor.CGColor; } // 整条bar的背景 @@ -511,7 +481,7 @@ - (void)qmui_setShowsLeftAccessoryView:(BOOL)showsLeftAccessoryView animated:(BO if (animated) { if (showsLeftAccessoryView) { self.qmui_leftAccessoryView.hidden = NO; - self.qmui_leftAccessoryView.qmui_frameApplyTransform = CGRectSetXY(self.qmui_leftAccessoryView.frame, -CGRectGetWidth(self.qmui_leftAccessoryView.frame), CGRectGetMinYVerticallyCenter(self.qmui_textField.frame, self.qmui_leftAccessoryView.frame)); + self.qmui_leftAccessoryView.qmui_frameApplyTransform = CGRectSetXY(self.qmui_leftAccessoryView.frame, -CGRectGetWidth(self.qmui_leftAccessoryView.frame), CGRectGetMinYVerticallyCenter(self.searchTextField.frame, self.qmui_leftAccessoryView.frame)); [UIView animateWithDuration:.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ [self qmuisb_updateCustomTextFieldMargins]; } completion:nil]; @@ -542,7 +512,7 @@ - (BOOL)qmui_showsLeftAccessoryView { - (void)setQmui_leftAccessoryView:(UIView *)qmui_leftAccessoryView { if (self.qmui_leftAccessoryView != qmui_leftAccessoryView) { [self.qmui_leftAccessoryView removeFromSuperview]; - [self.qmui_textField.superview addSubview:qmui_leftAccessoryView]; + [self.searchTextField.superview addSubview:qmui_leftAccessoryView]; } objc_setAssociatedObject(self, &kAssociatedObjectKey_leftAccessoryView, qmui_leftAccessoryView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -569,7 +539,7 @@ - (UIEdgeInsets)qmui_leftAccessoryViewMargins { // 这个方法会在 textField 调整完布局后才调用,所以可以直接基于 textField 当前的布局去计算布局 - (void)qmuisb_adjustLeftAccessoryViewFrameAfterTextFieldLayout { if (self.qmui_leftAccessoryView && !self.qmui_leftAccessoryView.hidden) { - self.qmui_leftAccessoryView.qmui_frameApplyTransform = CGRectSetXY(self.qmui_leftAccessoryView.frame, CGRectGetMinX(self.qmui_textField.frame) - [UISearchBar qmuisb_textFieldDefaultMargins].left - self.qmui_leftAccessoryViewMargins.right - CGRectGetWidth(self.qmui_leftAccessoryView.frame), CGRectGetMinYVerticallyCenter(self.qmui_textField.frame, self.qmui_leftAccessoryView.frame)); + self.qmui_leftAccessoryView.qmui_frameApplyTransform = CGRectSetXY(self.qmui_leftAccessoryView.frame, CGRectGetMinX(self.searchTextField.frame) - [UISearchBar qmuisb_textFieldDefaultMargins].left - self.qmui_leftAccessoryViewMargins.right - CGRectGetWidth(self.qmui_leftAccessoryView.frame), CGRectGetMinYVerticallyCenter(self.searchTextField.frame, self.qmui_leftAccessoryView.frame)); } } @@ -582,7 +552,7 @@ - (void)qmui_setShowsRightAccessoryView:(BOOL)showsRightAccessoryView animated:( BOOL shouldAnimateAlpha = self.showsCancelButton;// 由于 rightAccessoryView 会从 cancelButton 那边飞过来,会有一点重叠,所以加一个 alpha 过渡 if (showsRightAccessoryView) { self.qmui_rightAccessoryView.hidden = NO; - self.qmui_rightAccessoryView.qmui_frameApplyTransform = CGRectSetXY(self.qmui_rightAccessoryView.frame, CGRectGetWidth(self.qmui_rightAccessoryView.superview.bounds), CGRectGetMinYVerticallyCenter(self.qmui_textField.frame, self.qmui_rightAccessoryView.frame)); + self.qmui_rightAccessoryView.qmui_frameApplyTransform = CGRectSetXY(self.qmui_rightAccessoryView.frame, CGRectGetWidth(self.qmui_rightAccessoryView.superview.bounds), CGRectGetMinYVerticallyCenter(self.searchTextField.frame, self.qmui_rightAccessoryView.frame)); if (shouldAnimateAlpha) { self.qmui_rightAccessoryView.alpha = 0; } @@ -625,7 +595,7 @@ - (BOOL)qmui_showsRightAccessoryView { - (void)setQmui_rightAccessoryView:(UIView *)qmui_rightAccessoryView { if (self.qmui_rightAccessoryView != qmui_rightAccessoryView) { [self.qmui_rightAccessoryView removeFromSuperview]; - [self.qmui_textField.superview addSubview:qmui_rightAccessoryView]; + [self.searchTextField.superview addSubview:qmui_rightAccessoryView]; } objc_setAssociatedObject(self, &kAssociatedObjectKey_rightAccessoryView, qmui_rightAccessoryView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -665,21 +635,16 @@ - (void)qmuisb_updateCustomTextFieldMargins { // 这个方法会在 textField 调整完布局后才调用,所以可以直接基于 textField 当前的布局去计算布局 - (void)qmuisb_adjustRightAccessoryViewFrameAfterTextFieldLayout { if (self.qmui_rightAccessoryView && !self.qmui_rightAccessoryView.hidden) { - self.qmui_rightAccessoryView.qmui_frameApplyTransform = CGRectSetXY(self.qmui_rightAccessoryView.frame, CGRectGetMaxX(self.qmui_textField.frame) + [UISearchBar qmuisb_textFieldDefaultMargins].right + self.qmui_textFieldMargins.right + self.qmui_rightAccessoryViewMargins.left, CGRectGetMinYVerticallyCenter(self.qmui_textField.frame, self.qmui_rightAccessoryView.frame)); + self.qmui_rightAccessoryView.qmui_frameApplyTransform = CGRectSetXY(self.qmui_rightAccessoryView.frame, CGRectGetMaxX(self.searchTextField.frame) + [UISearchBar qmuisb_textFieldDefaultMargins].right + self.qmui_textFieldMargins.right + self.qmui_rightAccessoryViewMargins.left, CGRectGetMinYVerticallyCenter(self.searchTextField.frame, self.qmui_rightAccessoryView.frame)); } } #pragma mark - Layout - (void)qmuisb_setNeedsLayoutTextField { - if (self.qmui_textField && !CGRectIsEmpty(self.qmui_textField.frame)) { - if (@available(iOS 13.0, *)) { - [self.qmui_textField.superview setNeedsLayout]; - [self.qmui_textField.superview layoutIfNeeded]; - } else { - [self setNeedsLayout]; - [self layoutIfNeeded]; - } + if (self.searchTextField && !CGRectIsEmpty(self.searchTextField.frame)) { + [self.searchTextField.superview setNeedsLayout]; + [self.searchTextField.superview layoutIfNeeded]; } } @@ -689,17 +654,9 @@ - (BOOL)qmuisb_shouldFixLayoutWhenUsedAsTableHeaderView { - (CGRect)qmuisb_adjustCancelButtonFrame:(CGRect)followingFrame { if (self.qmuisb_shouldFixLayoutWhenUsedAsTableHeaderView) { - CGRect textFieldFrame = self.qmui_textField.frame; - - BOOL shouldFixCancelButton = NO; - if (@available(iOS 13.0, *)) { - shouldFixCancelButton = YES;// iOS 13 当 searchBar 作为 tableHeaderView 使用时,并且非搜索状态下 searchBar.showsCancelButton = YES,则进入搜搜状态后再退出,可看到 cancelButton 下降过程中会有抖动 - } else { - shouldFixCancelButton = self.qmui_isActive; - } - if (shouldFixCancelButton) { - followingFrame = CGRectSetY(followingFrame, CGRectGetMinYVerticallyCenter(textFieldFrame, followingFrame)); - } + CGRect textFieldFrame = self.searchTextField.frame; + // iOS 13 当 searchBar 作为 tableHeaderView 使用时,并且非搜索状态下 searchBar.showsCancelButton = YES,则进入搜搜状态后再退出,可看到 cancelButton 下降过程中会有抖动 + followingFrame = CGRectSetY(followingFrame, CGRectGetMinYVerticallyCenter(textFieldFrame, followingFrame)); } if (self.qmui_cancelButtonMarginsBlock) { @@ -712,8 +669,8 @@ - (CGRect)qmuisb_adjustCancelButtonFrame:(CGRect)followingFrame { - (void)qmuisb_adjustSegmentedControlFrameIfNeeded { if (!self.qmuisb_shouldFixLayoutWhenUsedAsTableHeaderView) return; if (self.qmui_isActive) { - CGRect textFieldFrame = self.qmui_textField.frame; - if (self.qmui_segmentedControl.superview.qmui_top < self.qmui_textField.qmui_bottom) { + CGRect textFieldFrame = self.searchTextField.frame; + if (self.qmui_segmentedControl.superview.qmui_top < self.searchTextField.qmui_bottom) { // scopeBar 显示在搜索框右边 self.qmui_segmentedControl.superview.qmui_top = CGRectGetMinYVerticallyCenter(textFieldFrame, self.qmui_segmentedControl.superview.frame); } @@ -781,16 +738,11 @@ - (CGRect)qmuisb_adjustedSearchTextFieldFrameByOriginalFrame:(CGRect)frame { frame.size.height = fixedHeight; } if (self.qmui_isActive) { - BOOL statusBarHidden = NO; - if (@available(iOS 13.0, *)) { - statusBarHidden = self.window.windowScene.statusBarManager.statusBarHidden; - } else { - statusBarHidden = UIApplication.sharedApplication.statusBarHidden; - } + BOOL statusBarHidden = self.window.windowScene.statusBarManager.statusBarHidden; CGFloat visibleHeight = statusBarHidden ? 56 : 50; - frame.origin.y = (visibleHeight - self.qmui_textField.qmui_height) / 2; + frame.origin.y = (visibleHeight - self.searchTextField.qmui_height) / 2; } else if (self.qmui_searchController.isBeingDismissed) { - frame.origin.y = (56 - self.qmui_textField.qmui_height) / 2; + frame.origin.y = (56 - self.searchTextField.qmui_height) / 2; } } @@ -816,10 +768,10 @@ - (void)qmuisb_searchTextFieldFrameDidChange { // apply SearchBarTextFieldCornerRadius CGFloat textFieldCornerRadius = SearchBarTextFieldCornerRadius; if (textFieldCornerRadius != 0) { - textFieldCornerRadius = textFieldCornerRadius > 0 ? textFieldCornerRadius : CGRectGetHeight(self.qmui_textField.frame) / 2.0; + textFieldCornerRadius = textFieldCornerRadius > 0 ? textFieldCornerRadius : CGRectGetHeight(self.searchTextField.frame) / 2.0; } - self.qmui_textField.layer.cornerRadius = textFieldCornerRadius; - self.qmui_textField.clipsToBounds = textFieldCornerRadius != 0; + self.searchTextField.layer.cornerRadius = textFieldCornerRadius; + self.searchTextField.clipsToBounds = textFieldCornerRadius != 0; [self qmuisb_adjustLeftAccessoryViewFrameAfterTextFieldLayout]; [self qmuisb_adjustRightAccessoryViewFrameAfterTextFieldLayout]; @@ -882,11 +834,8 @@ - (void)qmuisb_fixSearchResultsScrollViewContentInsetIfNeeded { static CGSize textFieldDefaultSize; + (CGSize)qmuisb_textFieldDefaultSize { if (CGSizeIsEmpty(textFieldDefaultSize)) { - textFieldDefaultSize = CGSizeMake(60, 28); // 在 iOS 11 及以上,搜索输入框系统默认高度是 36,iOS 10 及以下的高度是 28 - if (@available(iOS 11.0, *)) { - textFieldDefaultSize.height = 36; - } + textFieldDefaultSize = CGSizeMake(60, 36); } return textFieldDefaultSize; } diff --git a/QMUIKit/UIKitExtensions/UISwitch+QMUI.m b/QMUIKit/UIKitExtensions/UISwitch+QMUI.m index 2578bc0d..4f2fe7af 100644 --- a/QMUIKit/UIKitExtensions/UISwitch+QMUI.m +++ b/QMUIKit/UIKitExtensions/UISwitch+QMUI.m @@ -30,8 +30,14 @@ + (void)load { }); // 设置 qmui_offTintColor 的原理是找到 UISwitch 内部的 switchWellView 并改变它的 backgroundColor,而 switchWellView 在某些时机会重新创建 ,因此需要在这些时机之后对 switchWellView 重新设置一次背景颜色: - if (@available(iOS 13.0, *)) { - ExtendImplementationOfVoidMethodWithSingleArgument([UISwitch class], @selector(traitCollectionDidChange:), UITraitCollection *, ^(UISwitch *selfObject, UITraitCollection *previousTraitCollection) { + OverrideImplementation([UISwitch class], @selector(traitCollectionDidChange:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UISwitch *selfObject, UITraitCollection *previousTraitCollection) { + + // call super + void (*originSelectorIMP)(id, SEL, UITraitCollection *); + originSelectorIMP = (void (*)(id, SEL, UITraitCollection *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, previousTraitCollection); + BOOL interfaceStyleChanged = [previousTraitCollection hasDifferentColorAppearanceComparedToTraitCollection:selfObject.traitCollection]; if (interfaceStyleChanged) { // 在 iOS 13 切换 Dark/Light Mode 之后,会在重新创建 switchWellView,之所以延迟一个 runloop 是因为这个时机是在晚于 traitCollectionDidChange 的 _traitCollectionDidChangeInternal中进行 @@ -39,18 +45,8 @@ + (void)load { [selfObject qmui_applyOffTintColorIfNeeded]; }); } - }); - } else { - // iOS 9 - 12 上调用 setOnTintColor: 或 setTintColor: 之后,会在重新创建 switchWellView - ExtendImplementationOfVoidMethodWithSingleArgument([UISwitch class], @selector(setTintColor:), UIColor *, ^(UISwitch *selfObject, UIColor *firstArgv) { - [selfObject qmui_applyOffTintColorIfNeeded]; - }); - ExtendImplementationOfVoidMethodWithSingleArgument([UISwitch class], @selector(setOnTintColor:), UIColor *, ^(UISwitch *selfObject, UIColor *firstArgv) { - [selfObject qmui_applyOffTintColorIfNeeded]; - }); - - } - + }; + }); }); } diff --git a/QMUIKit/UIKitExtensions/UITabBar+QMUI.m b/QMUIKit/UIKitExtensions/UITabBar+QMUI.m index 64097191..00658c7f 100644 --- a/QMUIKit/UIKitExtensions/UITabBar+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITabBar+QMUI.m @@ -43,46 +43,6 @@ + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - ExtendImplementationOfVoidMethodWithoutArguments([UITabBarController class], @selector(viewDidLoad), ^(UITabBarController *selfObject) { - if (QMUICMIActivated) { - if (@available(iOS 13.0, *)) { - // iOS 13 不使用 tintColor 了,改为用 UITabBarAppearance,具体请看 QMUIConfiguration.m - } else { - // 根据 TabBarContainerClasses 的值来决定是否设置 UITabBar.tintColor - // UITabBar.tintColor 没有被添加 UI_APPEARANCE_SELECTOR 标记,所以没有采用 UIAppearance 的方式去实现(虽然它实际上是支持的) - BOOL shouldSetTintColor = NO; - if (TabBarContainerClasses.count) { - for (Class class in TabBarContainerClasses) { - if ([selfObject isKindOfClass:class]) { - shouldSetTintColor = YES; - break; - } - } - } else { - shouldSetTintColor = YES; - } - if (shouldSetTintColor) { - selfObject.tabBar.tintColor = TabBarItemImageColorSelected; - } - } - } - }); - - // iOS 12 及以下,如果 UITabBar backgroundImage 为 nil,则 tabBar 会显示磨砂背景,此时不管怎么修改 shadowImage 都无效,都会显示系统默认的分隔线,导致无法很好地统一不同 iOS 版本的表现(iOS 13 及以上没有这个限制),所以这里做了兼容。 - if (@available(iOS 13.0, *)) { - } else { - ExtendImplementationOfVoidMethodWithoutArguments(NSClassFromString(@"_UITabBarVisualProviderLegacyIOS"), NSSelectorFromString(@"_updateBackground"), ^(NSObject *selfObject) { - UITabBar *tabBar = [selfObject qmui_valueForKey:@"tabBar"]; - if (!tabBar) return; - UIImage *shadowImage = tabBar.shadowImage;// 就算 tabBar 显示系统的分隔线,但依然能从 shadowImage 属性获取到业务自己设置的图片 - UIImageView *shadowImageView = tabBar.qmui_shadowImageView; - if (shadowImage && shadowImageView && shadowImageView.backgroundColor && !shadowImageView.image) { - shadowImageView.backgroundColor = nil; - shadowImageView.image = shadowImage; - } - }); - } - OverrideImplementation([UITabBar class], @selector(setItems:animated:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^void(UITabBar *selfObject, NSArray *items, BOOL animated) { @@ -115,174 +75,36 @@ + (void)load { }; }); - OverrideImplementation([UITabBar class], @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UITabBar *selfObject, CGRect frame) { + // iOS 13 下如果以 UITabBarAppearance 的方式将 UITabBarItem 的 font 大小设置为超过默认的 10,则会出现布局错误,文字被截断,所以这里做了个兼容,iOS 14.0 测试过已不存在该问题 + // https://github.com/Tencent/QMUI_iOS/issues/740 + // + // iOS 14 修改 UITabBarAppearance.inlineLayoutAppearance.normal.titleTextAttributes[NSForegroundColor] 会导致 UITabBarItem 文字无法完整展示 + // https://github.com/Tencent/QMUI_iOS/issues/1110 + // + // [UIKit Bug] 使用 UITabBarAppearance 将 UITabBarItem 选中时的字体设置为 bold 则无法完整显示 title + // https://github.com/Tencent/QMUI_iOS/issues/1286 + OverrideImplementation(NSClassFromString(@"UITabBarButtonLabel"), @selector(setAttributedText:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UILabel *selfObject, NSAttributedString *firstArgv) { - if (UIApplication.sharedApplication.qmui_didFinishLaunching) { - if (QMUICMIActivated && ShouldFixTabBarTransitionBugInIPhoneX && IOS_VERSION < 11.2 && IS_58INCH_SCREEN) { - if (CGRectGetHeight(frame) == TabBarHeight && CGRectGetMaxY(frame) < CGRectGetHeight(selfObject.superview.bounds)) { - // iOS 11 在界面 push 的过程中 tabBar 会瞬间往上跳,所以做这个修复。这个 bug 在 iOS 11.2 里已被系统修复。 - // https://github.com/Tencent/QMUI_iOS/issues/217 - frame = CGRectSetY(frame, CGRectGetHeight(selfObject.superview.bounds) - CGRectGetHeight(frame)); - } - } - - // [UIKit Bug] iOS 11-12,opaque 的 tabBar 在某些情况下会高度塌陷 - // https://github.com/Tencent/QMUI_iOS/issues/309 - // [UIKit Bug] iOS 11-12,全面屏设备下,带 TabBar 的界面在 push/pop 后,UIScrollView 的滚动位置可能发生变化 - // https://github.com/Tencent/QMUI_iOS/issues/934 - if (@available(iOS 13.0, *)) { - } else if (IS_NOTCHED_SCREEN && ((CGRectGetHeight(frame) == 49 || CGRectGetHeight(frame) == 32))) {// 只关注全面屏设备下的这两种非正常的 tabBar 高度即可 - CGFloat bottomSafeAreaInsets = selfObject.safeAreaInsets.bottom > 0 ? selfObject.safeAreaInsets.bottom : selfObject.superview.safeAreaInsets.bottom;// 注意,如果只是拿 selfObject.safeAreaInsets 判断,会肉眼看到高度的跳变,因此引入 superview 的值(虽然理论上 tabBar 不一定都会布局到 UITabBarController.view 的底部) - if (bottomSafeAreaInsets == CGRectGetHeight(selfObject.frame)) { - return;// 由于这个系统 bug https://github.com/Tencent/QMUI_iOS/issues/446,这里先暂时屏蔽本次 frame 变化 - } - frame.size.height += bottomSafeAreaInsets; - frame.origin.y -= bottomSafeAreaInsets; + // call super + void (*originSelectorIMP)(id, SEL, NSAttributedString *); + originSelectorIMP = (void (*)(id, SEL, NSAttributedString *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, firstArgv); + + if (@available(iOS 14.0, *)) { + // iOS 14 只有在 bold 时才有问题,所以把额外的 sizeToFit 做一些判断,尽量减少调用次数 + UIFont *font = selfObject.font; + BOOL isBold = [font.fontName containsString:@"bold"]; + if (isBold) { + [selfObject sizeToFit]; } + } else { + // iOS 13 加粗时有 #1286 描述的问题,不加粗时有 #740 描述的问题,所以干脆只要是 iOS 13 都加粗算了 + [selfObject sizeToFit]; } - - // call super - void (*originSelectorIMP)(id, SEL, CGRect); - originSelectorIMP = (void (*)(id, SEL, CGRect))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, frame); }; }); - // 以下代码修复两个仅存在于 12.1.0 版本的系统 bug,实测 12.1.1 苹果已经修复 - if (@available(iOS 12.1, *)) { - if (@available(iOS 12.1.1, *)) { - } else { - OverrideImplementation(NSClassFromString(@"UITabBarButton"), @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIView *selfObject, CGRect firstArgv) { - - // Fixed: UITabBar layout is broken on iOS 12.1 - // https://github.com/Tencent/QMUI_iOS/issues/410 - - if (!CGRectIsEmpty(selfObject.frame) && CGRectIsEmpty(firstArgv)) { - return; - } - - // Fixed: iOS 12.1 UITabBarItem positioning issue during swipe back gesture (when UINavigationBar is hidden) - // https://github.com/Tencent/QMUI_iOS/issues/422 - if (IS_NOTCHED_SCREEN) { - if ((CGRectGetHeight(selfObject.frame) == 48 && CGRectGetHeight(firstArgv) == 33) || (CGRectGetHeight(selfObject.frame) == 31 && CGRectGetHeight(firstArgv) == 20)) { - return; - } - } - - // call super - void (*originSelectorIMP)(id, SEL, CGRect); - originSelectorIMP = (void (*)(id, SEL, CGRect))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv); - }; - }); - } - } - - if (@available(iOS 13.0, *)) { - } else { - Class tabBarButtonLabelClass = NSClassFromString(@"UITabBarButtonLabel"); - - UITabBarItem *(^tabBarItemOfLabelBlock)(UILabel *label) = ^UITabBarItem *(UILabel *label) { - UIControl *tabBarButton = [label qmui_valueForKey:@"_tabBarButton"]; - UITabBar *tabBar = [tabBarButton qmui_valueForKey:@"tabBar"]; - __block UITabBarItem *tabBarItem = nil; - if (!tabBar) { - return nil; - } - [tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if (obj.qmui_view == tabBarButton) { - tabBarItem = obj; - *stop = YES; - } - }]; - return tabBarItem; - }; - - // iOS 12,如果用 UIAppearance 的方式设置了 UITabBar.appearance.unselectedItemTintColor,此时不管以 appearance 方式修改 UITabBarItem titleTextAttributes 的 NSForegroundColorAttributeName,或是直接修改 UITabBarItem 实例,均会被 unselectedItemTintColor 覆盖,所以这里做个保护 - OverrideImplementation(tabBarButtonLabelClass, NSSelectorFromString(@"_setUnselectedTintColor:"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UILabel *selfObject, UIColor *firstArgv) { - - UITabBarItem *item = tabBarItemOfLabelBlock(selfObject); - if (item) { - UITabBar *tabBar = [[selfObject qmui_valueForKey:@"_tabBarButton"] qmui_valueForKey:@"tabBar"]; - NSDictionary *normalAttributes = [item titleTextAttributesForState:UIControlStateNormal] ?: [UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateNormal]; - UIColor *normalColor = normalAttributes[NSForegroundColorAttributeName]; - UIColor *unselectedTintColor = tabBar.unselectedItemTintColor; - if (normalColor && [unselectedTintColor isEqual:firstArgv] && ![normalColor isEqual:unselectedTintColor]) { - return; - } - } - - // call super - void (*originSelectorIMP)(id, SEL, UIColor *); - originSelectorIMP = (void (*)(id, SEL, UIColor *))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv); - }; - }); - - // 修复系统在 iOS 12 及以下,通过 [UITabBarItem setTitleTextAttributes:forState:] 设置的 selected 字体无法生效的 bug(selected 的颜色是可以生效的) - OverrideImplementation(tabBarButtonLabelClass, @selector(setSelected:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UILabel *selfObject, BOOL selected) { - - // call super - void (*originSelectorIMP)(id, SEL, BOOL); - originSelectorIMP = (void (*)(id, SEL, BOOL))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, selected); - - UITabBarItem *item = tabBarItemOfLabelBlock(selfObject); - if (!item) { - return; - } - - NSDictionary *normalAttributes = [item titleTextAttributesForState:UIControlStateNormal] ?: [UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateNormal]; - NSDictionary *selectedAttributes = [item titleTextAttributesForState:UIControlStateSelected] ?: [UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateSelected]; - if (normalAttributes[NSFontAttributeName] && selectedAttributes[NSFontAttributeName]) { - if (selected) { - selfObject.font = selectedAttributes[NSFontAttributeName]; - } else { - selfObject.font = normalAttributes[NSFontAttributeName]; - } - [selfObject sizeToFit]; - [selfObject.superview setNeedsLayout]; - } - }; - }); - } - - if (@available(iOS 13.0, *)) { - // iOS 13 下如果以 UITabBarAppearance 的方式将 UITabBarItem 的 font 大小设置为超过默认的 10,则会出现布局错误,文字被截断,所以这里做了个兼容,iOS 14.0 测试过已不存在该问题 - // https://github.com/Tencent/QMUI_iOS/issues/740 - // - // iOS 14 修改 UITabBarAppearance.inlineLayoutAppearance.normal.titleTextAttributes[NSForegroundColor] 会导致 UITabBarItem 文字无法完整展示 - // https://github.com/Tencent/QMUI_iOS/issues/1110 - // - // [UIKit Bug] 使用 UITabBarAppearance 将 UITabBarItem 选中时的字体设置为 bold 则无法完整显示 title - // https://github.com/Tencent/QMUI_iOS/issues/1286 - OverrideImplementation(NSClassFromString(@"UITabBarButtonLabel"), @selector(setAttributedText:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UILabel *selfObject, NSAttributedString *firstArgv) { - - // call super - void (*originSelectorIMP)(id, SEL, NSAttributedString *); - originSelectorIMP = (void (*)(id, SEL, NSAttributedString *))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv); - - if (@available(iOS 14.0, *)) { - // iOS 14 只有在 bold 时才有问题,所以把额外的 sizeToFit 做一些判断,尽量减少调用次数 - UIFont *font = selfObject.font; - BOOL isBold = [font.fontName containsString:@"bold"]; - if (isBold) { - [selfObject sizeToFit]; - } - } else { - // iOS 13 加粗时有 #1286 描述的问题,不加粗时有 #740 描述的问题,所以干脆只要是 iOS 13 都加粗算了 - [selfObject sizeToFit]; - } - }; - }); - } - // iOS 14.0 如果 pop 到一个 hidesBottomBarWhenPushed = NO 的 vc,tabBar 无法正确显示出来 // 根据测试,iOS 14.2 开始,系统已修复该问题 // https://github.com/Tencent/QMUI_iOS/issues/1100 @@ -355,72 +177,69 @@ + (void)load { // 以下是将 iOS 12 修改 UITabBar 样式的接口转换成用 iOS 13 的新接口去设置(因为新旧方法是互斥的,所以统一在新系统都用新方法) // 但这样有个风险,因为 QMUIConfiguration 配置表里都是用 appearance 的方式去设置 standardAppearance,所以如果在 UITabBar 实例被添加到 window 之前修改过旧版任意一个样式接口,就会导致一个新的 UITabBarAppearance 对象被设置给 standardAppearance 属性,这样系统就会认为你这个 UITabBar 实例自定义了 standardAppearance,那么当它被 moveToWindow 时就不会自动应用 appearance 的值了,因此需要保证在添加到 window 前不要自行修改属性 - if (@available(iOS 13.0, *)) { + void (^syncAppearance)(UITabBar *, void(^barActionBlock)(UITabBarAppearance *appearance), void (^itemActionBlock)(UITabBarItemAppearance *itemAppearance)) = ^void(UITabBar *tabBar, void(^barActionBlock)(UITabBarAppearance *appearance), void (^itemActionBlock)(UITabBarItemAppearance *itemAppearance)) { + if (!barActionBlock && !itemActionBlock) return; - void (^syncAppearance)(UITabBar *, void(^barActionBlock)(UITabBarAppearance *appearance), void (^itemActionBlock)(UITabBarItemAppearance *itemAppearance)) = ^void(UITabBar *tabBar, void(^barActionBlock)(UITabBarAppearance *appearance), void (^itemActionBlock)(UITabBarItemAppearance *itemAppearance)) { - if (!barActionBlock && !itemActionBlock) return; - - UITabBarAppearance *appearance = tabBar.standardAppearance; - if (barActionBlock) { - barActionBlock(appearance); - } - if (itemActionBlock) { - [appearance qmui_applyItemAppearanceWithBlock:itemActionBlock]; - } - tabBar.standardAppearance = appearance; + UITabBarAppearance *appearance = tabBar.standardAppearance; + if (barActionBlock) { + barActionBlock(appearance); + } + if (itemActionBlock) { + [appearance qmui_applyItemAppearanceWithBlock:itemActionBlock]; + } + tabBar.standardAppearance = appearance; #ifdef IOS15_SDK_ALLOWED - if (@available(iOS 15.0, *)) { - if (QMUICMIActivated && TabBarUsesStandardAppearanceOnly) { - tabBar.scrollEdgeAppearance = appearance; - } + if (@available(iOS 15.0, *)) { + if (QMUICMIActivated && TabBarUsesStandardAppearanceOnly) { + tabBar.scrollEdgeAppearance = appearance; } + } #endif - }; - - ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setTintColor:), UIColor *, ^(UITabBar *selfObject, UIColor *tintColor) { - syncAppearance(selfObject, nil, ^void(UITabBarItemAppearance *itemAppearance) { - itemAppearance.selected.iconColor = tintColor; - - NSMutableDictionary *textAttributes = itemAppearance.selected.titleTextAttributes.mutableCopy; - textAttributes[NSForegroundColorAttributeName] = tintColor; - itemAppearance.selected.titleTextAttributes = textAttributes.copy; - }); - }); - - ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setBarTintColor:), UIColor *, ^(UITabBar *selfObject, UIColor *barTintColor) { - syncAppearance(selfObject, ^void(UITabBarAppearance *appearance) { - appearance.backgroundColor = barTintColor; - }, nil); - }); - - ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setUnselectedItemTintColor:), UIColor *, ^(UITabBar *selfObject, UIColor *tintColor) { - syncAppearance(selfObject, nil, ^void(UITabBarItemAppearance *itemAppearance) { - itemAppearance.normal.iconColor = tintColor; - - NSMutableDictionary *textAttributes = itemAppearance.normal.titleTextAttributes.mutableCopy; - textAttributes[NSForegroundColorAttributeName] = tintColor; - itemAppearance.normal.titleTextAttributes = textAttributes.copy; - }); - }); - - ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setBackgroundImage:), UIImage *, ^(UITabBar *selfObject, UIImage *image) { - syncAppearance(selfObject, ^void(UITabBarAppearance *appearance) { - appearance.backgroundImage = image; - }, nil); - }); - - ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setShadowImage:), UIImage *, ^(UITabBar *selfObject, UIImage *shadowImage) { - syncAppearance(selfObject, ^void(UITabBarAppearance *appearance) { - appearance.shadowImage = shadowImage; - }, nil); + }; + + ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setTintColor:), UIColor *, ^(UITabBar *selfObject, UIColor *tintColor) { + syncAppearance(selfObject, nil, ^void(UITabBarItemAppearance *itemAppearance) { + itemAppearance.selected.iconColor = tintColor; + + NSMutableDictionary *textAttributes = itemAppearance.selected.titleTextAttributes.mutableCopy; + textAttributes[NSForegroundColorAttributeName] = tintColor; + itemAppearance.selected.titleTextAttributes = textAttributes.copy; }); - - ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setBarStyle:), UIBarStyle, ^(UITabBar *selfObject, UIBarStyle barStyle) { - syncAppearance(selfObject, ^void(UITabBarAppearance *appearance) { - appearance.backgroundEffect = [UIBlurEffect effectWithStyle:barStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark]; - }, nil); + }); + + ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setBarTintColor:), UIColor *, ^(UITabBar *selfObject, UIColor *barTintColor) { + syncAppearance(selfObject, ^void(UITabBarAppearance *appearance) { + appearance.backgroundColor = barTintColor; + }, nil); + }); + + ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setUnselectedItemTintColor:), UIColor *, ^(UITabBar *selfObject, UIColor *tintColor) { + syncAppearance(selfObject, nil, ^void(UITabBarItemAppearance *itemAppearance) { + itemAppearance.normal.iconColor = tintColor; + + NSMutableDictionary *textAttributes = itemAppearance.normal.titleTextAttributes.mutableCopy; + textAttributes[NSForegroundColorAttributeName] = tintColor; + itemAppearance.normal.titleTextAttributes = textAttributes.copy; }); - } + }); + + ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setBackgroundImage:), UIImage *, ^(UITabBar *selfObject, UIImage *image) { + syncAppearance(selfObject, ^void(UITabBarAppearance *appearance) { + appearance.backgroundImage = image; + }, nil); + }); + + ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setShadowImage:), UIImage *, ^(UITabBar *selfObject, UIImage *shadowImage) { + syncAppearance(selfObject, ^void(UITabBarAppearance *appearance) { + appearance.shadowImage = shadowImage; + }, nil); + }); + + ExtendImplementationOfVoidMethodWithSingleArgument([UITabBar class], @selector(setBarStyle:), UIBarStyle, ^(UITabBar *selfObject, UIBarStyle barStyle) { + syncAppearance(selfObject, ^void(UITabBarAppearance *appearance) { + appearance.backgroundEffect = [UIBlurEffect effectWithStyle:barStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark]; + }, nil); + }); }); } @@ -472,11 +291,9 @@ - (void)revertTabBarItemTouch { @implementation UITabBarAppearance (QMUI) - (void)qmui_applyItemAppearanceWithBlock:(void (^)(UITabBarItemAppearance * _Nonnull))block { - if (@available(iOS 13.0, *)) { - block(self.stackedLayoutAppearance); - block(self.inlineLayoutAppearance); - block(self.compactInlineLayoutAppearance); - } + block(self.stackedLayoutAppearance); + block(self.inlineLayoutAppearance); + block(self.compactInlineLayoutAppearance); } @end diff --git a/QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m b/QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m index 6f368734..c3cd55dd 100644 --- a/QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m @@ -30,10 +30,7 @@ + (UIImageView *)qmui_imageViewInTabBarButton:(UIView *)tabBarButton { if (!tabBarButton) { return nil; } - if (@available(iOS 13.0, *)) { - return [tabBarButton qmui_valueForKey:@"_imageView"]; - } - return [tabBarButton qmui_valueForKey:@"_info"]; + return [tabBarButton qmui_valueForKey:@"_imageView"]; } @end diff --git a/QMUIKit/UIKitExtensions/UITableView+QMUI.h b/QMUIKit/UIKitExtensions/UITableView+QMUI.h index 0a585ab2..7c83ce4f 100644 --- a/QMUIKit/UIKitExtensions/UITableView+QMUI.h +++ b/QMUIKit/UIKitExtensions/UITableView+QMUI.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN -#define PreferredValueForTableViewStyle(_style, _plain, _grouped, _insetGrouped) (_style == UITableViewStyleGrouped ? _grouped : (_style == QMUITableViewStyleInsetGrouped ? _insetGrouped : _plain)) +#define PreferredValueForTableViewStyle(_style, _plain, _grouped, _insetGrouped) (_style == UITableViewStyleGrouped ? _grouped : (_style == UITableViewStyleInsetGrouped ? _insetGrouped : _plain)) /// cell 在当前 section 里的位置,注意判断时要用 (var & xxx) == xxx 的方式 typedef NS_OPTIONS(NSInteger, QMUITableViewCellPosition) { @@ -126,35 +126,21 @@ typedef NS_OPTIONS(NSInteger, QMUITableViewCellPosition) { @end - -extern const UITableViewStyle QMUITableViewStyleInsetGrouped; - /** 系统在 iOS 13 新增了 UITableViewStyleInsetGrouped 类型用于展示往内缩进、cell 带圆角的列表,而这个 Category 让 iOS 12 及以下的系统也能支持这种样式,iOS 13 也可以通过这个 Category 修改左右的缩进值和 cell 的圆角。 使用方式: - 对于 UITableView,通过 -[UITableView initWithStyle:QMUITableViewStyleInsetGrouped] 初始化 tableView。 - 对于 UITableViewController,通过 -[UITableViewController initWithStyle:QMUITableViewStyleInsetGrouped] 初始化 tableViewController。 + 对于 UITableView,通过 -[UITableView initWithStyle:UITableViewStyleInsetGrouped] 初始化 tableView。 + 对于 UITableViewController,通过 -[UITableViewController initWithStyle:UITableViewStyleInsetGrouped] 初始化 tableViewController。 可通过 @c qmui_insetGroupedCornerRadius @c qmui_insetGroupedHorizontalInset 统一修改圆角值和左右缩进,如果要为不同 indexPath 指定不同圆角值,可在 -[UITableViewDelegate tableView:willDisplayCell:forRowAtIndexPath:] 内修改 cell.layer.cornerRadius 的值。 @note 对于 sectionHeader/footer,建议使用 QMUITableViewHeaderFooterView,或者继承系统的 UITableViewHeaderFooterView 并重写它的 sizeThatFits:、layoutSubviews 去计算高度和布局,sizeThatFits: 的参数 size.width 即为减去左右缩进后的宽度。如果直接用系统的 UITableViewHeaderFooterView,iOS 10 及以下多行文本时布局会错误,暂时无法解决,但如果业务项目本身不需要支持 iOS 10 及以下系统,那可忽略这个限制。 */ @interface UITableView (QMUI_InsetGrouped) -/** - 对于代码的使用场景,通过这个属性可以获取当前 UITableView 的 style(如果当前 tableView 没有使用 InsetGrouped 则可以忽略这个属性的存在)。 - 对于 Interface Builder 的使用场景,如果你的 App 最低版本从 iOS 13 开始,则直接用系统自带的 style 选项框去修改 style 即可,但如果你的 App - 最低版本包含 iOS 12 及以下,则需要在 Interface Builder 里把 qmui_style 修改为“2”来使用 QMUITableViewStyleInsetGrouped(选中 TableView 节点后在“User Defined Runtime Attributes”里添加名为“qmui_style”,类型为“Number”,值为“2”的条目)。 - */ -#if TARGET_INTERFACE_BUILDER -@property(nonatomic, assign, readwrite) IBInspectable UITableViewStyle qmui_style; -#else -@property(nonatomic, assign, readonly) UITableViewStyle qmui_style; -#endif - -/// 当使用 QMUITableViewStyleInsetGrouped 时可通过这个属性修改 cell 的圆角值,默认值为 10,也即 iOS 13 系统默认表现。如果要为不同 indexPath 指定不同圆角值,可在 -[UITableViewDelegate tableView:willDisplayCell:forRowAtIndexPath:] 内修改 cell.layer.cornerRadius 的值。 +/// 当使用 UITableViewStyleInsetGrouped 时可通过这个属性修改 cell 的圆角值,默认值为 10,也即 iOS 13 系统默认表现。如果要为不同 indexPath 指定不同圆角值,可在 -[UITableViewDelegate tableView:willDisplayCell:forRowAtIndexPath:] 内修改 cell.layer.cornerRadius 的值。 @property(nonatomic, assign) CGFloat qmui_insetGroupedCornerRadius UI_APPEARANCE_SELECTOR; -/// 当使用 QMUITableViewStyleInsetGrouped 时可通过这个属性修改列表的左右缩进值,默认值为 20,也即 iOS 13 系统默认表现。 +/// 当使用 UITableViewStyleInsetGrouped 时可通过这个属性修改列表的左右缩进值,默认值为 20,也即 iOS 13 系统默认表现。 @property(nonatomic, assign) CGFloat qmui_insetGroupedHorizontalInset UI_APPEARANCE_SELECTOR; @end diff --git a/QMUIKit/UIKitExtensions/UITableView+QMUI.m b/QMUIKit/UIKitExtensions/UITableView+QMUI.m index ac21d6e8..e25fffd6 100644 --- a/QMUIKit/UIKitExtensions/UITableView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITableView+QMUI.m @@ -25,7 +25,6 @@ const NSUInteger kFloatValuePrecision = 4;// 统一一个小数点运算精度 @interface UITableView () -@property(nonatomic, assign, readwrite) UITableViewStyle qmui_style; @property(nonatomic, assign, readonly) CGRect qmui_indexFrame; @end @@ -38,15 +37,6 @@ + (void)load { OverrideImplementation([UITableView class], @selector(initWithFrame:style:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^UITableView *(UITableView *selfObject, CGRect firstArgv, UITableViewStyle secondArgv) { - if (@available(iOS 13.0, *)) { - // iOS 13 qmui_style 的 getter 直接返回 tableView.style,所以这里不需要给 qmui_style 赋值 - } else { - selfObject.qmui_style = secondArgv; - if (secondArgv == QMUITableViewStyleInsetGrouped) { - secondArgv = UITableViewStyleGrouped; - } - } - // call super UITableView *(*originSelectorIMP)(id, SEL, CGRect, UITableViewStyle); originSelectorIMP = (UITableView * (*)(id, SEL, CGRect, UITableViewStyle))originalIMPProvider(); @@ -229,12 +219,12 @@ - (void)qmui_styledAsQMUITableView { [self _qmui_configEstimatedRowHeight]; - self.backgroundColor = PreferredValueForTableViewStyle(self.qmui_style, TableViewBackgroundColor, TableViewGroupedBackgroundColor, TableViewInsetGroupedBackgroundColor); - self.separatorColor = PreferredValueForTableViewStyle(self.qmui_style, TableViewSeparatorColor, TableViewGroupedSeparatorColor, TableViewInsetGroupedSeparatorColor); + self.backgroundColor = PreferredValueForTableViewStyle(self.style, TableViewBackgroundColor, TableViewGroupedBackgroundColor, TableViewInsetGroupedBackgroundColor); + self.separatorColor = PreferredValueForTableViewStyle(self.style, TableViewSeparatorColor, TableViewGroupedSeparatorColor, TableViewInsetGroupedSeparatorColor); // 去掉空白的cell - if (self.qmui_style == UITableViewStylePlain) { + if (self.style == UITableViewStylePlain) { self.tableFooterView = [[UIView alloc] init]; } @@ -245,7 +235,7 @@ - (void)qmui_styledAsQMUITableView { self.sectionIndexBackgroundColor = TableSectionIndexBackgroundColor; #ifdef IOS15_SDK_ALLOWED if (@available(iOS 15.0, *)) { - self.sectionHeaderTopPadding = PreferredValueForTableViewStyle(self.qmui_style, TableViewSectionHeaderTopPadding, TableViewGroupedSectionHeaderTopPadding, TableViewInsetGroupedSectionHeaderTopPadding); + self.sectionHeaderTopPadding = PreferredValueForTableViewStyle(self.style, TableViewSectionHeaderTopPadding, TableViewGroupedSectionHeaderTopPadding, TableViewInsetGroupedSectionHeaderTopPadding); } #endif @@ -350,7 +340,7 @@ - (NSInteger)qmui_indexOfPinnedSectionHeader { } - (BOOL)qmui_isHeaderPinnedForSection:(NSInteger)section { - if (self.qmui_style != UITableViewStylePlain) return NO; + if (self.style != UITableViewStylePlain) return NO; if (section >= [self numberOfSections]) return NO; // 系统这两个接口获取到的 rect 是在 contentSize 里的 rect,而不是实际看到的 rect @@ -371,7 +361,7 @@ - (BOOL)qmui_isHeaderVisibleForSection:(NSInteger)section { // 系统这个接口获取到的 rect 是在 contentSize 里的 rect,而不是实际看到的 rect CGRect rectForSection = CGRectZero; - if (self.qmui_style == UITableViewStylePlain) { + if (self.style == UITableViewStylePlain) { rectForSection = [self rectForSection:section]; } else { rectForSection = [self rectForHeaderInSection:section]; @@ -437,8 +427,8 @@ - (void)qmui_scrollToRowFittingOffsetY:(CGFloat)offsetY atIndexPath:(NSIndexPath - (CGFloat)qmui_validContentWidth { CGRect indexFrame = self.qmui_indexFrame; - CGFloat rightInset = MAX(self.safeAreaInsets.right + (self.qmui_style == QMUITableViewStyleInsetGrouped ? self.qmui_insetGroupedHorizontalInset : 0), CGRectGetWidth(indexFrame)); - CGFloat leftInset = self.safeAreaInsets.left + (self.qmui_style == QMUITableViewStyleInsetGrouped ? self.qmui_insetGroupedHorizontalInset : 0); + CGFloat rightInset = MAX(self.safeAreaInsets.right + (self.style == UITableViewStyleInsetGrouped ? self.qmui_insetGroupedHorizontalInset : 0), CGRectGetWidth(indexFrame)); + CGFloat leftInset = self.safeAreaInsets.left + (self.style == UITableViewStyleInsetGrouped ? self.qmui_insetGroupedHorizontalInset : 0); CGFloat width = CGRectGetWidth(self.bounds) - leftInset - rightInset; return width; } @@ -510,8 +500,6 @@ @interface UITableViewCell (QMUI_Private) @property(nonatomic, assign, readwrite) QMUITableViewCellPosition qmui_cellPosition; @end -const UITableViewStyle QMUITableViewStyleInsetGrouped = UITableViewStyleGrouped + 1; - @implementation UITableView (QMUI_InsetGrouped) + (void)load { @@ -531,28 +519,11 @@ + (void)load { QMUITableViewCellPosition position = [selfObject qmui_positionForRowAtIndexPath:indexPath]; cell.qmui_cellPosition = position; - if (selfObject.qmui_style == QMUITableViewStyleInsetGrouped) { + if (selfObject.style == UITableViewStyleInsetGrouped) { CGFloat cornerRadius = selfObject.qmui_insetGroupedCornerRadius; if (position == QMUITableViewCellPositionMiddleInSection || position == QMUITableViewCellPositionNone) { cornerRadius = 0; } - // InsetGrouped 的圆角,iOS 13 系统有另外的私有方法去设置,所以这里在 qmui_cellPosition 的值更新时设置一次,下方在 _setContentClipCorners:updateCorners: 时再覆盖一次。iOS 12 只需要在这里处理一次即可 - if (@available(iOS 13.0, *)) { - } else { - CACornerMask mask = kCALayerMinXMinYCorner|kCALayerMaxXMinYCorner|kCALayerMinXMaxYCorner|kCALayerMaxXMaxYCorner; - switch (position) { - case QMUITableViewCellPositionFirstInSection: - mask = kCALayerMinXMinYCorner|kCALayerMaxXMinYCorner; - break; - case QMUITableViewCellPositionLastInSection: - mask = kCALayerMinXMaxYCorner|kCALayerMaxXMaxYCorner; - break; - default: - break; - } - cell.layer.maskedCorners = mask; - cell.layer.masksToBounds = YES; - } cell.layer.cornerRadius = cornerRadius; } @@ -562,69 +533,51 @@ + (void)load { }; }); - if (@available(iOS 13.0, *)) { - - // -[UITableViewCell _setContentClipCorners:updateCorners:],用来控制系统 InsetGrouped 的圆角(很多情况都会触发系统更新圆角,例如设置 cell.backgroundColor、...,对于 iOS 12 及以下的系统,则靠 -[UITableView _configureCellForDisplay:forIndexPath:] 来处理 - // - (void) _setContentClipCorners:(unsigned long)arg1 updateCorners:(BOOL)arg2; (0x10db0a5b7) - OverrideImplementation([UITableViewCell class], NSSelectorFromString([NSString qmui_stringByConcat:@"_setContentClipCorners", @":", @"updateCorners", @":", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UITableViewCell *selfObject, CACornerMask firstArgv, BOOL secondArgv) { - - // call super - void (*originSelectorIMP)(id, SEL, CACornerMask, BOOL); - originSelectorIMP = (void (*)(id, SEL, CACornerMask, BOOL))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv, secondArgv); - - UITableView *tableView = selfObject.qmui_tableView; - if (tableView && tableView.qmui_style == QMUITableViewStyleInsetGrouped) { - CGFloat cornerRadius = tableView.qmui_insetGroupedCornerRadius; - if (selfObject.qmui_cellPosition == QMUITableViewCellPositionMiddleInSection || selfObject.qmui_cellPosition == QMUITableViewCellPositionNone) { - cornerRadius = 0; - } - selfObject.layer.cornerRadius = cornerRadius; - } - }; - }); - - - // -[UITableView layoutMargins],用来控制系统 InsetGrouped 的左右间距 - OverrideImplementation([UITableView class], @selector(layoutMargins), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^UIEdgeInsets(UITableView *selfObject) { - // call super - UIEdgeInsets (*originSelectorIMP)(id, SEL); - originSelectorIMP = (UIEdgeInsets (*)(id, SEL))originalIMPProvider(); - UIEdgeInsets result = originSelectorIMP(selfObject, originCMD); - - if (selfObject.qmui_style == QMUITableViewStyleInsetGrouped) { - result.left = selfObject.safeAreaInsets.left + selfObject.qmui_insetGroupedHorizontalInset; - result.right = selfObject.safeAreaInsets.right + selfObject.qmui_insetGroupedHorizontalInset; + // -[UITableViewCell _setContentClipCorners:updateCorners:],用来控制系统 InsetGrouped 的圆角(很多情况都会触发系统更新圆角,例如设置 cell.backgroundColor、...,对于 iOS 12 及以下的系统,则靠 -[UITableView _configureCellForDisplay:forIndexPath:] 来处理 + // - (void) _setContentClipCorners:(unsigned long)arg1 updateCorners:(BOOL)arg2; (0x10db0a5b7) + OverrideImplementation([UITableViewCell class], NSSelectorFromString([NSString qmui_stringByConcat:@"_setContentClipCorners", @":", @"updateCorners", @":", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UITableViewCell *selfObject, CACornerMask firstArgv, BOOL secondArgv) { + + // call super + void (*originSelectorIMP)(id, SEL, CACornerMask, BOOL); + originSelectorIMP = (void (*)(id, SEL, CACornerMask, BOOL))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, firstArgv, secondArgv); + + UITableView *tableView = selfObject.qmui_tableView; + if (tableView && tableView.style == UITableViewStyleInsetGrouped) { + CGFloat cornerRadius = tableView.qmui_insetGroupedCornerRadius; + if (selfObject.qmui_cellPosition == QMUITableViewCellPositionMiddleInSection || selfObject.qmui_cellPosition == QMUITableViewCellPositionNone) { + cornerRadius = 0; } - - return result; - }; - }); - } + selfObject.layer.cornerRadius = cornerRadius; + } + }; + }); + + + // -[UITableView layoutMargins],用来控制系统 InsetGrouped 的左右间距 + OverrideImplementation([UITableView class], @selector(layoutMargins), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^UIEdgeInsets(UITableView *selfObject) { + // call super + UIEdgeInsets (*originSelectorIMP)(id, SEL); + originSelectorIMP = (UIEdgeInsets (*)(id, SEL))originalIMPProvider(); + UIEdgeInsets result = originSelectorIMP(selfObject, originCMD); + + if (selfObject.style == UITableViewStyleInsetGrouped) { + result.left = selfObject.safeAreaInsets.left + selfObject.qmui_insetGroupedHorizontalInset; + result.right = selfObject.safeAreaInsets.right + selfObject.qmui_insetGroupedHorizontalInset; + } + + return result; + }; + }); }); } -static char kAssociatedObjectKey_style; -- (void)setQmui_style:(UITableViewStyle)qmui_style { - if (@available(iOS 13.0, *)) { - } else { - objc_setAssociatedObject(self, &kAssociatedObjectKey_style, @(qmui_style), OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } -} - -- (UITableViewStyle)qmui_style { - if (@available(iOS 13.0, *)) { - return self.style; - } - return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_style)) integerValue]; -} - static char kAssociatedObjectKey_insetGroupedCornerRadius; - (void)setQmui_insetGroupedCornerRadius:(CGFloat)qmui_insetGroupedCornerRadius { objc_setAssociatedObject(self, &kAssociatedObjectKey_insetGroupedCornerRadius, @(qmui_insetGroupedCornerRadius), OBJC_ASSOCIATION_RETAIN_NONATOMIC); - if (self.qmui_style == QMUITableViewStyleInsetGrouped && self.indexPathsForVisibleRows.count) { + if (self.style == UITableViewStyleInsetGrouped && self.indexPathsForVisibleRows.count) { [self reloadData]; } } @@ -642,7 +595,7 @@ - (CGFloat)qmui_insetGroupedCornerRadius { static char kAssociatedObjectKey_insetGroupedHorizontalInset; - (void)setQmui_insetGroupedHorizontalInset:(CGFloat)qmui_insetGroupedHorizontalInset { objc_setAssociatedObject(self, &kAssociatedObjectKey_insetGroupedHorizontalInset, @(qmui_insetGroupedHorizontalInset), OBJC_ASSOCIATION_RETAIN_NONATOMIC); - if (self.qmui_style == QMUITableViewStyleInsetGrouped && self.indexPathsForVisibleRows.count) { + if (self.style == UITableViewStyleInsetGrouped && self.indexPathsForVisibleRows.count) { [self reloadData]; } } diff --git a/QMUIKit/UIKitExtensions/UITableViewCell+QMUI.m b/QMUIKit/UIKitExtensions/UITableViewCell+QMUI.m index cf69b239..42f1e3af 100644 --- a/QMUIKit/UIKitExtensions/UITableViewCell+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITableViewCell+QMUI.m @@ -53,9 +53,7 @@ + (void)load { // 系统虽然有私有 API - (UITableViewCellStyle)style; 可以用,但该方法在 init 内得到的永远是 0,只有 init 执行完成后才可以得到正确的值,所以这里只能自己记录 result.qmui_style = firstArgv; - if (@available(iOS 13.0, *)) { - [selfObject qmuiTbc_callAddToTableViewBlockIfCan]; - } + [selfObject qmuiTbc_callAddToTableViewBlockIfCan]; return result; }; @@ -74,18 +72,16 @@ + (void)load { // 修复 iOS 13.0 UIButton 作为 cell.accessoryView 时布局错误的问题 // https://github.com/Tencent/QMUI_iOS/issues/693 - if (@available(iOS 13.0, *)) { - if (@available(iOS 13.1, *)) { - } else { - ExtendImplementationOfVoidMethodWithoutArguments([UITableViewCell class], @selector(layoutSubviews), ^(UITableViewCell *selfObject) { - if ([selfObject.accessoryView isKindOfClass:[UIButton class]]) { - CGFloat defaultRightMargin = 15 + SafeAreaInsetsConstantForDeviceWithNotch.right; - selfObject.accessoryView.qmui_left = selfObject.qmui_width - defaultRightMargin - selfObject.accessoryView.qmui_width; - selfObject.accessoryView.qmui_top = CGRectGetMinYVerticallyCenterInParentRect(selfObject.frame, selfObject.accessoryView.frame);; - selfObject.contentView.qmui_right = selfObject.accessoryView.qmui_left; - } - }); - } + if (@available(iOS 13.1, *)) { + } else { + ExtendImplementationOfVoidMethodWithoutArguments([UITableViewCell class], @selector(layoutSubviews), ^(UITableViewCell *selfObject) { + if ([selfObject.accessoryView isKindOfClass:[UIButton class]]) { + CGFloat defaultRightMargin = 15 + SafeAreaInsetsConstantForDeviceWithNotch.right; + selfObject.accessoryView.qmui_left = selfObject.qmui_width - defaultRightMargin - selfObject.accessoryView.qmui_width; + selfObject.accessoryView.qmui_top = CGRectGetMinYVerticallyCenterInParentRect(selfObject.frame, selfObject.accessoryView.frame);; + selfObject.contentView.qmui_right = selfObject.accessoryView.qmui_left; + } + }); } OverrideImplementation([UITableViewCell class], NSSelectorFromString(@"_setTableView:"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { @@ -247,24 +243,21 @@ - (UIView *)qmui_accessoryView { } // UITableViewCellAccessoryDetailDisclosureButton 在 iOS 13 及以上是分开的两个 accessoryView,以 NSSet 的形式存在这个私有接口里。而 iOS 12 及以下是以一个 UITableViewCellDetailDisclosureView 的 UIControl 存在。 - if (@available(iOS 13.0, *)) { - NSSet *accessoryViews = [self qmui_valueForKey:@"_existingSystemAccessoryViews"]; - if ([accessoryViews isKindOfClass:NSSet.class] && accessoryViews.count) { - UIView *leftView = nil; - for (UIView *accessoryView in accessoryViews) { - if (!leftView) { - leftView = accessoryView; - continue; - } - if (CGRectGetMinX(accessoryView.frame) < CGRectGetMinX(leftView.frame)) { - leftView = accessoryView; - } + NSSet *accessoryViews = [self qmui_valueForKey:@"_existingSystemAccessoryViews"]; + if ([accessoryViews isKindOfClass:NSSet.class] && accessoryViews.count) { + UIView *leftView = nil; + for (UIView *accessoryView in accessoryViews) { + if (!leftView) { + leftView = accessoryView; + continue; + } + if (CGRectGetMinX(accessoryView.frame) < CGRectGetMinX(leftView.frame)) { + leftView = accessoryView; } - return leftView; } - return nil; + return leftView; } - return [self qmui_valueForKey:@"_accessoryView"]; + return nil; } static char kAssociatedObjectKey_configureReorderingStyleBlock; @@ -436,23 +429,23 @@ - (void)qmui_styledAsQMUITableViewCell { } - (UIColor *)qmui_styledTextLabelColor { - return PreferredValueForTableViewStyle(self.qmui_tableView.qmui_style, TableViewCellTitleLabelColor, TableViewGroupedCellTitleLabelColor, TableViewInsetGroupedCellTitleLabelColor); + return PreferredValueForTableViewStyle(self.qmui_tableView.style, TableViewCellTitleLabelColor, TableViewGroupedCellTitleLabelColor, TableViewInsetGroupedCellTitleLabelColor); } - (UIColor *)qmui_styledDetailTextLabelColor { - return PreferredValueForTableViewStyle(self.qmui_tableView.qmui_style, TableViewCellDetailLabelColor, TableViewGroupedCellDetailLabelColor, TableViewInsetGroupedCellDetailLabelColor); + return PreferredValueForTableViewStyle(self.qmui_tableView.style, TableViewCellDetailLabelColor, TableViewGroupedCellDetailLabelColor, TableViewInsetGroupedCellDetailLabelColor); } - (UIColor *)qmui_styledBackgroundColor { - return PreferredValueForTableViewStyle(self.qmui_tableView.qmui_style, TableViewCellBackgroundColor, TableViewGroupedCellBackgroundColor, TableViewInsetGroupedCellBackgroundColor); + return PreferredValueForTableViewStyle(self.qmui_tableView.style, TableViewCellBackgroundColor, TableViewGroupedCellBackgroundColor, TableViewInsetGroupedCellBackgroundColor); } - (UIColor *)qmui_styledSelectedBackgroundColor { - return PreferredValueForTableViewStyle(self.qmui_tableView.qmui_style, TableViewCellSelectedBackgroundColor, TableViewGroupedCellSelectedBackgroundColor, TableViewInsetGroupedCellSelectedBackgroundColor); + return PreferredValueForTableViewStyle(self.qmui_tableView.style, TableViewCellSelectedBackgroundColor, TableViewGroupedCellSelectedBackgroundColor, TableViewInsetGroupedCellSelectedBackgroundColor); } - (UIColor *)qmui_styledWarningBackgroundColor { - return PreferredValueForTableViewStyle(self.qmui_tableView.qmui_style, TableViewCellWarningBackgroundColor, TableViewGroupedCellWarningBackgroundColor, TableViewInsetGroupedCellWarningBackgroundColor); + return PreferredValueForTableViewStyle(self.qmui_tableView.style, TableViewCellWarningBackgroundColor, TableViewGroupedCellWarningBackgroundColor, TableViewInsetGroupedCellWarningBackgroundColor); } @end @@ -470,14 +463,6 @@ + (void)load { return CGRectZero; } - // iOS 13 自己会控制好 InsetGrouped 时不同 cellPosition 的分隔线显隐,iOS 12 及以下要全部手动处理 - if (@available(iOS 13.0, *)) { - } else { - if (selfObject.qmui_tableView && selfObject.qmui_tableView.qmui_style == QMUITableViewStyleInsetGrouped && (selfObject.qmui_cellPosition & QMUITableViewCellPositionLastInSection) == QMUITableViewCellPositionLastInSection) { - return CGRectZero; - } - } - // call super CGRect (*originSelectorIMP)(id, SEL); originSelectorIMP = (CGRect (*)(id, SEL))originalIMPProvider(); @@ -493,15 +478,6 @@ + (void)load { return CGRectZero; } - if (@available(iOS 13.0, *)) { - } else { - // iOS 13 系统在 InsetGrouped 时默认就会隐藏顶部分隔线,所以这里只对 iOS 12 及以下处理 - if (selfObject.qmui_tableView && selfObject.qmui_tableView.qmui_style == QMUITableViewStyleInsetGrouped) { - return CGRectZero; - } - } - - // call super CGRect (*originSelectorIMP)(id, SEL); originSelectorIMP = (CGRect (*)(id, SEL))originalIMPProvider(); @@ -509,60 +485,6 @@ + (void)load { return result; }; }); - - // 下方的功能,iOS 13 都交给系统的 InsetGrouped 处理 - if (@available(iOS 13.0, *)) return; - - OverrideImplementation([UITableViewCell class], @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UITableViewCell *selfObject, CGRect firstArgv) { - - UITableView *tableView = selfObject.qmui_tableView; - if (tableView && tableView.qmui_style == QMUITableViewStyleInsetGrouped) { - // 以下的宽度不基于 firstArgv 来改,而是直接获取 tableView 的内容宽度,是因为 iOS 12 及以下的系统,在 cell 拖拽排序时,frame 会基于上一个 frame 计算,导致宽度不断减小,所以这里每次都用 tableView 的内容宽度来算 - // https://github.com/Tencent/QMUI_iOS/issues/1216 - firstArgv = CGRectMake(tableView.safeAreaInsets.left + tableView.qmui_insetGroupedHorizontalInset, CGRectGetMinY(firstArgv), tableView.qmui_validContentWidth, CGRectGetHeight(firstArgv)); - } - - // call super - void (*originSelectorIMP)(id, SEL, CGRect); - originSelectorIMP = (void (*)(id, SEL, CGRect))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv); - }; - }); - - // 将缩进后的宽度传给 cell 的 sizeThatFits:,注意 sizeThatFits: 只有在 tableView 开启 self-sizing 的情况下才会被调用(也即高度被指定为 UITableViewAutomaticDimension) - // TODO: molice 系统的 UITableViewCell 第一次布局总是得到错误的高度,不知道为什么 - OverrideImplementation([UITableViewCell class], @selector(systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^CGSize(UITableViewCell *selfObject, CGSize targetSize, UILayoutPriority horizontalFittingPriority, UILayoutPriority verticalFittingPriority) { - - UITableView *tableView = selfObject.qmui_tableView; - if (tableView && tableView.qmui_style == QMUITableViewStyleInsetGrouped) { - [QMUIHelper executeBlock:^{ - OverrideImplementation(selfObject.class, @selector(sizeThatFits:), ^id(__unsafe_unretained Class originClass, SEL cellOriginCMD, IMP (^cellOriginalIMPProvider)(void)) { - return ^CGSize(UITableViewCell *cell, CGSize firstArgv) { - - UITableView *tableView = cell.qmui_tableView; - if (tableView && tableView.qmui_style == QMUITableViewStyleInsetGrouped) { - firstArgv.width = firstArgv.width - UIEdgeInsetsGetHorizontalValue(tableView.safeAreaInsets) - tableView.qmui_insetGroupedHorizontalInset * 2; - } - - // call super - CGSize (*originSelectorIMP)(id, SEL, CGSize); - originSelectorIMP = (CGSize (*)(id, SEL, CGSize))cellOriginalIMPProvider(); - CGSize result = originSelectorIMP(cell, cellOriginCMD, firstArgv); - return result; - }; - }); - } oncePerIdentifier:[NSString stringWithFormat:@"InsetGroupedCell %@-%@", NSStringFromClass(selfObject.class), NSStringFromSelector(@selector(sizeThatFits:))]]; - } - - // call super - CGSize (*originSelectorIMP)(id, SEL, CGSize, UILayoutPriority, UILayoutPriority); - originSelectorIMP = (CGSize (*)(id, SEL, CGSize, UILayoutPriority, UILayoutPriority))originalIMPProvider(); - CGSize result = originSelectorIMP(selfObject, originCMD, targetSize, horizontalFittingPriority, verticalFittingPriority); - return result; - }; - }); }); } diff --git a/QMUIKit/UIKitExtensions/UITableViewHeaderFooterView+QMUI.h b/QMUIKit/UIKitExtensions/UITableViewHeaderFooterView+QMUI.h index b81e780c..ad2ff052 100644 --- a/QMUIKit/UIKitExtensions/UITableViewHeaderFooterView+QMUI.h +++ b/QMUIKit/UIKitExtensions/UITableViewHeaderFooterView+QMUI.h @@ -22,8 +22,4 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, weak, readonly) UITableView *qmui_tableView; @end -@interface UITableViewHeaderFooterView (QMUI_InsetGrouped) - -@end - NS_ASSUME_NONNULL_END diff --git a/QMUIKit/UIKitExtensions/UITableViewHeaderFooterView+QMUI.m b/QMUIKit/UIKitExtensions/UITableViewHeaderFooterView+QMUI.m index 1da1553e..3cccd2a1 100644 --- a/QMUIKit/UIKitExtensions/UITableViewHeaderFooterView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITableViewHeaderFooterView+QMUI.m @@ -25,75 +25,3 @@ - (UITableView *)qmui_tableView { } @end - -@implementation UITableViewHeaderFooterView (QMUI_InsetGrouped) - -+ (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - - // 决定 tableView 赋予 header/footer 的高度 - OverrideImplementation([UITableViewHeaderFooterView class], @selector(initWithReuseIdentifier:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^UITableViewHeaderFooterView *(UITableViewHeaderFooterView *selfObject, NSString *firstArgv) { - - // call super - UITableViewHeaderFooterView *(*originSelectorIMP)(id, SEL, NSString *); - originSelectorIMP = (UITableViewHeaderFooterView * (*)(id, SEL, NSString *))originalIMPProvider(); - UITableViewHeaderFooterView *result = originSelectorIMP(selfObject, originCMD, firstArgv); - - // iOS 13 系统的 UITableViewHeaderFooterView sizeThatFits: 接收的宽度是整个 tableView 的宽度,内部再根据 layoutMargins 调整 contentView,而为了保证所有 iOS 版本在重写 UITableViewHeaderFooterView sizeThatFits: 时可以用相同的计算方式,这里为 iOS 13 下的子类也调整了 sizeThatFits: 宽度的值,这样子类重写时直接把参数 size.width 当成缩进后的宽度即可。 - BOOL shouldConsiderSystemClass = YES; - if (@available(iOS 13.0, *)) { - shouldConsiderSystemClass = NO; - } - if (shouldConsiderSystemClass || selfObject.class != UITableViewHeaderFooterView.class) { - [QMUIHelper executeBlock:^{ - OverrideImplementation(selfObject.class, @selector(sizeThatFits:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^CGSize(UITableViewHeaderFooterView *view, CGSize size) { - - BOOL shouldChangeWidth = view.qmui_tableView && view.qmui_tableView.qmui_style == QMUITableViewStyleInsetGrouped; - if (shouldChangeWidth) { - size.width = size.width - UIEdgeInsetsGetHorizontalValue(view.qmui_tableView.safeAreaInsets) - view.qmui_tableView.qmui_insetGroupedHorizontalInset * 2; - } - - // call super - CGSize (*originSelectorIMP)(id, SEL, CGSize); - originSelectorIMP = (CGSize (*)(id, SEL, CGSize))originalIMPProvider(); - CGSize result = originSelectorIMP(view, originCMD, size); - - return result; - }; - }); - } oncePerIdentifier:[NSString stringWithFormat:@"InsetGroupedHeader %@-%@", NSStringFromClass(selfObject.class), NSStringFromSelector(@selector(sizeThatFits:))]]; - } - - return result; - }; - }); - - // iOS 13 都交给系统处理,下面的逻辑不需要 - if (@available(iOS 13.0, *)) return; - - // 系统通过这个方法返回值来决定 contentView 的布局 - OverrideImplementation([UITableViewHeaderFooterView class], NSSelectorFromString(@"_contentRectForWidth:"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^CGRect(UITableViewHeaderFooterView *selfObject, CGFloat firstArgv) { - BOOL shouldChangeWidth = firstArgv > 0 && selfObject.qmui_tableView && selfObject.qmui_tableView.qmui_style == QMUITableViewStyleInsetGrouped; - if (shouldChangeWidth) { - firstArgv -= UIEdgeInsetsGetHorizontalValue(selfObject.qmui_tableView.safeAreaInsets) + selfObject.qmui_tableView.qmui_insetGroupedHorizontalInset * 2; - } - - // call super - CGRect (*originSelectorIMP)(id, SEL, CGFloat); - originSelectorIMP = (CGRect (*)(id, SEL, CGFloat))originalIMPProvider(); - CGRect result = originSelectorIMP(selfObject, originCMD, firstArgv); - - if (shouldChangeWidth) { - result = CGRectSetX(result, selfObject.qmui_tableView.safeAreaInsets.left + selfObject.qmui_tableView.qmui_insetGroupedHorizontalInset); - } - return result; - }; - }); - }); -} - -@end diff --git a/QMUIKit/UIKitExtensions/UITextField+QMUI.m b/QMUIKit/UIKitExtensions/UITextField+QMUI.m index e3e1fa25..8e99c188 100644 --- a/QMUIKit/UIKitExtensions/UITextField+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITextField+QMUI.m @@ -20,34 +20,6 @@ @implementation UITextField (QMUI) -+ (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - - // iOS 12 及以下版本需要重写该方法才能替换 - if (@available(iOS 13.0, *)) { - } else { - OverrideImplementation([UITextField class], NSSelectorFromString(@"_clearButtonImageForState:"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^UIImage *(UITextField *selfObject, UIControlState firstArgv) { - - if (selfObject.qmui_clearButtonImage) { - if (firstArgv & UIControlStateHighlighted) { - return [selfObject.qmui_clearButtonImage qmui_imageWithAlpha:UIControlHighlightedAlpha]; - } - return selfObject.qmui_clearButtonImage; - } - - // call super - UIImage *(*originSelectorIMP)(id, SEL, UIControlState); - originSelectorIMP = (UIImage *(*)(id, SEL, UIControlState))originalIMPProvider(); - UIImage *result = originSelectorIMP(selfObject, originCMD, firstArgv); - return result; - }; - }); - } - }); -} - - (void)setQmui_selectedRange:(NSRange)qmui_selectedRange { self.selectedTextRange = [self qmui_convertUITextRangeFromNSRange:qmui_selectedRange]; } diff --git a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m index df222ab1..3a779c06 100644 --- a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m @@ -22,20 +22,18 @@ @implementation UITraitCollection (QMUI) static NSString * const kQMUIUserInterfaceStyleWillChangeSelectorsKey = @"qmui_userInterfaceStyleWillChangeObserver"; + (void)qmui_addUserInterfaceStyleWillChangeObserver:(id)observer selector:(SEL)aSelector { - if (@available(iOS 13.0, *)) { - @synchronized (self) { - [UITraitCollection _qmui_overrideTraitCollectionMethodIfNeeded]; - if (!_eventObservers) { - _eventObservers = [NSHashTable weakObjectsHashTable]; - } - NSMutableSet *selectors = [observer qmui_getBoundObjectForKey:kQMUIUserInterfaceStyleWillChangeSelectorsKey]; - if (!selectors) { - selectors = [NSMutableSet set]; - [observer qmui_bindObject:selectors forKey:kQMUIUserInterfaceStyleWillChangeSelectorsKey]; - } - [selectors addObject:NSStringFromSelector(aSelector)]; - [_eventObservers addObject:observer]; + @synchronized (self) { + [UITraitCollection _qmui_overrideTraitCollectionMethodIfNeeded]; + if (!_eventObservers) { + _eventObservers = [NSHashTable weakObjectsHashTable]; + } + NSMutableSet *selectors = [observer qmui_getBoundObjectForKey:kQMUIUserInterfaceStyleWillChangeSelectorsKey]; + if (!selectors) { + selectors = [NSMutableSet set]; + [observer qmui_bindObject:selectors forKey:kQMUIUserInterfaceStyleWillChangeSelectorsKey]; } + [selectors addObject:NSStringFromSelector(aSelector)]; + [_eventObservers addObject:observer]; } } @@ -62,62 +60,60 @@ + (void)_qmui_notifyUserInterfaceStyleWillChangeEvents:(UITraitCollection *)trai } + (void)_qmui_overrideTraitCollectionMethodIfNeeded { - if (@available(iOS 13.0, *)) { - [QMUIHelper executeBlock:^{ - static UIUserInterfaceStyle qmui_lastNotifiedUserInterfaceStyle; - qmui_lastNotifiedUserInterfaceStyle = [UITraitCollection currentTraitCollection].userInterfaceStyle; - - // - (void) _willTransitionToTraitCollection:(id)arg1 withTransitionCoordinator:(id)arg2; (0x7fff24711d49) - OverrideImplementation([UIWindow class], NSSelectorFromString([NSString qmui_stringByConcat:@"_", @"willTransitionToTraitCollection:", @"withTransitionCoordinator:", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIWindow *selfObject, UITraitCollection *traitCollection, id coordinator) { - - // call super - void (*originSelectorIMP)(id, SEL, UITraitCollection *, id ); - originSelectorIMP = (void (*)(id, SEL, UITraitCollection *, id ))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, traitCollection, coordinator); + [QMUIHelper executeBlock:^{ + static UIUserInterfaceStyle qmui_lastNotifiedUserInterfaceStyle; + qmui_lastNotifiedUserInterfaceStyle = [UITraitCollection currentTraitCollection].userInterfaceStyle; + + // - (void) _willTransitionToTraitCollection:(id)arg1 withTransitionCoordinator:(id)arg2; (0x7fff24711d49) + OverrideImplementation([UIWindow class], NSSelectorFromString([NSString qmui_stringByConcat:@"_", @"willTransitionToTraitCollection:", @"withTransitionCoordinator:", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIWindow *selfObject, UITraitCollection *traitCollection, id coordinator) { + + // call super + void (*originSelectorIMP)(id, SEL, UITraitCollection *, id ); + originSelectorIMP = (void (*)(id, SEL, UITraitCollection *, id ))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, traitCollection, coordinator); + + BOOL snapshotFinishedOnBackground = traitCollection.userInterfaceLevel == UIUserInterfaceLevelElevated && UIApplication.sharedApplication.applicationState == UIApplicationStateBackground; + // 进入后台且完成截图了就不继续去响应 style 变化(实测 iOS 13.0 iPad 进入后台并完成截图后,仍会多次改变 style,但是系统并没有调用界面的相关刷新方法) + if (selfObject.windowScene && !snapshotFinishedOnBackground) { + UIWindow *firstValidatedWindow = nil; - BOOL snapshotFinishedOnBackground = traitCollection.userInterfaceLevel == UIUserInterfaceLevelElevated && UIApplication.sharedApplication.applicationState == UIApplicationStateBackground; - // 进入后台且完成截图了就不继续去响应 style 变化(实测 iOS 13.0 iPad 进入后台并完成截图后,仍会多次改变 style,但是系统并没有调用界面的相关刷新方法) - if (selfObject.windowScene && !snapshotFinishedOnBackground) { - UIWindow *firstValidatedWindow = nil; - - if ([NSStringFromClass(selfObject.class) containsString:@"_UIWindowSceneUserInterfaceStyle"]) { // _UIWindowSceneUserInterfaceStyleAnimationSnapshotWindow - firstValidatedWindow = selfObject; - } else { - // 系统会按照这个数组的顺序去更新 window 的 traitCollection,找出最先响应样式更新的 window - NSPointerArray *windows = [[selfObject windowScene] valueForKeyPath:@"_contextBinder._attachedBindables"]; - for (NSUInteger i = 0, count = windows.count; i < count; i++) { - UIWindow *window = [windows pointerAtIndex:i]; - // 例如用 UIWindow 方式显示的弹窗,在消失后,在 windows 数组里会残留一个 nil 的位置,这里过滤掉,否则会导致 App 从桌面唤醒时无法立即显示正确的 style - if (!window) { - continue;; - } - - // 由于 Keyboard 可以通过 keyboardAppearance 来控制 userInterfaceStyle 的 Dark/Light,不一定和系统一样,这里要过滤掉 - if ([window isKindOfClass:NSClassFromString(@"UIRemoteKeyboardWindow")] || [window isKindOfClass:NSClassFromString(@"UITextEffectsWindow")]) { - continue; - } - if (window.overrideUserInterfaceStyle != UIUserInterfaceStyleUnspecified) { - // 这里需要获取到和系统样式同步的 UserInterfaceStyle(所以指定 overrideUserInterfaceStyle 需要跳过) - // 所以当全部 window.overrideUserInterfaceStyle 都指定为非 UIUserInterfaceStyleUnspecified 时将无法获得当前系统的外观 - continue; - } - firstValidatedWindow = window; - break; + if ([NSStringFromClass(selfObject.class) containsString:@"_UIWindowSceneUserInterfaceStyle"]) { // _UIWindowSceneUserInterfaceStyleAnimationSnapshotWindow + firstValidatedWindow = selfObject; + } else { + // 系统会按照这个数组的顺序去更新 window 的 traitCollection,找出最先响应样式更新的 window + NSPointerArray *windows = [[selfObject windowScene] valueForKeyPath:@"_contextBinder._attachedBindables"]; + for (NSUInteger i = 0, count = windows.count; i < count; i++) { + UIWindow *window = [windows pointerAtIndex:i]; + // 例如用 UIWindow 方式显示的弹窗,在消失后,在 windows 数组里会残留一个 nil 的位置,这里过滤掉,否则会导致 App 从桌面唤醒时无法立即显示正确的 style + if (!window) { + continue;; } - } - - if (selfObject == firstValidatedWindow) { - if (qmui_lastNotifiedUserInterfaceStyle != traitCollection.userInterfaceStyle) { - qmui_lastNotifiedUserInterfaceStyle = traitCollection.userInterfaceStyle; - [self _qmui_notifyUserInterfaceStyleWillChangeEvents:traitCollection]; + + // 由于 Keyboard 可以通过 keyboardAppearance 来控制 userInterfaceStyle 的 Dark/Light,不一定和系统一样,这里要过滤掉 + if ([window isKindOfClass:NSClassFromString(@"UIRemoteKeyboardWindow")] || [window isKindOfClass:NSClassFromString(@"UITextEffectsWindow")]) { + continue; } + if (window.overrideUserInterfaceStyle != UIUserInterfaceStyleUnspecified) { + // 这里需要获取到和系统样式同步的 UserInterfaceStyle(所以指定 overrideUserInterfaceStyle 需要跳过) + // 所以当全部 window.overrideUserInterfaceStyle 都指定为非 UIUserInterfaceStyleUnspecified 时将无法获得当前系统的外观 + continue; + } + firstValidatedWindow = window; + break; } } - }; - }); - } oncePerIdentifier:@"UITraitCollection addUserInterfaceStyleWillChangeObserver"]; - } + + if (selfObject == firstValidatedWindow) { + if (qmui_lastNotifiedUserInterfaceStyle != traitCollection.userInterfaceStyle) { + qmui_lastNotifiedUserInterfaceStyle = traitCollection.userInterfaceStyle; + [self _qmui_notifyUserInterfaceStyleWillChangeEvents:traitCollection]; + } + } + } + }; + }); + } oncePerIdentifier:@"UITraitCollection addUserInterfaceStyleWillChangeObserver"]; } @end diff --git a/QMUIKit/UIKitExtensions/UIView+QMUI.m b/QMUIKit/UIKitExtensions/UIView+QMUI.m index b64ee59e..f8f7c0bd 100644 --- a/QMUIKit/UIKitExtensions/UIView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIView+QMUI.m @@ -290,11 +290,7 @@ - (BOOL)qmui_visible { return YES; } if ([self isKindOfClass:UIWindow.class]) { - if (@available(iOS 13.0, *)) { - return !!((UIWindow *)self).windowScene; - } else { - return YES; - } + return !!((UIWindow *)self).windowScene; } UIViewController *viewController = self.qmui_viewController; return viewController.qmui_visibleState >= QMUIViewControllerWillAppear && viewController.qmui_visibleState < QMUIViewControllerWillDisappear; diff --git a/QMUIKit/UIKitExtensions/UIWindow+QMUI.m b/QMUIKit/UIKitExtensions/UIWindow+QMUI.m index 099b6bc4..8c910015 100644 --- a/QMUIKit/UIKitExtensions/UIWindow+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIWindow+QMUI.m @@ -24,18 +24,19 @@ + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + // -[UIWindow initWithFrame:] ExtendImplementationOfNonVoidMethodWithSingleArgument([UIWindow class], @selector(initWithFrame:), CGRect, UIWindow *, ^UIWindow *(UIWindow *selfObject, CGRect frame, UIWindow *originReturnValue) { selfObject.qmui_capturesStatusBarAppearance = YES; return originReturnValue; }); + + // -[UIWindow initWithWindowScene:] + ExtendImplementationOfNonVoidMethodWithSingleArgument([UIWindow class], @selector(initWithWindowScene:), UIWindowScene *, UIWindow *, ^UIWindow *(UIWindow *selfObject, UIWindowScene *windowScene, UIWindow *originReturnValue) { + selfObject.qmui_capturesStatusBarAppearance = YES; + return originReturnValue; + }); - if (@available(iOS 13.0, *)) { - ExtendImplementationOfNonVoidMethodWithSingleArgument([UIWindow class], @selector(initWithWindowScene:), UIWindowScene *, UIWindow *, ^UIWindow *(UIWindow *selfObject, UIWindowScene *windowScene, UIWindow *originReturnValue) { - selfObject.qmui_capturesStatusBarAppearance = YES; - return originReturnValue; - }); - } - + // -[UIWindow _canAffectStatusBarAppearance] OverrideImplementation([UIWindow class], NSSelectorFromString([NSString stringWithFormat:@"_%@%@%@", @"canAffect", @"StatusBar", @"Appearance"]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^BOOL(UIWindow *selfObject) { diff --git a/QMUIKitTests/UIKitExtensions/NSObjectTests.m b/QMUIKitTests/UIKitExtensions/NSObjectTests.m index 72596bf6..0451c9eb 100644 --- a/QMUIKitTests/UIKitExtensions/NSObjectTests.m +++ b/QMUIKitTests/UIKitExtensions/NSObjectTests.m @@ -25,20 +25,12 @@ - (void)testValueForKey { UINavigationBar *navigationBar = [UINavigationBar new]; [navigationBar sizeToFit]; XCTAssertTrue(navigationBar.qmui_backgroundView); - if (@available(iOS 13.0, *)) { - XCTAssertFalse(navigationBar.qmui_shadowImageView); - } else { - XCTAssertTrue(navigationBar.qmui_shadowImageView); - } + XCTAssertFalse(navigationBar.qmui_shadowImageView); UITabBar *tabBar = [UITabBar new]; [tabBar sizeToFit]; XCTAssertTrue(tabBar.qmui_backgroundView); - if (@available(iOS 13.0, *)) { - XCTAssertFalse(tabBar.qmui_shadowImageView); - } else { - XCTAssertTrue(tabBar.qmui_shadowImageView); - } + XCTAssertFalse(tabBar.qmui_shadowImageView); UISearchBar *searchBar = [UISearchBar new]; searchBar.scopeButtonTitles = @[@"A", @"B"]; diff --git a/qmui.xcodeproj/project.pbxproj b/qmui.xcodeproj/project.pbxproj index 7a5dd815..112ee5f0 100644 --- a/qmui.xcodeproj/project.pbxproj +++ b/qmui.xcodeproj/project.pbxproj @@ -637,7 +637,7 @@ CDEA6D071F4B07E700F627AF /* UIGestureRecognizer+QMUI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIGestureRecognizer+QMUI.m"; sourceTree = ""; }; CDF2D69A207F7E3F009E04DD /* NSPointerArray+QMUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSPointerArray+QMUI.h"; sourceTree = ""; }; CDF2D69B207F7E3F009E04DD /* NSPointerArray+QMUI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSPointerArray+QMUI.m"; sourceTree = ""; }; - CDFE9574293FB1DE007AE1AA /* QMUIKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = file; path = QMUIKit.podspec; sourceTree = SOURCE_ROOT; }; + CDFE9574293FB1DE007AE1AA /* QMUIKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = QMUIKit.podspec; sourceTree = SOURCE_ROOT; }; CDFF5FB52369926300B63B92 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/Photos.framework; sourceTree = DEVELOPER_DIR; }; D00881752677B5870061CABF /* UIButtonTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIButtonTests.m; sourceTree = ""; }; D00B651F242A67D7002C27AB /* QMUIAppearance.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QMUIAppearance.h; sourceTree = ""; }; @@ -1827,7 +1827,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; @@ -1875,7 +1875,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; @@ -1899,7 +1899,7 @@ "$(inherited)", ); INFOPLIST_FILE = QMUIKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1928,7 +1928,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = QMUIKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1966,14 +1966,14 @@ GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; INFOPLIST_FILE = QMUIKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MACH_O_TYPE = mh_dylib; - MARKETING_VERSION = 4.6.0; + MARKETING_VERSION = 4.6.1; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.qmui.QMUIKit; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2010,14 +2010,14 @@ GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; INFOPLIST_FILE = QMUIKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MACH_O_TYPE = mh_dylib; - MARKETING_VERSION = 4.6.0; + MARKETING_VERSION = 4.6.1; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.qmui.QMUIKit; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/qmui.xcodeproj/project.xcworkspace/xcuserdata/molice.xcuserdatad/UserInterfaceState.xcuserstate b/qmui.xcodeproj/project.xcworkspace/xcuserdata/molice.xcuserdatad/UserInterfaceState.xcuserstate index 20bdbf3d..b98ecfc4 100644 Binary files a/qmui.xcodeproj/project.xcworkspace/xcuserdata/molice.xcuserdatad/UserInterfaceState.xcuserstate and b/qmui.xcodeproj/project.xcworkspace/xcuserdata/molice.xcuserdatad/UserInterfaceState.xcuserstate differ