博客
关于我
iOS 实现快速切换主题详细教程(附上源码)| 掘金技术征文
阅读量:772 次
发布时间:2019-03-23

本文共 4415 字,大约阅读时间需要 14 分钟。

iOS主题切换实现与应用

在移动开发尤其是iOS应用开发中,主题切换功能逐渐成为用户体验的重要组成部分。从主流应用(如QQ、新浪微博、酷狗音乐、网易云音乐等)中可以观察到这一趋势:用户不仅希望通过切换主题来个性化体验,还期望主题切换能够伴随频繁更新,带来更多惊喜。基于此,本文将深入探讨如何通过NSObject分类实现主题切换功能,并结合实际应用场景进行分析和总结。

实现思路

为了降低系统耦合度,选择使用NSObject分类实现主题设置功能。相较于直接对UIView进行类似的操作,使用NSObject分类可以更好地灵活管理各个视图元素的外观属性。这一点可以通过学习UISearchBar、UIBarButtonItem等父类来深入理解。

主题色管理

主题色管理是实现主题切换的核心环节。本文采用NSMapTable的弱引用方式存储主题色池,既满足内存管理需求,又保证了高效性。具体流程如下:

  • 创建主题色池:通过懒加载机制,使用NSMapTable存储控件及其对应属性。
  • 添加控件到主题色池
    • 如果控件属性可以直接通过属性设置(如UIButton.backgroundColor),则直接添加对应的属性名。
    • 如果需要调用方法(如UIButton.setTitleColor:forState:)/修改动态属性,按照方法选择器和参数方式添加。
  • 移除控件:对应的移除方法提供灵活的控制接口。
  • 代码实现

    1.主题色池管理

    通过全局变量_lazyLoading的方式实现懒加载,确保主题色池在首次访问时创建,以减少内存占用。以下是相关代码示例:

    /** 主题颜色池 */
    static NSMutableArray *_themeColorPool;
    - (NSMutableArray *)themeColorPool {
    if (!_themeColorPool) {
    _themeColorPool = [NSMutableArray array];
    }
    return _themeColorPool;
    }

    2.添加控件

    对于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];
    }
    }

    3.主题色应用

    调用方法时,通过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/

    你可能感兴趣的文章
    Nacos在双击startup.cmd启动时提示:Unable to start embedded Tomcat
    查看>>
    Nacos如何实现Raft算法与Raft协议原理详解
    查看>>
    Nacos安装教程(非常详细)从零基础入门到精通,看完这一篇就够了
    查看>>
    nacos注册失败,Feign调用失败,feign无法注入成我们的bean对象
    查看>>
    nacos源码 nacos注册中心1.4.x 源码 nacos源码如何下载 nacos 客户端源码下载地址 nacos discovery下载地址(一)
    查看>>
    Nacos编译报错NacosException: endpoint is blank
    查看>>
    NACOS部署,微服务框架之NACOS-单机、集群方式部署
    查看>>
    Nacos配置中心集群原理及源码分析
    查看>>
    nacos配置自动刷新源码解析
    查看>>
    Nacos集群搭建
    查看>>
    nacos集群搭建
    查看>>
    nagios安装文档
    查看>>
    name_save matlab
    查看>>
    Nami 项目使用教程
    查看>>
    NAT-DDNS内网穿透技术,解决动态域名解析难题
    查看>>
    NativePHP:使用PHP构建跨平台桌面应用的新框架
    查看>>
    NAT技术
    查看>>
    NAT模式下虚拟机centOs和主机ping不通解决方法
    查看>>
    NAT的两种模式SNAT和DNAT,到底有啥区别?
    查看>>
    Navicat for MySQL 命令列 执行SQL语句 历史日志
    查看>>