| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- //
- // TUIThemeManager.m
- // TUICore
- //
- // Created by harvy on 2022/1/5.
- // Copyright © 2023 Tencent. All rights reserved.
- //
- #import "TUIThemeManager.h"
- #import "UIColor+TUIHexColor.h"
- @interface TUIDarkThemeRootVC : UIViewController
- @end
- @implementation TUIDarkThemeRootVC
- - (BOOL)shouldAutorotate {
- return NO;
- }
- @end
- @interface TUIDarkWindow : UIWindow
- @property(nonatomic, readonly, class) TUIDarkWindow *sharedInstance;
- @property(nonatomic, strong) UIWindow *previousKeyWindow;
- @end
- @implementation TUIDarkWindow
- + (void)load {
- [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(windowDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
- }
- + (void)windowDidBecomeActive {
- UIWindow *darkWindow = [self sharedInstance];
- if (@available(iOS 13.0, *)) {
- UIScene *scene = UIApplication.sharedApplication.connectedScenes.anyObject;
- if (scene) {
- darkWindow.windowScene = (UIWindowScene *)scene;
- }
- }
- [darkWindow setRootViewController:[TUIDarkThemeRootVC new]];
- darkWindow.hidden = NO;
- [NSNotificationCenter.defaultCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
- }
- - (void)becomeKeyWindow {
- _previousKeyWindow = [self appKeyWindow];
- [super becomeKeyWindow];
- }
- - (void)resignKeyWindow {
- [super resignKeyWindow];
- [_previousKeyWindow makeKeyWindow];
- _previousKeyWindow = nil;
- }
- - (UIWindow *)appKeyWindow {
- UIWindow *keywindow = UIApplication.sharedApplication.keyWindow;
- if (keywindow == nil) {
- if (@available(iOS 13.0, *)) {
- for (UIWindowScene *scene in UIApplication.sharedApplication.connectedScenes) {
- if (scene.activationState == UISceneActivationStateForegroundActive) {
- UIWindow *tmpWindow = nil;
- if (@available(iOS 15.0, *)) {
- tmpWindow = scene.keyWindow;
- }
- if (tmpWindow == nil) {
- for (UIWindow *window in scene.windows) {
- if (window.windowLevel == UIWindowLevelNormal && window.hidden == NO &&
- CGRectEqualToRect(window.bounds, UIScreen.mainScreen.bounds)) {
- tmpWindow = window;
- break;
- }
- }
- }
- }
- }
- }
- }
- if (keywindow == nil) {
- for (UIWindow *window in UIApplication.sharedApplication.windows) {
- if (window.windowLevel == UIWindowLevelNormal && window.hidden == NO && CGRectEqualToRect(window.bounds, UIScreen.mainScreen.bounds)) {
- keywindow = window;
- break;
- }
- }
- }
- return keywindow;
- }
- + (instancetype)sharedInstance {
- static TUIDarkWindow *shareWindow = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- shareWindow = [[self alloc] init];
- shareWindow.frame = UIScreen.mainScreen.bounds;
- shareWindow.userInteractionEnabled = YES;
- shareWindow.windowLevel = UIWindowLevelNormal - 1;
- shareWindow.hidden = YES;
- shareWindow.opaque = NO;
- shareWindow.backgroundColor = [UIColor clearColor];
- shareWindow.layer.backgroundColor = [UIColor clearColor].CGColor;
- });
- return shareWindow;
- }
- - (void)setOverrideUserInterfaceStyle:(UIUserInterfaceStyle)overrideUserInterfaceStyle {
- }
- - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
- [super traitCollectionDidChange:previousTraitCollection];
- if (@available(iOS 13.0, *)) {
- if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
- [NSNotificationCenter.defaultCenter postNotificationName:TUIDidApplyingThemeChangedNotfication object:nil];
- if ([TUIThemeManager.shareManager respondsToSelector:@selector(allListenerExcuteonApplyThemeMethod:module:)]) {
- [TUIThemeManager.shareManager performSelector:@selector(allListenerExcuteonApplyThemeMethod:module:) withObject:nil withObject:nil];
- }
- }
- }
- }
- @end
- @implementation TUITheme
- + (UIColor *)dynamicColor:(NSString *)colorKey module:(TUIThemeModule)module defaultColor:(NSString *)hex {
- TUITheme *theme = TUICurrentTheme(module);
- TUITheme *darkTheme = TUIDarkTheme(module);
- if (theme) {
- return [theme dynamicColor:colorKey defaultColor:hex];
- } else {
- if (@available(iOS 13.0, *)) {
- return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(UITraitCollection *_Nonnull traitCollection) {
- switch (traitCollection.userInterfaceStyle) {
- case UIUserInterfaceStyleDark:
- if(darkTheme){
- return [darkTheme dynamicColor:colorKey defaultColor:hex];
- }
- case UIUserInterfaceStyleLight:
- case UIUserInterfaceStyleUnspecified:
- default:
- return [UIColor tui_colorWithHex:hex];
- }
- }];
- } else {
- return [UIColor tui_colorWithHex:hex];
- }
- }
- }
- - (UIColor *)dynamicColor:(NSString *)colorKey defaultColor:(NSString *)hex {
- UIColor *color = nil;
- NSString *colorHex = [self.manifest objectForKey:colorKey];
- if (colorHex && [colorHex isKindOfClass:NSString.class]) {
- color = [UIColor tui_colorWithHex:colorHex];
- }
- if (color == nil) {
- color = [UIColor tui_colorWithHex:hex];
- }
- return color;
- }
- + (UIImage *)dynamicImage:(NSString *)imageKey module:(TUIThemeModule)module defaultImage:(UIImage *)image {
- TUITheme *theme = TUICurrentTheme(module);
- TUITheme *darkTheme = TUIDarkTheme(module);
- if (theme) {
- return [theme dynamicImage:imageKey defaultImage:image];
- } else {
- UIImage *lightImage = image;
- UIImage *darkImage = [darkTheme dynamicImage:imageKey defaultImage:image];
- return [self imageWithImageLight:lightImage dark:darkImage];
- }
- }
- - (UIImage *)dynamicImage:(NSString *)imageKey defaultImage:(UIImage *)image {
- UIImage *dynamic = nil;
- NSString *imageName = [self.manifest objectForKey:imageKey];
- if ([imageName isKindOfClass:NSString.class]) {
- imageName = [self.resourcePath stringByAppendingPathComponent:imageName];
- dynamic = [UIImage imageWithContentsOfFile:imageName];
- }
- if (dynamic == nil) {
- dynamic = image;
- }
- return dynamic;
- }
- + (UIImage *)imageWithImageLight:(UIImage *)lightImage dark:(UIImage *)darkImage {
- if (@available(iOS 13.0, *)) {
- UITraitCollection *const scaleTraitCollection = [UITraitCollection currentTraitCollection];
- UITraitCollection *const darkUnscaledTraitCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
- UITraitCollection *const darkScaledTraitCollection =
- [UITraitCollection traitCollectionWithTraitsFromCollections:@[ scaleTraitCollection, darkUnscaledTraitCollection ]];
- UIImage *image = [lightImage
- imageWithConfiguration:[lightImage.configuration
- configurationWithTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]]];
- darkImage = [darkImage
- imageWithConfiguration:[darkImage.configuration
- configurationWithTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark]]];
- [image.imageAsset registerImage:darkImage withTraitCollection:darkScaledTraitCollection];
- return image;
- } else {
- return lightImage;
- }
- }
- @end
- @interface TUIThemeManager ()
- /**
- * The theme resource path of each module, module: theme path
- */
- @property(nonatomic, strong) NSMutableDictionary<NSNumber *, NSString *> *themeResourcePathCache;
- /**
- * The theme currently used by module, module: theme
- */
- @property(nonatomic, strong) NSMutableDictionary<NSNumber *, TUITheme *> *currentThemeCache;
- /**
- * The dark theme for each module, if any
- */
- @property(nonatomic, strong) NSMutableDictionary<NSNumber *, TUITheme *> *darkThemeCache;
- @property(nonatomic, strong) NSHashTable *listeners;
- - (void)allListenerExcuteonApplyThemeMethod:(TUITheme *)theme module:(TUIThemeModule)module;
- @end
- @implementation TUIThemeManager
- #ifdef TUIThreadSafe
- dispatch_queue_t _queue;
- dispatch_queue_t _read_write_queue;
- #endif
- static id gShareInstance;
- + (instancetype)shareManager {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- gShareInstance = [[self alloc] init];
- });
- return gShareInstance;
- }
- - (instancetype)init {
- if (self = [super init]) {
- #ifdef TUIThreadSafe
- _queue = dispatch_queue_create("theme_manager_queue", DISPATCH_QUEUE_SERIAL);
- _read_write_queue = dispatch_queue_create("read_write_secure_queue", DISPATCH_QUEUE_CONCURRENT);
- #endif
- _themeResourcePathCache = [NSMutableDictionary dictionary];
- _currentThemeCache = [NSMutableDictionary dictionary];
- _darkThemeCache = [NSMutableDictionary dictionary];
- _listeners = [NSHashTable weakObjectsHashTable];
- }
- return self;
- }
- - (void)addListener:(id<TUIThemeManagerListener>)listener {
- #ifdef TUIThreadSafe
- dispatch_async(_queue, ^{
- #endif
- if (![self.listeners containsObject:listener]) {
- [self.listeners addObject:listener];
- }
- #ifdef TUIThreadSafe
- });
- #endif
- }
- - (void)removeListener:(id<TUIThemeManagerListener>)listener {
- #ifdef TUIThreadSafe
- dispatch_async(_queue, ^{
- #endif
- if ([self.listeners containsObject:listener]) {
- [self.listeners removeObject:listener];
- }
- #ifdef TUIThreadSafe
- });
- #endif
- }
- - (void)registerThemeResourcePath:(NSString *)path darkThemeID:(NSString *)darkThemeID forModule:(TUIThemeModule)module {
- if (path.length == 0) {
- return;
- }
- #ifdef TUIThreadSafe
- dispatch_async(_queue, ^{
- #endif
- [self.themeResourcePathCache setObject:path forKey:@(module)];
- TUITheme *theme = [self loadTheme:darkThemeID module:module];
- if (theme) {
- [self setDarkTheme:theme forModule:module];
- }
- #ifdef TUIThreadSafe
- });
- #endif
- }
- - (void)registerThemeResourcePath:(NSString *)path forModule:(TUIThemeModule)module {
- [self registerThemeResourcePath:path darkThemeID:@"dark" forModule:module];
- }
- - (TUITheme *)currentThemeForModule:(TUIThemeModule)module {
- __block TUITheme *theme = nil;
- #ifdef TUIThreadSafe
- dispatch_sync(_read_write_queue, ^{
- #endif
- theme = [self.currentThemeCache objectForKey:@(module)];
- #ifdef TUIThreadSafe
- });
- #endif
- return theme;
- }
- - (void)setCurrentTheme:(TUITheme *)theme forModule:(TUIThemeModule)module {
- #ifdef TUIThreadSafe
- dispatch_barrier_async(_read_write_queue, ^{
- #endif
- if ([self.currentThemeCache.allKeys containsObject:@(module)]) {
- [self.currentThemeCache removeObjectForKey:@(module)];
- }
- if (theme) {
- [self.currentThemeCache setObject:theme forKey:@(module)];
- }
- #ifdef TUIThreadSafe
- });
- #endif
- }
- - (TUITheme *)darkThemeForModule:(TUIThemeModule)module {
- __block TUITheme *theme = nil;
- #ifdef TUIThreadSafe
- dispatch_sync(_read_write_queue, ^{
- #endif
- if ([self.darkThemeCache.allKeys containsObject:@(module)]) {
- theme = [self.darkThemeCache objectForKey:@(module)];
- }
- #ifdef TUIThreadSafe
- });
- #endif
- return theme;
- }
- - (void)setDarkTheme:(TUITheme *)theme forModule:(TUIThemeModule)module {
- #ifdef TUIThreadSafe
- dispatch_barrier_async(_read_write_queue, ^{
- #endif
- if ([self.darkThemeCache.allKeys containsObject:@(module)]) {
- [self.darkThemeCache removeObjectForKey:@(module)];
- }
- if (theme) {
- [self.darkThemeCache setObject:theme forKey:@(module)];
- }
- #ifdef TUIThreadSafe
- });
- #endif
- }
- - (void)applyTheme:(NSString *)themeID forModule:(TUIThemeModule)module {
- if (themeID.length == 0) {
- NSLog(@"[theme][applyTheme] invalid themeID, module:%zd", module);
- return;
- }
- #ifdef TUIThreadSafe
- dispatch_async(_queue, ^{
- #endif
- BOOL isAll = NO;
- NSMutableArray *allKeys = [NSMutableArray arrayWithArray:self.themeResourcePathCache.allKeys];
- if (module == TUIThemeModuleAll || ((module & TUIThemeModuleAll) == TUIThemeModuleAll)) {
- isAll = YES;
- }
- if (isAll) {
- for (NSNumber *moduleObject in allKeys) {
- TUIThemeModule tmpModue = (TUIThemeModule)[moduleObject integerValue];
- [self doApplyTheme:themeID forSingleModule:tmpModue];
- }
- } else {
- for (NSNumber *moduleObject in allKeys) {
- TUIThemeModule tmpModue = (TUIThemeModule)[moduleObject integerValue];
- if ((module & tmpModue) == tmpModue) {
- [self doApplyTheme:themeID forSingleModule:tmpModue];
- }
- }
- }
- #ifdef TUIThreadSafe
- });
- #endif
- }
- - (void)unApplyThemeForModule:(TUIThemeModule)module {
- #ifdef TUIThreadSafe
- dispatch_async(_queue, ^{
- #endif
- BOOL isAll = NO;
- NSMutableArray *allKeys = [NSMutableArray arrayWithArray:self.themeResourcePathCache.allKeys];
- if (module == TUIThemeModuleAll || ((module & TUIThemeModuleAll) == TUIThemeModuleAll)) {
- isAll = YES;
- }
- if (isAll) {
- for (NSNumber *moduleObject in allKeys) {
- TUIThemeModule tmpModue = (TUIThemeModule)[moduleObject integerValue];
- [self setCurrentTheme:nil forModule:tmpModue];
- }
- [NSNotificationCenter.defaultCenter postNotificationName:TUIDidApplyingThemeChangedNotfication object:nil userInfo:nil];
- } else {
- for (NSNumber *moduleObject in allKeys) {
- TUIThemeModule tmpModue = (TUIThemeModule)[moduleObject integerValue];
- if ((module & tmpModue) == tmpModue) {
- [self setCurrentTheme:nil forModule:tmpModue];
- }
- }
- }
- #ifdef TUIThreadSafe
- });
- #endif
- }
- #pragma mark - Not thread safe
- - (void)doApplyTheme:(NSString *)themeID forSingleModule:(TUIThemeModule)module {
- TUITheme *theme = [self loadTheme:themeID module:module];
- if (theme == nil) {
- return;
- }
- [self setCurrentTheme:theme forModule:module];
- [self notifyApplyTheme:theme module:module];
- }
- - (TUITheme *)loadTheme:(NSString *)themeID module:(TUIThemeModule)module {
- NSString *themeResourcePath = [self themeResourcePathForModule:module];
- if (themeResourcePath.length == 0) {
- NSLog(@"[theme][applyTheme] theme resurce path not set, themeID:%@, module:%zd", themeID, module);
- return nil;
- }
- themeResourcePath = [themeResourcePath stringByAppendingPathComponent:themeID];
- {
- BOOL isDirectory = NO;
- BOOL exist = [NSFileManager.defaultManager fileExistsAtPath:themeResourcePath isDirectory:&isDirectory];
- if (!exist || !isDirectory) {
- NSLog(@"[theme][applyTheme] invalid theme resurce, themeID:%@, module:%zd", themeID, module);
- return nil;
- }
- }
- NSString *manifestPath = [themeResourcePath stringByAppendingPathComponent:@"manifest.plist"];
- {
- BOOL isDirectory = NO;
- BOOL exist = [NSFileManager.defaultManager fileExistsAtPath:manifestPath isDirectory:&isDirectory];
- if (!exist || isDirectory) {
- NSLog(@"[theme][applyTheme] invalid manifest, themeID:%@, module:%zd", themeID, module);
- return nil;
- }
- }
- NSString *resourcePath = [themeResourcePath stringByAppendingPathComponent:@"resource"];
- {
- BOOL isDirectory = NO;
- BOOL exist = [NSFileManager.defaultManager fileExistsAtPath:resourcePath isDirectory:&isDirectory];
- if (!exist || !isDirectory) {
- NSLog(@"[theme][applyTheme] invalid resurce, themeID:%@, module:%zd", themeID, module);
- }
- }
- NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:manifestPath];
- if (dict == nil) {
- NSLog(@"[theme][applyTheme] manifest is null");
- return nil;
- }
- TUITheme *theme = [[TUITheme alloc] init];
- theme.themeID = themeID;
- theme.module = module;
- theme.themeDesc = [NSString stringWithFormat:@"theme_%@_%zd", themeID, module];
- theme.resourcePath = resourcePath;
- theme.manifest = dict;
- return theme;
- }
- - (void)notifyApplyTheme:(TUITheme *)theme module:(TUIThemeModule)module {
- if (theme == nil) {
- return;
- }
- if (![NSThread isMainThread]) {
- __weak typeof(self) weakSelf = self;
- dispatch_async(dispatch_get_main_queue(), ^{
- [weakSelf notifyApplyTheme:theme module:module];
- });
- return;
- }
- [self allListenerExcuteonApplyThemeMethod:theme module:module];
- NSDictionary *userInfo = @{TUIDidApplyingThemeChangedNotficationModuleKey : @(module), TUIDidApplyingThemeChangedNotficationThemeKey : theme};
- [NSNotificationCenter.defaultCenter postNotificationName:TUIDidApplyingThemeChangedNotfication object:nil userInfo:userInfo];
- }
- - (void)allListenerExcuteonApplyThemeMethod:(TUITheme *)theme module:(TUIThemeModule)module {
- for (id<TUIThemeManagerListener> listener in self.listeners) {
- if ([listener respondsToSelector:@selector(onApplyTheme:module:)]) {
- [listener onApplyTheme:theme module:module];
- }
- }
- }
- - (NSString *)themeResourcePathForModule:(TUIThemeModule)module {
- if ([self.themeResourcePathCache.allKeys containsObject:@(module)]) {
- return [self.themeResourcePathCache objectForKey:@(module)];
- }
- return @"";
- }
- @end
|