diff --git a/QMUIConfigurationTemplate/QMUIConfigurationTemplate.h b/QMUIConfigurationTemplate/QMUIConfigurationTemplate.h index 695b8036..d7387ee8 100644 --- a/QMUIConfigurationTemplate/QMUIConfigurationTemplate.h +++ b/QMUIConfigurationTemplate/QMUIConfigurationTemplate.h @@ -8,15 +8,13 @@ #import /** - * QMUIConfigurationTemplate 是一份配置表,用于配合 QMUIKit 来管理整个 App 的全局样式,使用方式如下: - * 1. 在 QMUI 项目代码的文件夹里找到 QMUIConfigurationTemplate 目录,把里面所有文件复制到自己项目里。 - * 2. 在自己项目的 AppDelegate 里 #import "QMUIConfigurationTemplate.h",然后在 application:didFinishLaunchingWithOptions: 里调用 [QMUIConfigurationTemplate setupConfigurationTemplate],即可让配置表生效。 - * 3. 更新 QMUIKit 的版本时,请留意 Release Log 里是否有提醒更新配置表,请尽量保持自己项目里的配置表与 QMUIKit 里的配置表一致,避免遗漏新的属性。 + * QMUIConfigurationTemplate 是一份配置表,用于配合 QMUIConfiguration 来管理整个 App 的全局样式,使用方式: + * 在 QMUI 项目代码的文件夹里找到 QMUIConfigurationTemplate 目录,把里面所有文件复制到自己项目里,保证能被编译到即可,不需要在某些地方 import,也不需要手动运行。 * - * @warning 请不要在 + load 方法里调用 QMUIConfigurationTemplate 或 QMUIConfigurationMacros 提供的宏,那个时机太早,可能导致 crash + * @warning 更新 QMUIKit 的版本时,请留意 Release Log 里是否有提醒更新配置表,请尽量保持自己项目里的配置表与 QMUIKit 里的配置表一致,避免遗漏新的属性。 + * @warning 配置表的 class 名必须以 QMUIConfigurationTemplate 开头,并且实现 ,因为这两者是 QMUI 识别该 NSObject 是否为一份配置表的条件。 + * @warning QMUI 2.3.0 之后,配置表改为自动运行,不需要再在某个地方手动运行了。 */ -@interface QMUIConfigurationTemplate : NSObject - -+ (void)setupConfigurationTemplate; +@interface QMUIConfigurationTemplate : NSObject @end diff --git a/QMUIConfigurationTemplate/QMUIConfigurationTemplate.m b/QMUIConfigurationTemplate/QMUIConfigurationTemplate.m index 4222e07d..fc7dfdfc 100644 --- a/QMUIConfigurationTemplate/QMUIConfigurationTemplate.m +++ b/QMUIConfigurationTemplate/QMUIConfigurationTemplate.m @@ -11,7 +11,9 @@ @implementation QMUIConfigurationTemplate -+ (void)setupConfigurationTemplate { +#pragma mark - + +- (void)applyConfigurationTemplate { // === 修改配置值 === // @@ -85,7 +87,7 @@ + (void)setupConfigurationTemplate { QMUICMI.navBarLargeTitleColor = nil; // NavBarLargeTitleColor : UINavigationBar 在大标题模式下的标题颜色,仅在 iOS 11 之后才有效 QMUICMI.navBarLargeTitleFont = nil; // NavBarLargeTitleFont : UINavigationBar 在大标题模式下的标题字体,仅在 iOS 11 之后才有效 QMUICMI.navBarBackButtonTitlePositionAdjustment = UIOffsetZero; // NavBarBarBackButtonTitlePositionAdjustment : 导航栏返回按钮的文字偏移 - QMUICMI.navBarBackIndicatorImage = nil; // NavBarBackIndicatorImage : 导航栏的返回按钮的图片 + QMUICMI.navBarBackIndicatorImage = nil; // NavBarBackIndicatorImage : 导航栏的返回按钮的图片,图片尺寸需要为(13, 21),如果尺寸不一致则会自动调整,以保证与系统的返回按钮图片布局相同。 QMUICMI.navBarCloseButtonImage = [UIImage qmui_imageWithShape:QMUIImageShapeNavClose size:CGSizeMake(16, 16) tintColor:NavBarTintColor]; // NavBarCloseButtonImage : QMUINavigationButton 用到的 × 的按钮图片 QMUICMI.navBarLoadingMarginRight = 3; // NavBarLoadingMarginRight : QMUINavigationTitleView 里左边 loading 的右边距 @@ -193,4 +195,9 @@ + (void)setupConfigurationTemplate { QMUICMI.shouldFixTabBarTransitionBugInIPhoneX = NO; // ShouldFixTabBarTransitionBugInIPhoneX : 是否需要自动修复 iOS 11 下,iPhone X 的设备在 push 界面时,tabBar 会瞬间往上跳的 bug } +// QMUI 2.3.0 版本里,配置表新增这个方法,返回 YES 表示在 App 启动时要自动应用这份配置表。仅当你的 App 里存在多份配置表时,才需要把除默认配置表之外的其他配置表的返回值改为 NO。 +- (BOOL)shouldApplyTemplateAutomatically { + return YES; +} + @end diff --git a/QMUIKit/QMUICore/QMUIConfiguration.h b/QMUIKit/QMUICore/QMUIConfiguration.h index ce036e8e..392ef69b 100644 --- a/QMUIKit/QMUICore/QMUIConfiguration.h +++ b/QMUIKit/QMUICore/QMUIConfiguration.h @@ -9,6 +9,19 @@ #import #import +/// 所有配置表都应该实现的 protocol +@protocol QMUIConfigurationTemplateProtocol + +@required +/// 应用配置表的设置 +- (void)applyConfigurationTemplate; + +@optional +/// 当返回 YES 时,启动 App 的时候 QMUIConfiguration 会自动应用这份配置表。但启动 App 时自动应用的配置表最多只允许一份,如果有多份则其他的会被忽略,需要在某些时机手动应用 +- (BOOL)shouldApplyTemplateAutomatically; + +@end + /** * 维护项目全局 UI 配置的单例,通过业务项目自己的 QMUIConfigurationTemplate 来为这个单例赋值,而业务代码里则通过 QMUIConfigurationMacros.h 文件里的宏来使用这些值。 */ @@ -197,5 +210,6 @@ NS_ASSUME_NONNULL_END /// 单例对象 + (instancetype _Nullable )sharedInstance; +- (void)applyInitialTemplate; @end diff --git a/QMUIKit/QMUICore/QMUIConfiguration.m b/QMUIKit/QMUICore/QMUIConfiguration.m index 4ea63d67..aa0d67a4 100644 --- a/QMUIKit/QMUICore/QMUIConfiguration.m +++ b/QMUIKit/QMUICore/QMUIConfiguration.m @@ -11,26 +11,13 @@ #import "UIImage+QMUI.h" #import "NSString+QMUI.h" #import "UIViewController+QMUI.h" +#import @implementation QMUIConfiguration + (instancetype)sharedInstance { static dispatch_once_t pred; - static QMUIConfiguration *sharedInstance = nil; - - // 检查是否有在某些类的 +load 方法里调用 QMUICMI,因为在 [QMUIConfiguration init] 方法里会操作到 UI 的东西,例如 [UINavigationBar appearance] xxx 等,这些操作不能太早(+load 里就太早了)执行,否则会 crash,所以加这个检测 -//#ifdef DEBUG -// BOOL shouldCheckCallStack = NO; -// if (shouldCheckCallStack) { -// for (NSString *symbol in [NSThread callStackSymbols]) { -// if ([symbol containsString:@" load]"]) { -// NSAssert(NO, @"不应该在 + load 方法里调用 %s", __func__); -// return nil; -// } -// } -// } -//#endif - + static QMUIConfiguration *sharedInstance; dispatch_once(&pred, ^{ sharedInstance = [[QMUIConfiguration alloc] init]; }); @@ -45,6 +32,39 @@ - (instancetype)init { return self; } +static BOOL QMUI_hasAppliedInitialTemplate; +- (void)applyInitialTemplate { + if (QMUI_hasAppliedInitialTemplate) { + return; + } + + // 自动寻找并应用模板的解释参照这里 https://github.com/QMUI/QMUI_iOS/issues/264 + + Protocol *protocol = @protocol(QMUIConfigurationTemplateProtocol); + int numberOfClasses = objc_getClassList(NULL, 0); + if (numberOfClasses > 0) { + Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numberOfClasses); + numberOfClasses = objc_getClassList(classes, numberOfClasses); + for (int i = 0; i < numberOfClasses; i++) { + Class class = classes[i]; + if ([NSStringFromClass(class) hasPrefix:@"QMUIConfigurationTemplate"] && [class conformsToProtocol:protocol]) { + if ([class instancesRespondToSelector:@selector(shouldApplyTemplateAutomatically)]) { + id template = [[class alloc] init]; + if ([template shouldApplyTemplateAutomatically]) { + QMUI_hasAppliedInitialTemplate = YES; + [template applyConfigurationTemplate]; + // 只应用第一个 shouldApplyTemplateAutomatically 的主题 + break; + } + } + } + } + free(classes); + } + + QMUI_hasAppliedInitialTemplate = YES; +} + #pragma mark - 初始化默认值 - (void)initDefaultConfiguration { @@ -329,10 +349,10 @@ - (void)setNavBarBackIndicatorImage:(UIImage *)navBarBackIndicatorImage { CGSize customBackIndicatorImageSize = _navBarBackIndicatorImage.size; if (!CGSizeEqualToSize(customBackIndicatorImageSize, systemBackIndicatorImageSize)) { CGFloat imageExtensionVerticalFloat = CGFloatGetCenter(systemBackIndicatorImageSize.height, customBackIndicatorImageSize.height); - _navBarBackIndicatorImage = [_navBarBackIndicatorImage qmui_imageWithSpacingExtensionInsets:UIEdgeInsetsMake(imageExtensionVerticalFloat, - 0, - imageExtensionVerticalFloat, - systemBackIndicatorImageSize.width - customBackIndicatorImageSize.width)]; + _navBarBackIndicatorImage = [[_navBarBackIndicatorImage qmui_imageWithSpacingExtensionInsets:UIEdgeInsetsMake(imageExtensionVerticalFloat, + 0, + imageExtensionVerticalFloat, + systemBackIndicatorImageSize.width - customBackIndicatorImageSize.width)] imageWithRenderingMode:_navBarBackIndicatorImage.renderingMode]; } navBarAppearance.backIndicatorImage = _navBarBackIndicatorImage; diff --git a/QMUIKit/QMUICore/QMUIConfigurationMacros.h b/QMUIKit/QMUICore/QMUIConfigurationMacros.h index 4bd9ddac..33af9eca 100644 --- a/QMUIKit/QMUICore/QMUIConfigurationMacros.h +++ b/QMUIKit/QMUICore/QMUIConfigurationMacros.h @@ -18,7 +18,7 @@ // 单例的宏 -#define QMUICMI [QMUIConfiguration sharedInstance] +#define QMUICMI ({[[QMUIConfiguration sharedInstance] applyInitialTemplate];[QMUIConfiguration sharedInstance];}) #pragma mark - Global Color