QMUIConfiguration.m 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221
  1. /**
  2. * Tencent is pleased to support the open source community by making QMUI_iOS available.
  3. * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
  4. * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
  5. * http://opensource.org/licenses/MIT
  6. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
  7. */
  8. //
  9. // QMUIConfiguration.m
  10. // qmui
  11. //
  12. // Created by QMUI Team on 15/3/29.
  13. //
  14. #import "QMUIConfiguration.h"
  15. #import "QMUICore.h"
  16. #import "UIImage+QMUI.h"
  17. #import "NSString+QMUI.h"
  18. #import "UIViewController+QMUI.h"
  19. #import "QMUIKit.h"// 为了引入其中定义的 QMUI_VERSION
  20. // 在 iOS 8 - 11 上实际测量得到
  21. // Measured on iOS 8 - 11
  22. const CGSize kUINavigationBarBackIndicatorImageSize = {13, 21};
  23. @interface QMUIConfiguration ()
  24. @property(nonatomic, strong) UINavigationBarAppearance *navigationBarAppearance API_AVAILABLE(ios(15.0));
  25. @property(nonatomic, strong) UIToolbarAppearance *toolBarAppearance API_AVAILABLE(ios(15.0));
  26. @property(nonatomic, strong) UITabBarAppearance *tabBarAppearance API_AVAILABLE(ios(13.0));
  27. @end
  28. @implementation UIViewController (QMUIConfiguration)
  29. - (NSArray <UIViewController *>*)qmui_existingViewControllersOfClasses:(NSArray<Class<UIAppearanceContainer>> *)classes {
  30. NSMutableSet *viewControllers = [NSMutableSet set];
  31. if (self.presentedViewController) {
  32. [viewControllers addObjectsFromArray:[self.presentedViewController qmui_existingViewControllersOfClasses:classes]];
  33. }
  34. if ([self isKindOfClass:UINavigationController.class]) {
  35. [viewControllers addObjectsFromArray:[((UINavigationController *)self).visibleViewController qmui_existingViewControllersOfClasses:classes]];
  36. } else if ([self isKindOfClass:UITabBarController.class]) {
  37. [viewControllers addObjectsFromArray:[((UITabBarController *)self).selectedViewController qmui_existingViewControllersOfClasses:classes]];
  38. } else {
  39. // 如果不是常见的 container viewController,则直接获取所有 childViewController
  40. for (UIViewController *child in self.childViewControllers) {
  41. [viewControllers addObjectsFromArray:[child qmui_existingViewControllersOfClasses:classes]];
  42. }
  43. }
  44. for (Class class in classes) {
  45. if ([self isKindOfClass:class]) {
  46. [viewControllers addObject:self];
  47. break;
  48. }
  49. }
  50. return viewControllers.allObjects;
  51. }
  52. @end
  53. @implementation QMUIConfiguration
  54. + (instancetype)sharedInstance {
  55. static dispatch_once_t pred;
  56. static QMUIConfiguration *sharedInstance;
  57. dispatch_once(&pred, ^{
  58. sharedInstance = [[QMUIConfiguration alloc] init];
  59. });
  60. return sharedInstance;
  61. }
  62. - (instancetype)init {
  63. self = [super init];
  64. if (self) {
  65. [self initDefaultConfiguration];
  66. }
  67. return self;
  68. }
  69. static BOOL QMUI_hasAppliedInitialTemplate;
  70. - (void)applyInitialTemplate {
  71. // XCTest 无法加载配置表,因此没有寻找 classes 的必要
  72. // https://github.com/Tencent/QMUI_iOS/issues/1312
  73. if (QMUI_hasAppliedInitialTemplate || IS_XCTEST) {
  74. return;
  75. }
  76. // 自动寻找并应用模板
  77. // Automatically look for templates and apply them
  78. // @see https://github.com/Tencent/QMUI_iOS/issues/264
  79. Protocol *protocol = @protocol(QMUIConfigurationTemplateProtocol);
  80. classref_t *classesref = nil;
  81. Class *classes = nil;
  82. int numberOfClasses = qmui_getProjectClassList(&classesref);
  83. if (numberOfClasses <= 0) {
  84. numberOfClasses = objc_getClassList(NULL, 0);
  85. classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numberOfClasses);
  86. objc_getClassList(classes, numberOfClasses);
  87. NSAssert(NO, @"如果你看到这条提示,建议到 GitHub 上提 issue,让我们联系你查看项目的配置表使用情况,否则请注释掉这一行。");
  88. }
  89. for (NSInteger i = 0; i < numberOfClasses; i++) {
  90. Class class = classesref ? (__bridge Class)classesref[i] : classes[i];
  91. // 这里用 containsString 是考虑到 Swift 里 className 由“项目前缀+class 名”组成,如果用 hasPrefix 就无法判断了
  92. // Use `containsString` instead of `hasPrefix` because class names in Swift have project prefix prepended
  93. if ([NSStringFromClass(class) containsString:@"QMUIConfigurationTemplate"] && [class conformsToProtocol:protocol]) {
  94. if ([class instancesRespondToSelector:@selector(shouldApplyTemplateAutomatically)]) {
  95. id<QMUIConfigurationTemplateProtocol> template = [[class alloc] init];
  96. if ([template shouldApplyTemplateAutomatically]) {
  97. QMUI_hasAppliedInitialTemplate = YES;
  98. _active = YES;// 标志配置表已生效
  99. [template applyConfigurationTemplate];
  100. // 只应用第一个 shouldApplyTemplateAutomatically 的主题
  101. // Only apply the first template returned
  102. break;
  103. }
  104. }
  105. }
  106. }
  107. if (IS_DEBUG && self.sendAnalyticsToQMUITeam) {
  108. [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:[NSOperationQueue new] usingBlock:^(NSNotification * _Nonnull note) {
  109. // 这里根据是否能成功获取到 classesref 来统计信息,以供后续确认对 classesref 为 nil 的保护是否真的必要
  110. [self sendAnalyticsWithQuery:classes ? @"findByObjc=true" : nil];
  111. }];
  112. }
  113. if (classes) free(classes);
  114. QMUI_hasAppliedInitialTemplate = YES;
  115. }
  116. - (void)sendAnalyticsWithQuery:(NSString *)query {
  117. NSString *identifier = [NSBundle mainBundle].bundleIdentifier.qmui_stringByEncodingUserInputQuery;
  118. NSString *displayName = ((NSString *)([NSBundle mainBundle].infoDictionary[@"CFBundleDisplayName"] ?: [NSBundle mainBundle].infoDictionary[@"CFBundleName"])).qmui_stringByEncodingUserInputQuery;
  119. NSString *QMUIVersion = QMUI_VERSION.qmui_stringByEncodingUserInputQuery;// 如果不以 framework 方式引入 QMUI 的话,是无法通过 CFBundleShortVersionString 获取到 QMUI 所在的 bundle 的版本号的,所以这里改为用脚本生成的变量来获取
  120. NSString *queryString = [NSString stringWithFormat:@"appId=%@&appName=%@&version=%@&platform=iOS", identifier, displayName, QMUIVersion];
  121. if (query.length > 0) queryString = [NSString stringWithFormat:@"%@&%@", queryString, query];
  122. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://qmuiteam.com/analytics/usageReport"]];
  123. request.HTTPMethod = @"POST";
  124. request.HTTPBody = [queryString dataUsingEncoding:NSUTF8StringEncoding];
  125. NSURLSession *session = [NSURLSession sharedSession];
  126. [[session dataTaskWithRequest:request] resume];
  127. }
  128. #pragma mark - Initialize default values
  129. - (void)initDefaultConfiguration {
  130. #pragma mark - Global Color
  131. self.clearColor = UIColorMakeWithRGBA(255, 255, 255, 0);
  132. self.whiteColor = UIColorMake(255, 255, 255);
  133. self.blackColor = UIColorMake(0, 0, 0);
  134. self.grayColor = UIColorMake(179, 179, 179);
  135. self.grayDarkenColor = UIColorMake(163, 163, 163);
  136. self.grayLightenColor = UIColorMake(198, 198, 198);
  137. self.redColor = UIColorMake(250, 58, 58);
  138. self.greenColor = UIColorMake(159, 214, 97);
  139. self.blueColor = UIColorMake(49, 189, 243);
  140. self.yellowColor = UIColorMake(255, 207, 71);
  141. self.linkColor = UIColorMake(56, 116, 171);
  142. self.disabledColor = self.grayColor;
  143. self.maskDarkColor = UIColorMakeWithRGBA(0, 0, 0, .35f);
  144. self.maskLightColor = UIColorMakeWithRGBA(255, 255, 255, .5f);
  145. self.separatorColor = UIColorMake(222, 224, 226);
  146. self.separatorDashedColor = UIColorMake(17, 17, 17);
  147. self.placeholderColor = UIColorMake(196, 200, 208);
  148. self.testColorRed = UIColorMakeWithRGBA(255, 0, 0, .3);
  149. self.testColorGreen = UIColorMakeWithRGBA(0, 255, 0, .3);
  150. self.testColorBlue = UIColorMakeWithRGBA(0, 0, 255, .3);
  151. #pragma mark - UIControl
  152. self.controlHighlightedAlpha = 0.5f;
  153. self.controlDisabledAlpha = 0.5f;
  154. #pragma mark - UIButton
  155. self.buttonHighlightedAlpha = self.controlHighlightedAlpha;
  156. self.buttonDisabledAlpha = self.controlDisabledAlpha;
  157. self.buttonTintColor = self.blueColor;
  158. #pragma mark - UITextField & UITextView
  159. self.textFieldTextInsets = UIEdgeInsetsMake(0, 7, 0, 7);
  160. #pragma mark - NavigationBar
  161. self.navBarHighlightedAlpha = 0.2f;
  162. self.navBarDisabledAlpha = 0.2f;
  163. self.sizeNavBarBackIndicatorImageAutomatically = YES;
  164. self.navBarCloseButtonImage = [UIImage qmui_imageWithShape:QMUIImageShapeNavClose size:CGSizeMake(16, 16) tintColor:self.navBarTintColor];
  165. self.navBarLoadingMarginRight = 3;
  166. self.navBarAccessoryViewMarginLeft = 5;
  167. self.navBarActivityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
  168. self.navBarAccessoryViewTypeDisclosureIndicatorImage = [[UIImage qmui_imageWithShape:QMUIImageShapeTriangle size:CGSizeMake(8, 5) tintColor:self.navBarTitleColor] qmui_imageWithOrientation:UIImageOrientationDown];
  169. #pragma mark - Toolbar
  170. self.toolBarHighlightedAlpha = 0.4f;
  171. self.toolBarDisabledAlpha = 0.4f;
  172. #pragma mark - SearchBar
  173. self.searchBarPlaceholderColor = self.placeholderColor;
  174. self.searchBarTextFieldCornerRadius = 2.0;
  175. #pragma mark - TableView / TableViewCell
  176. self.tableViewEstimatedHeightEnabled = YES;
  177. self.tableViewSeparatorColor = self.separatorColor;
  178. self.tableViewCellNormalHeight = UITableViewAutomaticDimension;
  179. self.tableViewCellSelectedBackgroundColor = UIColorMake(238, 239, 241);
  180. self.tableViewCellWarningBackgroundColor = self.yellowColor;
  181. self.tableViewCellSpacingBetweenDetailButtonAndDisclosureIndicator = 12;
  182. self.tableViewSectionHeaderBackgroundColor = UIColorMake(244, 244, 244);
  183. self.tableViewSectionFooterBackgroundColor = UIColorMake(244, 244, 244);
  184. self.tableViewSectionHeaderFont = UIFontBoldMake(12);
  185. self.tableViewSectionFooterFont = UIFontBoldMake(12);
  186. self.tableViewSectionHeaderTextColor = self.grayDarkenColor;
  187. self.tableViewSectionFooterTextColor = self.grayColor;
  188. self.tableViewSectionHeaderAccessoryMargins = UIEdgeInsetsMake(0, 15, 0, 0);
  189. self.tableViewSectionFooterAccessoryMargins = UIEdgeInsetsMake(0, 15, 0, 0);
  190. self.tableViewSectionHeaderContentInset = UIEdgeInsetsMake(4, 15, 4, 15);
  191. self.tableViewSectionFooterContentInset = UIEdgeInsetsMake(4, 15, 4, 15);
  192. if (@available(iOS 15.0, *)) {
  193. self.tableViewSectionHeaderTopPadding = UITableViewAutomaticDimension;
  194. }
  195. self.tableViewGroupedSeparatorColor = self.tableViewSeparatorColor;
  196. self.tableViewGroupedSectionHeaderFont = UIFontMake(12);
  197. self.tableViewGroupedSectionFooterFont = UIFontMake(12);
  198. self.tableViewGroupedSectionHeaderTextColor = self.grayDarkenColor;
  199. self.tableViewGroupedSectionFooterTextColor = self.grayColor;
  200. self.tableViewGroupedSectionHeaderAccessoryMargins = UIEdgeInsetsMake(0, 15, 0, 0);
  201. self.tableViewGroupedSectionFooterAccessoryMargins = UIEdgeInsetsMake(0, 15, 0, 0);
  202. self.tableViewGroupedSectionHeaderDefaultHeight = UITableViewAutomaticDimension;
  203. self.tableViewGroupedSectionFooterDefaultHeight = UITableViewAutomaticDimension;
  204. self.tableViewGroupedSectionHeaderContentInset = UIEdgeInsetsMake(16, 15, 8, 15);
  205. self.tableViewGroupedSectionFooterContentInset = UIEdgeInsetsMake(8, 15, 2, 15);
  206. if (@available(iOS 15.0, *)) {
  207. self.tableViewInsetGroupedSectionHeaderTopPadding = UITableViewAutomaticDimension;
  208. }
  209. self.tableViewInsetGroupedCornerRadius = 10;
  210. self.tableViewInsetGroupedHorizontalInset = PreferredValueForVisualDevice(20, 15);
  211. self.tableViewInsetGroupedSeparatorColor = self.tableViewSeparatorColor;
  212. self.tableViewInsetGroupedSectionHeaderFont = self.tableViewGroupedSectionHeaderFont;
  213. self.tableViewInsetGroupedSectionFooterFont = self.tableViewGroupedSectionFooterFont;
  214. self.tableViewInsetGroupedSectionHeaderTextColor = self.tableViewSectionHeaderTextColor;
  215. self.tableViewInsetGroupedSectionFooterTextColor = self.tableViewGroupedSectionFooterTextColor;
  216. self.tableViewInsetGroupedSectionHeaderAccessoryMargins = self.tableViewGroupedSectionHeaderAccessoryMargins;
  217. self.tableViewInsetGroupedSectionFooterAccessoryMargins = self.tableViewGroupedSectionFooterAccessoryMargins;
  218. self.tableViewInsetGroupedSectionHeaderDefaultHeight = self.tableViewGroupedSectionHeaderDefaultHeight;
  219. self.tableViewInsetGroupedSectionFooterDefaultHeight = self.tableViewGroupedSectionFooterDefaultHeight;
  220. self.tableViewInsetGroupedSectionHeaderContentInset = self.tableViewGroupedSectionHeaderContentInset;
  221. self.tableViewInsetGroupedSectionFooterContentInset = self.tableViewGroupedSectionFooterContentInset;
  222. if (@available(iOS 15.0, *)) {
  223. self.tableViewInsetGroupedSectionHeaderTopPadding = UITableViewAutomaticDimension;
  224. }
  225. #pragma mark - UIWindowLevel
  226. self.windowLevelQMUIAlertView = UIWindowLevelAlert - 4.0;
  227. self.windowLevelQMUIConsole = 1;
  228. #pragma mark - QMUILog
  229. self.shouldPrintDefaultLog = YES;
  230. self.shouldPrintInfoLog = YES;
  231. self.shouldPrintWarnLog = YES;
  232. self.shouldPrintQMUIWarnLogToConsole = IS_DEBUG;
  233. #pragma mark - QMUIBadge
  234. self.badgeOffset = QMUIBadgeInvalidateOffset;
  235. self.badgeOffsetLandscape = QMUIBadgeInvalidateOffset;
  236. self.updatesIndicatorOffset = QMUIBadgeInvalidateOffset;
  237. self.updatesIndicatorOffsetLandscape = QMUIBadgeInvalidateOffset;
  238. #pragma mark - Others
  239. self.supportedOrientationMask = UIInterfaceOrientationMaskAll;
  240. self.needsBackBarButtonItemTitle = YES;
  241. self.preventConcurrentNavigationControllerTransitions = YES;
  242. self.shouldFixTabBarSafeAreaInsetsBug = YES;
  243. self.sendAnalyticsToQMUITeam = YES;
  244. }
  245. #pragma mark - Switch Setter
  246. /// 对 UIAppearance 设置一次 image 属性,在升起第三方键盘时就会执行一次 -[UIImage initWithCoder:],不管每次设置的是否是相同的对象,因此这里做一次值是否有变化的判断,尽量减少 UIAppearance 的设置。
  247. /// 注意,由于 QMUIConfiguration 里的 property setter 不仅是 retain 值,还起到刷新界面的作用,因此只有 QMUIThemeImage、QMUIThemeColor 等会“在 theme 变化时自动刷新”的对象才能用这个方法,其他类型的数据请自行检查 setter 里的逻辑是否需要在每次都调用。
  248. /// https://github.com/Tencent/QMUI_iOS/issues/1281
  249. + (void)performAction:(void (NS_NOESCAPE ^)(void))action ifValueChanged:(id)oldValue newValue:(id)newValue {
  250. if (!action) return;
  251. BOOL valueChanged = newValue != oldValue;
  252. if ([newValue isKindOfClass:NSValue.class]
  253. || [newValue isKindOfClass:UIFont.class]
  254. || ([newValue isKindOfClass:UIColor.class] && !((UIColor *)newValue).qmui_isQMUIDynamicColor)) {
  255. valueChanged = ![newValue isEqual:oldValue];
  256. }
  257. if (valueChanged) {
  258. action();
  259. }
  260. }
  261. - (void)setSwitchOnTintColor:(UIColor *)switchOnTintColor {
  262. [QMUIConfiguration performAction:^{
  263. _switchOnTintColor = switchOnTintColor;
  264. if (QMUIHelper.canUpdateAppearance) {
  265. [UISwitch appearance].onTintColor = switchOnTintColor;
  266. }
  267. } ifValueChanged:_switchOnTintColor newValue:switchOnTintColor];
  268. }
  269. - (void)setSwitchThumbTintColor:(UIColor *)switchThumbTintColor {
  270. [QMUIConfiguration performAction:^{
  271. _switchThumbTintColor = switchThumbTintColor;
  272. if (QMUIHelper.canUpdateAppearance) {
  273. [UISwitch appearance].thumbTintColor = switchThumbTintColor;
  274. }
  275. } ifValueChanged:_switchThumbTintColor newValue:switchThumbTintColor];
  276. }
  277. #pragma mark - NavigationBar Setter
  278. - (UINavigationBarAppearance *)navigationBarAppearance {
  279. if (!_navigationBarAppearance) {
  280. _navigationBarAppearance = [[UINavigationBarAppearance alloc] init];
  281. [_navigationBarAppearance configureWithDefaultBackground];
  282. }
  283. return _navigationBarAppearance;
  284. }
  285. - (void)updateNavigationBarBarAppearance {
  286. #ifdef IOS15_SDK_ALLOWED
  287. if (@available(iOS 15.0, *)) {
  288. if (QMUIHelper.canUpdateAppearance) {
  289. UINavigationBar.qmui_appearanceConfigured.standardAppearance = self.navigationBarAppearance;
  290. if (QMUICMIActivated && NavBarUsesStandardAppearanceOnly) {
  291. UINavigationBar.qmui_appearanceConfigured.scrollEdgeAppearance = self.navigationBarAppearance;
  292. }
  293. }
  294. }
  295. #endif
  296. }
  297. - (void)setNavBarButtonFont:(UIFont *)navBarButtonFont {
  298. [QMUIConfiguration performAction:^{
  299. _navBarButtonFont = navBarButtonFont;
  300. #ifdef IOS15_SDK_ALLOWED
  301. if (@available(iOS 15.0, *)) {
  302. NSMutableDictionary<NSAttributedStringKey, id> *titleTextAttributes = self.navigationBarAppearance.buttonAppearance.normal.titleTextAttributes.mutableCopy;
  303. titleTextAttributes[NSFontAttributeName] = navBarButtonFont;
  304. self.navigationBarAppearance.buttonAppearance.normal.titleTextAttributes = titleTextAttributes;
  305. [self updateNavigationBarBarAppearance];
  306. } else {
  307. #endif
  308. // by molice 2017-08-04 只要用 appearence 的方式修改 UIBarButtonItem 的 font,就会导致界面切换时 UIBarButtonItem 抖动,系统的问题,所以暂时不修改 appearance。
  309. // by molice 2018-06-14 iOS 11 观察貌似又没抖动了,先试试看
  310. if (QMUIHelper.canUpdateAppearance) {
  311. UIBarButtonItem *barButtonItemAppearance = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class]]];
  312. NSDictionary<NSAttributedStringKey,id> *attributes = navBarButtonFont ? @{NSFontAttributeName: navBarButtonFont} : nil;
  313. [barButtonItemAppearance setTitleTextAttributes:attributes forState:UIControlStateNormal];
  314. [barButtonItemAppearance setTitleTextAttributes:attributes forState:UIControlStateHighlighted];
  315. [barButtonItemAppearance setTitleTextAttributes:attributes forState:UIControlStateDisabled];
  316. }
  317. #ifdef IOS15_SDK_ALLOWED
  318. }
  319. #endif
  320. } ifValueChanged:_navBarButtonFont newValue:navBarButtonFont];
  321. }
  322. - (void)setNavBarButtonFontBold:(UIFont *)navBarButtonFontBold {
  323. // iOS 15 以前无法专门对 Done 类型设置样式,所以这里只对 iOS 15 生效
  324. if (@available(iOS 15.0, *)) {
  325. [QMUIConfiguration performAction:^{
  326. _navBarButtonFontBold = navBarButtonFontBold;
  327. #ifdef IOS15_SDK_ALLOWED
  328. NSMutableDictionary<NSAttributedStringKey, id> *titleTextAttributes = self.navigationBarAppearance.doneButtonAppearance.normal.titleTextAttributes.mutableCopy;
  329. titleTextAttributes[NSFontAttributeName] = navBarButtonFontBold;
  330. self.navigationBarAppearance.doneButtonAppearance.normal.titleTextAttributes = titleTextAttributes;
  331. [self updateNavigationBarBarAppearance];
  332. #endif
  333. } ifValueChanged:_navBarButtonFontBold newValue:navBarButtonFontBold];
  334. }
  335. }
  336. - (void)setNavBarTintColor:(UIColor *)navBarTintColor {
  337. _navBarTintColor = navBarTintColor;
  338. // tintColor 并没有声明 UI_APPEARANCE_SELECTOR,所以暂不使用 appearance 的方式去修改(虽然 appearance 方式实测是生效的)
  339. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController,NSUInteger idx, BOOL * _Nonnull stop) {
  340. if (![navigationController.topViewController respondsToSelector:@selector(qmui_navigationBarTintColor)]) {
  341. navigationController.navigationBar.tintColor = _navBarTintColor;
  342. }
  343. }];
  344. }
  345. - (void)setNavBarBarTintColor:(UIColor *)navBarBarTintColor {
  346. [QMUIConfiguration performAction:^{
  347. _navBarBarTintColor = navBarBarTintColor;
  348. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  349. if (QMUIHelper.canUpdateAppearance) {
  350. UINavigationBar.qmui_appearanceConfigured.barTintColor = navBarBarTintColor;
  351. }
  352. #ifdef IOS15_SDK_ALLOWED
  353. if (@available(iOS 15.0, *)) {
  354. self.navigationBarAppearance.backgroundColor = navBarBarTintColor;
  355. [self updateNavigationBarBarAppearance];
  356. }
  357. #endif
  358. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController,NSUInteger idx, BOOL * _Nonnull stop) {
  359. if (![navigationController.topViewController respondsToSelector:@selector(qmui_navigationBarBarTintColor)]) {
  360. navigationController.navigationBar.barTintColor = navBarBarTintColor;
  361. }
  362. }];
  363. } ifValueChanged:_navBarBarTintColor newValue:navBarBarTintColor];
  364. }
  365. - (void)setNavBarShadowImage:(UIImage *)navBarShadowImage {
  366. [QMUIConfiguration performAction:^{
  367. _navBarShadowImage = navBarShadowImage;
  368. #ifdef IOS15_SDK_ALLOWED
  369. if (@available(iOS 15.0, *)) {
  370. self.navigationBarAppearance.shadowImage = navBarShadowImage;
  371. [self updateNavigationBarBarAppearance];
  372. }
  373. #endif
  374. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  375. [self configureNavBarShadowImage];
  376. } ifValueChanged:_navBarShadowImage newValue:!navBarShadowImage ? _navBarShadowImage : navBarShadowImage];// NavBarShadowImage 特殊一点,因为它在 NavBarShadowImageColor 里又会被赋值,所以这里对常见的组合“image = nil && imageColor = xxx”做特殊处理,避免误以为 valueChanged
  377. }
  378. - (void)setNavBarShadowImageColor:(UIColor *)navBarShadowImageColor {
  379. [QMUIConfiguration performAction:^{
  380. _navBarShadowImageColor = navBarShadowImageColor;
  381. #ifdef IOS15_SDK_ALLOWED
  382. if (@available(iOS 15.0, *)) {
  383. self.navigationBarAppearance.shadowColor = navBarShadowImageColor;
  384. [self updateNavigationBarBarAppearance];
  385. }
  386. #endif
  387. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  388. [self configureNavBarShadowImage];
  389. } ifValueChanged:_navBarShadowImageColor newValue:navBarShadowImageColor];
  390. }
  391. - (void)configureNavBarShadowImage {
  392. UIImage *shadowImage = self.navBarShadowImage;
  393. if (shadowImage || self.navBarShadowImageColor) {
  394. if (shadowImage) {
  395. if (self.navBarShadowImageColor && shadowImage.renderingMode != UIImageRenderingModeAlwaysOriginal) {
  396. shadowImage = [shadowImage qmui_imageWithTintColor:self.navBarShadowImageColor];
  397. }
  398. } else {
  399. shadowImage = [UIImage qmui_imageWithColor:self.navBarShadowImageColor size:CGSizeMake(4, PixelOne) cornerRadius:0];
  400. }
  401. // 反向更新 NavBarShadowImage,以保证业务代码直接使用 NavBarShadowImage 宏能得到正确的图片
  402. _navBarShadowImage = shadowImage;
  403. }
  404. if (QMUIHelper.canUpdateAppearance) {
  405. UINavigationBar.qmui_appearanceConfigured.shadowImage = shadowImage;
  406. }
  407. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController,NSUInteger idx, BOOL * _Nonnull stop) {
  408. if (![navigationController.topViewController respondsToSelector:@selector(qmui_navigationBarShadowImage)]) {
  409. navigationController.navigationBar.shadowImage = shadowImage;
  410. }
  411. }];
  412. }
  413. - (void)setNavBarStyle:(UIBarStyle)navBarStyle {
  414. [QMUIConfiguration performAction:^{
  415. _navBarStyle = navBarStyle;
  416. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  417. if (QMUIHelper.canUpdateAppearance) {
  418. UINavigationBar.qmui_appearanceConfigured.barStyle = navBarStyle;
  419. }
  420. #ifdef IOS15_SDK_ALLOWED
  421. if (@available(iOS 15.0, *)) {
  422. self.navigationBarAppearance.backgroundEffect = [UIBlurEffect effectWithStyle:navBarStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark];
  423. [self updateNavigationBarBarAppearance];
  424. }
  425. #endif
  426. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController,NSUInteger idx, BOOL * _Nonnull stop) {
  427. if (![navigationController.topViewController respondsToSelector:@selector(qmui_navigationBarStyle)]) {
  428. navigationController.navigationBar.barStyle = navBarStyle;
  429. }
  430. }];
  431. } ifValueChanged:@(_navBarStyle) newValue:@(navBarStyle)];
  432. }
  433. - (void)setNavBarBackgroundImage:(UIImage *)navBarBackgroundImage {
  434. [QMUIConfiguration performAction:^{
  435. _navBarBackgroundImage = navBarBackgroundImage;
  436. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  437. if (QMUIHelper.canUpdateAppearance) {
  438. [UINavigationBar.qmui_appearanceConfigured setBackgroundImage:navBarBackgroundImage forBarMetrics:UIBarMetricsDefault];
  439. }
  440. #ifdef IOS15_SDK_ALLOWED
  441. if (@available(iOS 15.0, *)) {
  442. self.navigationBarAppearance.backgroundImage = navBarBackgroundImage;
  443. [self updateNavigationBarBarAppearance];
  444. }
  445. #endif
  446. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController,NSUInteger idx, BOOL * _Nonnull stop) {
  447. if (![navigationController.topViewController respondsToSelector:@selector(qmui_navigationBarBackgroundImage)]) {
  448. [navigationController.navigationBar setBackgroundImage:navBarBackgroundImage forBarMetrics:UIBarMetricsDefault];
  449. }
  450. }];
  451. } ifValueChanged:_navBarBackgroundImage newValue:navBarBackgroundImage];
  452. }
  453. - (void)setNavBarTitleFont:(UIFont *)navBarTitleFont {
  454. [QMUIConfiguration performAction:^{
  455. _navBarTitleFont = navBarTitleFont;
  456. #ifdef IOS15_SDK_ALLOWED
  457. if (@available(iOS 15.0, *)) {
  458. NSMutableDictionary<NSAttributedStringKey, id> *titleTextAttributes = self.navigationBarAppearance.titleTextAttributes.mutableCopy;
  459. titleTextAttributes[NSFontAttributeName] = navBarTitleFont;
  460. self.navigationBarAppearance.titleTextAttributes = titleTextAttributes;
  461. [self updateNavigationBarBarAppearance];
  462. }
  463. #endif
  464. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  465. [self updateNavigationBarTitleAttributesIfNeeded];
  466. } ifValueChanged:_navBarTitleFont newValue:navBarTitleFont];
  467. }
  468. - (void)setNavBarTitleColor:(UIColor *)navBarTitleColor {
  469. [QMUIConfiguration performAction:^{
  470. _navBarTitleColor = navBarTitleColor;
  471. #ifdef IOS15_SDK_ALLOWED
  472. if (@available(iOS 15.0, *)) {
  473. NSMutableDictionary<NSAttributedStringKey, id> *titleTextAttributes = self.navigationBarAppearance.titleTextAttributes.mutableCopy;
  474. titleTextAttributes[NSForegroundColorAttributeName] = navBarTitleColor;
  475. self.navigationBarAppearance.titleTextAttributes = titleTextAttributes;
  476. [self updateNavigationBarBarAppearance];
  477. }
  478. #endif
  479. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  480. [self updateNavigationBarTitleAttributesIfNeeded];
  481. } ifValueChanged:_navBarTitleColor newValue:navBarTitleColor];
  482. }
  483. - (void)updateNavigationBarTitleAttributesIfNeeded {
  484. NSMutableDictionary<NSAttributedStringKey, id> *titleTextAttributes = UINavigationBar.qmui_appearanceConfigured.titleTextAttributes.mutableCopy;
  485. if (!titleTextAttributes) {
  486. titleTextAttributes = [[NSMutableDictionary alloc] init];
  487. }
  488. if (self.navBarTitleFont) {
  489. titleTextAttributes[NSFontAttributeName] = self.navBarTitleFont;
  490. }
  491. if (self.navBarTitleColor) {
  492. titleTextAttributes[NSForegroundColorAttributeName] = self.navBarTitleColor;
  493. }
  494. if (QMUIHelper.canUpdateAppearance) {
  495. UINavigationBar.qmui_appearanceConfigured.titleTextAttributes = titleTextAttributes;
  496. }
  497. #ifdef IOS15_SDK_ALLOWED
  498. if (@available(iOS 15.0, *)) {
  499. } else {
  500. #endif
  501. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController,NSUInteger idx, BOOL * _Nonnull stop) {
  502. if (![navigationController.topViewController respondsToSelector:@selector(qmui_titleViewTintColor)]) {
  503. navigationController.navigationBar.titleTextAttributes = titleTextAttributes;
  504. }
  505. }];
  506. #ifdef IOS15_SDK_ALLOWED
  507. }
  508. #endif
  509. }
  510. - (void)setNavBarLargeTitleFont:(UIFont *)navBarLargeTitleFont {
  511. [QMUIConfiguration performAction:^{
  512. _navBarLargeTitleFont = navBarLargeTitleFont;
  513. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  514. [self updateNavigationBarLargeTitleTextAttributesIfNeeded];
  515. #ifdef IOS15_SDK_ALLOWED
  516. if (@available(iOS 15.0, *)) {
  517. NSMutableDictionary<NSAttributedStringKey, id> *largeTitleTextAttributes = self.navigationBarAppearance.largeTitleTextAttributes.mutableCopy;
  518. largeTitleTextAttributes[NSFontAttributeName] = navBarLargeTitleFont;
  519. self.navigationBarAppearance.largeTitleTextAttributes = largeTitleTextAttributes;
  520. [self updateNavigationBarBarAppearance];
  521. }
  522. #endif
  523. } ifValueChanged:_navBarLargeTitleFont newValue:navBarLargeTitleFont];
  524. }
  525. - (void)setNavBarLargeTitleColor:(UIColor *)navBarLargeTitleColor {
  526. [QMUIConfiguration performAction:^{
  527. _navBarLargeTitleColor = navBarLargeTitleColor;
  528. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  529. [self updateNavigationBarLargeTitleTextAttributesIfNeeded];
  530. #ifdef IOS15_SDK_ALLOWED
  531. if (@available(iOS 15.0, *)) {
  532. NSMutableDictionary<NSAttributedStringKey, id> *largeTitleTextAttributes = self.navigationBarAppearance.largeTitleTextAttributes.mutableCopy;
  533. largeTitleTextAttributes[NSForegroundColorAttributeName] = navBarLargeTitleColor;
  534. self.navigationBarAppearance.largeTitleTextAttributes = largeTitleTextAttributes;
  535. [self updateNavigationBarBarAppearance];
  536. }
  537. #endif
  538. } ifValueChanged:_navBarLargeTitleColor newValue:navBarLargeTitleColor];
  539. }
  540. - (void)updateNavigationBarLargeTitleTextAttributesIfNeeded {
  541. NSMutableDictionary<NSString *, id> *largeTitleTextAttributes = [[NSMutableDictionary alloc] init];
  542. if (self.navBarLargeTitleFont) {
  543. largeTitleTextAttributes[NSFontAttributeName] = self.navBarLargeTitleFont;
  544. }
  545. if (self.navBarLargeTitleColor) {
  546. largeTitleTextAttributes[NSForegroundColorAttributeName] = self.navBarLargeTitleColor;
  547. }
  548. if (QMUIHelper.canUpdateAppearance) {
  549. UINavigationBar.qmui_appearanceConfigured.largeTitleTextAttributes = largeTitleTextAttributes;
  550. }
  551. #ifdef IOS15_SDK_ALLOWED
  552. if (@available(iOS 15.0, *)) {
  553. } else {
  554. #endif
  555. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController,NSUInteger idx, BOOL * _Nonnull stop) {
  556. navigationController.navigationBar.largeTitleTextAttributes = largeTitleTextAttributes;
  557. }];
  558. #ifdef IOS15_SDK_ALLOWED
  559. }
  560. #endif
  561. }
  562. - (void)setSizeNavBarBackIndicatorImageAutomatically:(BOOL)sizeNavBarBackIndicatorImageAutomatically {
  563. _sizeNavBarBackIndicatorImageAutomatically = sizeNavBarBackIndicatorImageAutomatically;
  564. if (sizeNavBarBackIndicatorImageAutomatically && self.navBarBackIndicatorImage && !CGSizeEqualToSize(self.navBarBackIndicatorImage.size, kUINavigationBarBackIndicatorImageSize)) {
  565. self.navBarBackIndicatorImage = self.navBarBackIndicatorImage;// 重新设置一次,以触发自动调整大小
  566. }
  567. }
  568. - (void)setNavBarBackIndicatorImage:(UIImage *)navBarBackIndicatorImage {
  569. [QMUIConfiguration performAction:^{
  570. _navBarBackIndicatorImage = navBarBackIndicatorImage;
  571. // 返回按钮的图片frame是和系统默认的返回图片的大小一致的(13, 21),所以用自定义返回箭头时要保证图片大小与系统的箭头大小一样,否则无法对齐
  572. // Make sure custom back button image is the same size as the system's back button image, i.e. (13, 21), due to the same frame size they share.
  573. if (navBarBackIndicatorImage && self.sizeNavBarBackIndicatorImageAutomatically) {
  574. CGSize systemBackIndicatorImageSize = kUINavigationBarBackIndicatorImageSize;
  575. CGSize customBackIndicatorImageSize = _navBarBackIndicatorImage.size;
  576. if (!CGSizeEqualToSize(customBackIndicatorImageSize, systemBackIndicatorImageSize)) {
  577. CGFloat imageExtensionVerticalFloat = CGFloatGetCenter(systemBackIndicatorImageSize.height, customBackIndicatorImageSize.height);
  578. _navBarBackIndicatorImage = [[_navBarBackIndicatorImage qmui_imageWithSpacingExtensionInsets:UIEdgeInsetsMake(imageExtensionVerticalFloat,
  579. 0,
  580. imageExtensionVerticalFloat,
  581. systemBackIndicatorImageSize.width - customBackIndicatorImageSize.width)] imageWithRenderingMode:_navBarBackIndicatorImage.renderingMode];
  582. }
  583. }
  584. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  585. if (QMUIHelper.canUpdateAppearance) {
  586. UINavigationBar *navBarAppearance = UINavigationBar.qmui_appearanceConfigured;
  587. navBarAppearance.backIndicatorImage = _navBarBackIndicatorImage;
  588. navBarAppearance.backIndicatorTransitionMaskImage = _navBarBackIndicatorImage;
  589. }
  590. #ifdef IOS15_SDK_ALLOWED
  591. if (@available(iOS 15.0, *)) {
  592. [self.navigationBarAppearance setBackIndicatorImage:_navBarBackIndicatorImage transitionMaskImage:_navBarBackIndicatorImage];
  593. [self updateNavigationBarBarAppearance];
  594. }
  595. #endif
  596. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController,NSUInteger idx, BOOL * _Nonnull stop) {
  597. navigationController.navigationBar.backIndicatorImage = _navBarBackIndicatorImage;
  598. navigationController.navigationBar.backIndicatorTransitionMaskImage = _navBarBackIndicatorImage;
  599. }];
  600. } ifValueChanged:_navBarBackIndicatorImage newValue:navBarBackIndicatorImage];
  601. }
  602. - (void)setNavBarBackButtonTitlePositionAdjustment:(UIOffset)navBarBackButtonTitlePositionAdjustment {
  603. [QMUIConfiguration performAction:^{
  604. _navBarBackButtonTitlePositionAdjustment = navBarBackButtonTitlePositionAdjustment;
  605. // iOS 15 虽然不通过旧 API 设置样式,但 QMUI 里会从 appearance 的旧 API 取值作为默认值,所以这里不做 if iOS 15 的屏蔽。
  606. if (QMUIHelper.canUpdateAppearance) {
  607. UIBarButtonItem *backBarButtonItem = [UIBarButtonItem appearance];
  608. [backBarButtonItem setBackButtonTitlePositionAdjustment:_navBarBackButtonTitlePositionAdjustment forBarMetrics:UIBarMetricsDefault];
  609. }
  610. #ifdef IOS15_SDK_ALLOWED
  611. if (@available(iOS 15.0, *)) {
  612. self.navigationBarAppearance.backButtonAppearance.normal.titlePositionAdjustment = navBarBackButtonTitlePositionAdjustment;
  613. [self updateNavigationBarBarAppearance];
  614. } else {
  615. #endif
  616. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController, NSUInteger idx, BOOL * _Nonnull stop) {
  617. [navigationController.navigationItem.backBarButtonItem setBackButtonTitlePositionAdjustment:_navBarBackButtonTitlePositionAdjustment forBarMetrics:UIBarMetricsDefault];
  618. }];
  619. #ifdef IOS15_SDK_ALLOWED
  620. }
  621. #endif
  622. } ifValueChanged:[NSValue valueWithUIOffset:_navBarBackButtonTitlePositionAdjustment] newValue:[NSValue valueWithUIOffset:navBarBackButtonTitlePositionAdjustment]];
  623. }
  624. #pragma mark - ToolBar Setter
  625. - (UIToolbarAppearance *)toolBarAppearance {
  626. if (!_toolBarAppearance) {
  627. _toolBarAppearance = [[UIToolbarAppearance alloc] init];
  628. [_toolBarAppearance configureWithDefaultBackground];
  629. }
  630. return _toolBarAppearance;
  631. }
  632. - (void)updateToolBarBarAppearance {
  633. #ifdef IOS15_SDK_ALLOWED
  634. if (@available(iOS 15.0, *)) {
  635. if (QMUIHelper.canUpdateAppearance) {
  636. UIToolbar.qmui_appearanceConfigured.standardAppearance = self.toolBarAppearance;
  637. if (QMUICMIActivated && ToolBarUsesStandardAppearanceOnly) {
  638. UIToolbar.qmui_appearanceConfigured.scrollEdgeAppearance = self.toolBarAppearance;
  639. }
  640. }
  641. [self.appearanceUpdatingToolbarControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController, NSUInteger idx, BOOL * _Nonnull stop) {
  642. navigationController.toolbar.standardAppearance = self.toolBarAppearance;
  643. if (QMUICMIActivated && ToolBarUsesStandardAppearanceOnly) {
  644. navigationController.toolbar.scrollEdgeAppearance = self.toolBarAppearance;
  645. }
  646. }];
  647. }
  648. #endif
  649. }
  650. - (void)setToolBarTintColor:(UIColor *)toolBarTintColor {
  651. _toolBarTintColor = toolBarTintColor;
  652. // tintColor 并没有声明 UI_APPEARANCE_SELECTOR,所以暂不使用 appearance 的方式去修改(虽然 appearance 方式实测是生效的)
  653. [self.appearanceUpdatingNavigationControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController, NSUInteger idx, BOOL * _Nonnull stop) {
  654. navigationController.toolbar.tintColor = _toolBarTintColor;
  655. }];
  656. }
  657. - (void)setToolBarStyle:(UIBarStyle)toolBarStyle {
  658. [QMUIConfiguration performAction:^{
  659. _toolBarStyle = toolBarStyle;
  660. if (QMUIHelper.canUpdateAppearance) {
  661. UIToolbar.qmui_appearanceConfigured.barStyle = toolBarStyle;
  662. }
  663. #ifdef IOS15_SDK_ALLOWED
  664. if (@available(iOS 15.0, *)) {
  665. self.toolBarAppearance.backgroundEffect = [UIBlurEffect effectWithStyle:toolBarStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark];
  666. [self updateToolBarBarAppearance];
  667. } else {
  668. #endif
  669. [self.appearanceUpdatingToolbarControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController, NSUInteger idx, BOOL * _Nonnull stop) {
  670. navigationController.toolbar.barStyle = toolBarStyle;
  671. }];
  672. #ifdef IOS15_SDK_ALLOWED
  673. }
  674. #endif
  675. } ifValueChanged:@(_toolBarStyle) newValue:@(toolBarStyle)];
  676. }
  677. - (void)setToolBarBarTintColor:(UIColor *)toolBarBarTintColor {
  678. [QMUIConfiguration performAction:^{
  679. _toolBarBarTintColor = toolBarBarTintColor;
  680. if (QMUIHelper.canUpdateAppearance) {
  681. UIToolbar.qmui_appearanceConfigured.barTintColor = _toolBarBarTintColor;
  682. }
  683. #ifdef IOS15_SDK_ALLOWED
  684. if (@available(iOS 15.0, *)) {
  685. self.toolBarAppearance.backgroundColor = toolBarBarTintColor;
  686. [self updateToolBarBarAppearance];
  687. } else {
  688. #endif
  689. [self.appearanceUpdatingToolbarControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController, NSUInteger idx, BOOL * _Nonnull stop) {
  690. navigationController.toolbar.barTintColor = _toolBarBarTintColor;
  691. }];
  692. #ifdef IOS15_SDK_ALLOWED
  693. }
  694. #endif
  695. } ifValueChanged:_toolBarBarTintColor newValue:toolBarBarTintColor];
  696. }
  697. - (void)setToolBarBackgroundImage:(UIImage *)toolBarBackgroundImage {
  698. [QMUIConfiguration performAction:^{
  699. _toolBarBackgroundImage = toolBarBackgroundImage;
  700. if (QMUIHelper.canUpdateAppearance) {
  701. [UIToolbar.qmui_appearanceConfigured setBackgroundImage:_toolBarBackgroundImage forToolbarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault];
  702. }
  703. #ifdef IOS15_SDK_ALLOWED
  704. if (@available(iOS 15.0, *)) {
  705. self.toolBarAppearance.backgroundImage = toolBarBackgroundImage;
  706. [self updateToolBarBarAppearance];
  707. } else {
  708. #endif
  709. [self.appearanceUpdatingToolbarControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController, NSUInteger idx, BOOL * _Nonnull stop) {
  710. [navigationController.toolbar setBackgroundImage:_toolBarBackgroundImage forToolbarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault];
  711. }];
  712. #ifdef IOS15_SDK_ALLOWED
  713. }
  714. #endif
  715. } ifValueChanged:_toolBarBackgroundImage newValue:toolBarBackgroundImage];
  716. }
  717. - (void)setToolBarShadowImageColor:(UIColor *)toolBarShadowImageColor {
  718. [QMUIConfiguration performAction:^{
  719. _toolBarShadowImageColor = toolBarShadowImageColor;
  720. UIImage *shadowImage = toolBarShadowImageColor ? [UIImage qmui_imageWithColor:_toolBarShadowImageColor size:CGSizeMake(1, PixelOne) cornerRadius:0] : nil;
  721. if (QMUIHelper.canUpdateAppearance) {
  722. [UIToolbar.qmui_appearanceConfigured setShadowImage:shadowImage forToolbarPosition:UIBarPositionAny];
  723. }
  724. #ifdef IOS15_SDK_ALLOWED
  725. if (@available(iOS 15.0, *)) {
  726. self.toolBarAppearance.shadowColor = toolBarShadowImageColor;
  727. [self updateToolBarBarAppearance];
  728. } else {
  729. #endif
  730. [self.appearanceUpdatingToolbarControllers enumerateObjectsUsingBlock:^(UINavigationController * _Nonnull navigationController, NSUInteger idx, BOOL * _Nonnull stop) {
  731. [navigationController.toolbar setShadowImage:shadowImage forToolbarPosition:UIBarPositionAny];
  732. }];
  733. #ifdef IOS15_SDK_ALLOWED
  734. }
  735. #endif
  736. } ifValueChanged:_toolBarShadowImageColor newValue:toolBarShadowImageColor];
  737. }
  738. #pragma mark - TabBar Setter
  739. - (UITabBarAppearance *)tabBarAppearance {
  740. if (!_tabBarAppearance) {
  741. _tabBarAppearance = [[UITabBarAppearance alloc] init];
  742. [_tabBarAppearance configureWithDefaultBackground];
  743. }
  744. return _tabBarAppearance;
  745. }
  746. - (void)updateTabBarAppearance {
  747. if (@available(iOS 13.0, *)) {
  748. if (QMUIHelper.canUpdateAppearance) {
  749. UITabBar.qmui_appearanceConfigured.standardAppearance = self.tabBarAppearance;
  750. #ifdef IOS15_SDK_ALLOWED
  751. if (@available(iOS 15.0, *)) {
  752. if (QMUICMIActivated && TabBarUsesStandardAppearanceOnly) {
  753. UITabBar.qmui_appearanceConfigured.scrollEdgeAppearance = self.tabBarAppearance;
  754. }
  755. }
  756. #endif
  757. }
  758. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  759. tabBarController.tabBar.standardAppearance = self.tabBarAppearance;
  760. #ifdef IOS15_SDK_ALLOWED
  761. if (@available(iOS 15.0, *)) {
  762. if (QMUICMIActivated && TabBarUsesStandardAppearanceOnly) {
  763. tabBarController.tabBar.scrollEdgeAppearance = self.tabBarAppearance;
  764. }
  765. }
  766. #endif
  767. [tabBarController.tabBar setNeedsLayout];// theme 不跟随系统的情况下切换 Light/Dark,tabBarAppearance.backgroundEffect 虽然值被更新了,但样式被刷新,这里手动触发一下
  768. }];
  769. }
  770. }
  771. - (void)setTabBarBarTintColor:(UIColor *)tabBarBarTintColor {
  772. [QMUIConfiguration performAction:^{
  773. _tabBarBarTintColor = tabBarBarTintColor;
  774. if (@available(iOS 13.0, *)) {
  775. self.tabBarAppearance.backgroundColor = tabBarBarTintColor;
  776. [self updateTabBarAppearance];
  777. } else {
  778. if (QMUIHelper.canUpdateAppearance) {
  779. UITabBar.qmui_appearanceConfigured.barTintColor = _tabBarBarTintColor;
  780. }
  781. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  782. tabBarController.tabBar.barTintColor = _tabBarBarTintColor;
  783. }];
  784. }
  785. } ifValueChanged:_tabBarBarTintColor newValue:tabBarBarTintColor];
  786. }
  787. - (void)setTabBarStyle:(UIBarStyle)tabBarStyle {
  788. [QMUIConfiguration performAction:^{
  789. _tabBarStyle = tabBarStyle;
  790. if (@available(iOS 13.0, *)) {
  791. self.tabBarAppearance.backgroundEffect = [UIBlurEffect effectWithStyle:tabBarStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark];
  792. [self updateTabBarAppearance];
  793. } else {
  794. if (QMUIHelper.canUpdateAppearance) {
  795. UITabBar.qmui_appearanceConfigured.barStyle = tabBarStyle;
  796. }
  797. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  798. tabBarController.tabBar.barStyle = tabBarStyle;
  799. }];
  800. }
  801. } ifValueChanged:@(_tabBarStyle) newValue:@(tabBarStyle)];
  802. }
  803. - (void)setTabBarBackgroundImage:(UIImage *)tabBarBackgroundImage {
  804. [QMUIConfiguration performAction:^{
  805. _tabBarBackgroundImage = tabBarBackgroundImage;
  806. if (@available(iOS 13.0, *)) {
  807. self.tabBarAppearance.backgroundImage = tabBarBackgroundImage;
  808. [self updateTabBarAppearance];
  809. } else {
  810. if (QMUIHelper.canUpdateAppearance) {
  811. UITabBar.qmui_appearanceConfigured.backgroundImage = tabBarBackgroundImage;
  812. }
  813. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  814. tabBarController.tabBar.backgroundImage = tabBarBackgroundImage;
  815. }];
  816. }
  817. } ifValueChanged:_tabBarBackgroundImage newValue:tabBarBackgroundImage];
  818. }
  819. - (void)setTabBarShadowImageColor:(UIColor *)tabBarShadowImageColor {
  820. [QMUIConfiguration performAction:^{
  821. _tabBarShadowImageColor = tabBarShadowImageColor;
  822. if (@available(iOS 13.0, *)) {
  823. self.tabBarAppearance.shadowColor = tabBarShadowImageColor;
  824. [self updateTabBarAppearance];
  825. } else {
  826. UIImage *shadowImage = [UIImage qmui_imageWithColor:_tabBarShadowImageColor size:CGSizeMake(1, PixelOne) cornerRadius:0];
  827. if (QMUIHelper.canUpdateAppearance) {
  828. [UITabBar.qmui_appearanceConfigured setShadowImage:shadowImage];
  829. }
  830. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  831. tabBarController.tabBar.shadowImage = shadowImage;
  832. }];
  833. }
  834. } ifValueChanged:_tabBarShadowImageColor newValue:tabBarShadowImageColor];
  835. }
  836. - (void)setTabBarItemTitleFont:(UIFont *)tabBarItemTitleFont {
  837. [QMUIConfiguration performAction:^{
  838. _tabBarItemTitleFont = tabBarItemTitleFont;
  839. if (@available(iOS 13.0, *)) {
  840. [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) {
  841. NSMutableDictionary<NSAttributedStringKey, id> *attributes = itemAppearance.normal.titleTextAttributes.mutableCopy;
  842. attributes[NSFontAttributeName] = tabBarItemTitleFont;
  843. itemAppearance.normal.titleTextAttributes = attributes.copy;
  844. }];
  845. [self updateTabBarAppearance];
  846. } else {
  847. NSMutableDictionary<NSString *, id> *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateNormal]];
  848. if (_tabBarItemTitleFont) {
  849. textAttributes[NSFontAttributeName] = _tabBarItemTitleFont;
  850. }
  851. if (QMUIHelper.canUpdateAppearance) {
  852. [UITabBarItem.qmui_appearanceConfigured setTitleTextAttributes:textAttributes forState:UIControlStateNormal];
  853. }
  854. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  855. [tabBarController.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  856. [obj setTitleTextAttributes:textAttributes forState:UIControlStateNormal];
  857. }];
  858. }];
  859. }
  860. } ifValueChanged:_tabBarItemTitleFont newValue:tabBarItemTitleFont];
  861. }
  862. - (void)setTabBarItemTitleFontSelected:(UIFont *)tabBarItemTitleFontSelected {
  863. [QMUIConfiguration performAction:^{
  864. _tabBarItemTitleFontSelected = tabBarItemTitleFontSelected;
  865. if (@available(iOS 13.0, *)) {
  866. [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) {
  867. NSMutableDictionary<NSAttributedStringKey, id> *attributes = itemAppearance.selected.titleTextAttributes.mutableCopy;
  868. attributes[NSFontAttributeName] = tabBarItemTitleFontSelected;
  869. itemAppearance.selected.titleTextAttributes = attributes.copy;
  870. }];
  871. [self updateTabBarAppearance];
  872. } else {
  873. NSMutableDictionary<NSString *, id> *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateSelected]];
  874. if (tabBarItemTitleFontSelected) {
  875. textAttributes[NSFontAttributeName] = tabBarItemTitleFontSelected;
  876. }
  877. if (QMUIHelper.canUpdateAppearance) {
  878. [UITabBarItem.qmui_appearanceConfigured setTitleTextAttributes:textAttributes forState:UIControlStateSelected];
  879. }
  880. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  881. [tabBarController.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  882. [obj setTitleTextAttributes:textAttributes forState:UIControlStateSelected];
  883. }];
  884. }];
  885. }
  886. } ifValueChanged:_tabBarItemTitleFontSelected newValue:tabBarItemTitleFontSelected];
  887. }
  888. - (void)setTabBarItemTitleColor:(UIColor *)tabBarItemTitleColor {
  889. [QMUIConfiguration performAction:^{
  890. _tabBarItemTitleColor = tabBarItemTitleColor;
  891. if (@available(iOS 13.0, *)) {
  892. [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) {
  893. NSMutableDictionary<NSAttributedStringKey, id> *attributes = itemAppearance.normal.titleTextAttributes.mutableCopy;
  894. attributes[NSForegroundColorAttributeName] = tabBarItemTitleColor;
  895. itemAppearance.normal.titleTextAttributes = attributes.copy;
  896. }];
  897. [self updateTabBarAppearance];
  898. } else {
  899. NSMutableDictionary<NSString *, id> *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateNormal]];
  900. textAttributes[NSForegroundColorAttributeName] = _tabBarItemTitleColor;
  901. if (QMUIHelper.canUpdateAppearance) {
  902. [UITabBarItem.qmui_appearanceConfigured setTitleTextAttributes:textAttributes forState:UIControlStateNormal];
  903. }
  904. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  905. [tabBarController.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  906. [obj setTitleTextAttributes:textAttributes forState:UIControlStateNormal];
  907. }];
  908. }];
  909. }
  910. } ifValueChanged:_tabBarItemTitleColor newValue:tabBarItemTitleColor];
  911. }
  912. - (void)setTabBarItemTitleColorSelected:(UIColor *)tabBarItemTitleColorSelected {
  913. [QMUIConfiguration performAction:^{
  914. _tabBarItemTitleColorSelected = tabBarItemTitleColorSelected;
  915. if (@available(iOS 13.0, *)) {
  916. [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) {
  917. NSMutableDictionary<NSAttributedStringKey, id> *attributes = itemAppearance.selected.titleTextAttributes.mutableCopy;
  918. attributes[NSForegroundColorAttributeName] = tabBarItemTitleColorSelected;
  919. itemAppearance.selected.titleTextAttributes = attributes.copy;
  920. }];
  921. [self updateTabBarAppearance];
  922. } else {
  923. NSMutableDictionary<NSString *, id> *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[UITabBarItem.qmui_appearanceConfigured titleTextAttributesForState:UIControlStateSelected]];
  924. textAttributes[NSForegroundColorAttributeName] = _tabBarItemTitleColorSelected;
  925. if (QMUIHelper.canUpdateAppearance) {
  926. [UITabBarItem.qmui_appearanceConfigured setTitleTextAttributes:textAttributes forState:UIControlStateSelected];
  927. }
  928. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  929. [tabBarController.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  930. [obj setTitleTextAttributes:textAttributes forState:UIControlStateSelected];
  931. }];
  932. }];
  933. }
  934. } ifValueChanged:_tabBarItemTitleColorSelected newValue:tabBarItemTitleColorSelected];
  935. }
  936. - (void)setTabBarItemImageColor:(UIColor *)tabBarItemImageColor {
  937. [QMUIConfiguration performAction:^{
  938. _tabBarItemImageColor = tabBarItemImageColor;
  939. if (@available(iOS 13.0, *)) {
  940. [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) {
  941. itemAppearance.normal.iconColor = tabBarItemImageColor;
  942. }];
  943. [self updateTabBarAppearance];
  944. } else {
  945. if (QMUIHelper.canUpdateAppearance) {
  946. UITabBar.qmui_appearanceConfigured.unselectedItemTintColor = tabBarItemImageColor;
  947. }
  948. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  949. tabBarController.tabBar.unselectedItemTintColor = tabBarItemImageColor;
  950. }];
  951. }
  952. } ifValueChanged:_tabBarItemImageColor newValue:tabBarItemImageColor];
  953. }
  954. - (void)setTabBarItemImageColorSelected:(UIColor *)tabBarItemImageColorSelected {
  955. [QMUIConfiguration performAction:^{
  956. _tabBarItemImageColorSelected = tabBarItemImageColorSelected;
  957. if (@available(iOS 13.0, *)) {
  958. [self.tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) {
  959. itemAppearance.selected.iconColor = tabBarItemImageColorSelected;
  960. }];
  961. [self updateTabBarAppearance];
  962. } else {
  963. // iOS 12 及以下使用 tintColor 实现,但 tintColor 并没有声明 UI_APPEARANCE_SELECTOR,所以暂不使用 appearance 的方式去修改(虽然 appearance 方式实测是生效的)
  964. // UITabBar.qmui_appearanceConfigured.tintColor = tabBarItemImageColorSelected;
  965. [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
  966. tabBarController.tabBar.tintColor = tabBarItemImageColorSelected;
  967. }];
  968. }
  969. } ifValueChanged:_tabBarItemImageColorSelected newValue:tabBarItemImageColorSelected];
  970. }
  971. - (void)setDefaultStatusBarStyle:(UIStatusBarStyle)defaultStatusBarStyle {
  972. _defaultStatusBarStyle = defaultStatusBarStyle;
  973. [[QMUIHelper visibleViewController] setNeedsStatusBarAppearanceUpdate];
  974. }
  975. #pragma mark - Appearance Updating Views
  976. // 解决某些场景下更新配置表无法覆盖样式的问题 https://github.com/Tencent/QMUI_iOS/issues/700
  977. - (NSArray <UITabBarController *>*)appearanceUpdatingTabBarControllers {
  978. NSArray<Class<UIAppearanceContainer>> *classes = nil;
  979. if (self.tabBarContainerClasses.count > 0) {
  980. classes = self.tabBarContainerClasses;
  981. } else {
  982. classes = @[UITabBarController.class];
  983. }
  984. // tabBarContainerClasses 里可能会设置非 UITabBarController 的 class,由于这里只需要关注 UITabBarController 的,所以做一次过滤
  985. classes = [classes qmui_filterWithBlock:^BOOL(Class<UIAppearanceContainer> _Nonnull item) {
  986. return [item.class isSubclassOfClass:UITabBarController.class];
  987. }];
  988. return (NSArray <UITabBarController *>*)[self appearanceUpdatingViewControllersOfClasses:classes];
  989. }
  990. - (NSArray <UINavigationController *>*)appearanceUpdatingNavigationControllers {
  991. NSArray<Class<UIAppearanceContainer>> *classes = nil;
  992. if (self.navBarContainerClasses.count > 0) {
  993. classes = self.navBarContainerClasses;
  994. } else {
  995. classes = @[UINavigationController.class];
  996. }
  997. // navBarContainerClasses 里可能会设置非 UINavigationController 的 class,由于这里只需要关注 UINavigationController 的,所以做一次过滤
  998. classes = [classes qmui_filterWithBlock:^BOOL(Class<UIAppearanceContainer> _Nonnull item) {
  999. return [item.class isSubclassOfClass:UINavigationController.class];
  1000. }];
  1001. return (NSArray <UINavigationController *>*)[self appearanceUpdatingViewControllersOfClasses:classes];
  1002. }
  1003. - (NSArray <UINavigationController *>*)appearanceUpdatingToolbarControllers {
  1004. NSArray<Class<UIAppearanceContainer>> *classes = nil;
  1005. if (self.toolBarContainerClasses.count > 0) {
  1006. classes = self.toolBarContainerClasses;
  1007. } else {
  1008. classes = @[UINavigationController.class];
  1009. }
  1010. // toolBarContainerClasses 里可能会设置非 UINavigationController 的 class,由于这里只需要关注 UINavigationController 的,所以做一次过滤
  1011. classes = [classes qmui_filterWithBlock:^BOOL(Class<UIAppearanceContainer> _Nonnull item) {
  1012. return [item.class isSubclassOfClass:UINavigationController.class];
  1013. }];
  1014. return (NSArray <UINavigationController *>*)[self appearanceUpdatingViewControllersOfClasses:classes];
  1015. }
  1016. - (NSArray <UIViewController *>*)appearanceUpdatingViewControllersOfClasses:(NSArray<Class<UIAppearanceContainer>> *)classes {
  1017. if (!classes.count) return nil;
  1018. NSMutableArray *viewControllers = [NSMutableArray array];
  1019. [UIApplication.sharedApplication.windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
  1020. if (window.rootViewController) {
  1021. [viewControllers addObjectsFromArray:[window.rootViewController qmui_existingViewControllersOfClasses:classes]];
  1022. }
  1023. }];
  1024. return viewControllers;
  1025. }
  1026. @end
  1027. @implementation UINavigationBar (QMUIConfiguration)
  1028. + (instancetype)qmui_appearanceConfigured {
  1029. if (QMUICMIActivated && NavBarContainerClasses) {
  1030. return [self appearanceWhenContainedInInstancesOfClasses:NavBarContainerClasses];
  1031. }
  1032. return [self appearance];
  1033. }
  1034. @end
  1035. @implementation UITabBar (QMUIConfiguration)
  1036. + (instancetype)qmui_appearanceConfigured {
  1037. if (QMUICMIActivated && TabBarContainerClasses) {
  1038. return [self appearanceWhenContainedInInstancesOfClasses:TabBarContainerClasses];
  1039. }
  1040. return [self appearance];
  1041. }
  1042. @end
  1043. @implementation UIToolbar (QMUIConfiguration)
  1044. + (instancetype)qmui_appearanceConfigured {
  1045. if (QMUICMIActivated && ToolBarContainerClasses) {
  1046. return [self appearanceWhenContainedInInstancesOfClasses:ToolBarContainerClasses];
  1047. }
  1048. return [self appearance];
  1049. }
  1050. @end
  1051. @implementation UITabBarItem (QMUIConfiguration)
  1052. + (instancetype)qmui_appearanceConfigured {
  1053. if (QMUICMIActivated && TabBarContainerClasses) {
  1054. return [self appearanceWhenContainedInInstancesOfClasses:TabBarContainerClasses];
  1055. }
  1056. return [self appearance];
  1057. }
  1058. @end