本文共 4415 字,大约阅读时间需要 14 分钟。
在移动开发尤其是iOS应用开发中,主题切换功能逐渐成为用户体验的重要组成部分。从主流应用(如QQ、新浪微博、酷狗音乐、网易云音乐等)中可以观察到这一趋势:用户不仅希望通过切换主题来个性化体验,还期望主题切换能够伴随频繁更新,带来更多惊喜。基于此,本文将深入探讨如何通过NSObject分类实现主题切换功能,并结合实际应用场景进行分析和总结。
为了降低系统耦合度,选择使用NSObject分类实现主题设置功能。相较于直接对UIView进行类似的操作,使用NSObject分类可以更好地灵活管理各个视图元素的外观属性。这一点可以通过学习UISearchBar、UIBarButtonItem等父类来深入理解。
主题色管理是实现主题切换的核心环节。本文采用NSMapTable的弱引用方式存储主题色池,既满足内存管理需求,又保证了高效性。具体流程如下:
通过全局变量_lazyLoading的方式实现懒加载,确保主题色池在首次访问时创建,以减少内存占用。以下是相关代码示例:
/** 主题颜色池 */static NSMutableArray *_themeColorPool;- (NSMutableArray *)themeColorPool { if (!_themeColorPool) { _themeColorPool = [NSMutableArray array]; } return _themeColorPool;}
对于UIButton,需要区分属性设置和方法调用,故提供两个接口。具体实现如下:
- (void)py_addToThemeColorPoolWithSelector:(SEL)selector objects:(NSArray *)objects { if (!objects) return; Class appearanceClass = NSClassFromString(@"_UIAppearance"); if ([self isMemberOfClass:appearanceClass]) return; NSString *pointSelectorString = [NSString stringWithFormat:@"%p%@", self, NSStringFromSelector(selector)]; NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableCopyIn valueOptions:NSMapTableWeakMemory]; [mapTable setObject:self forKey:pointSelectorString]; [mapTable setObject:objects forKey:PYTHEME_COLOR_ARGS_KEY]; for (NSMapTable *subMapTable in [[self themeColorPool] copy]) { if ([[subMapTable description] isEqualToString:mapTable.description]) return; } [_themeColorPool addObject:mapTable]; if (_currentThemeColor) { [self py_performSelector:selector withObjects:objects]; }}- (void)py_addToThemeColorPool:(NSString *)propertyName { Class appearanceClass = NSClassFromString(@"_UIAppearance"); if ([self isMemberOfClass:appearanceClass]) return; NSString *pointString = [NSString stringWithFormat:@"%p%@", self, propertyName]; NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableCopyIn valueOptions:NSMapTableWeakMemory]; [mapTable setObject:self forKey:pointString]; for (NSMapTable *subMapTable in [[self themeColorPool] copy]) { if ([[subMapTable description] isEqualToString:mapTable.description]) return; } [_themeColorPool addObject:mapTable]; if (_currentThemeColor) { [self setValue:_currentThemeColor forKeyPath:propertyName]; }}
调用方法时,通过performSelector的方式传递参数,确保不同类型的参数可以正确处理:
- (id)py_performSelector:(SEL)selector withObjects:(NSArray *)objects { NSMethodSignature *methodSignate = [[self class] instanceMethodSignatureForSelector:selector]; if (!methodSignate) return self; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignate]; invocation.target = self; invocation.selector = selector; NSInteger paramsCount = methodSignate.numberOfArguments - 2; NSInteger count = MIN(paramsCount, objects.count); for (int i = 0; i < count; i++) { id obj = objects[i]; if ([obj isKindOfClass:[NSString class]] && [obj isEqualToString:PYTHEME_THEME_COLOR]) { obj = _currentThemeColor; } if ([obj isKindOfClass:[NSNull class]]) obj = nil; const char *argumentType = [methodSignate getArgumentTypeAtIndex:i + 2]; NSString *argumentTypeString = [NSString stringWithUTF8String:argumentType]; if ([argumentTypeString isEqualToString:@"@"]) { if ([obj isKindOfClass:[NSDictionary class]]) { obj = obj.mutableCopy; for (NSString *key in obj.allKeys) { if ([key isKindOfClass:[NSString class]] && [key isEqualToString:PYTHEME_THEME_COLOR]) { obj[key] = _currentThemeColor; } } [invocation setArgument:obj atIndex:i + 2]; } else { [invocation setArgument:obj atIndex:i + 2]; } } // 类似地处理其他类型如 Bool, Float, Double 等,直到 methodSignate覆盖完所有情况 } [invocation invoke]; id returnValue = nil; if (methodSignate.methodReturnLength != 0) { [invocation getReturnValue:&returnValue]; } return returnValue;}
工 امر号导航栏的背景颜色和按钮字体颜色随主题切换而改变的场景:
// 创建导航栏UINavigat
转载地址:http://xurzk.baihongyu.com/