使用appearance proxy定制控件的默认外观(详解)
控件的外观,受到tint color,background image, background color等属性的共同影响,通常要修改某个控件对象的外观,就去调用上面属性的相关setter方法(或者其他可以修改它们的方法)就可以了。但是,如果希望整个app中的控件都保持一致的风格,比如所有button的风格(指的是大小,背景图,形状等)都一样,那么一个一个去重复设置每个button的风格,就显得太麻烦了。如果可以给Button类设定一个默认外观,就方便多了。appearance proxy就可以实现。
对于导航栏,tabBarController等等管理多个视图控制器的控件,要对它们的全局外观进行修改,通常比较便捷的方法也是定制默认外观,也就是使用appearance proxy。下面就来详细介绍appearance proxy。
一、首先,要了解一个概念appearance proxy外观代理:
外观代理,它是一个管理可视化类(比如控件)的默认外观的对象。我们可以通过这个类的外观代理,来修改这个类的默认外观(与外观有关的属性的默认值),或者这个类在某种场合下的默认外观。
二、需要遵守两个协议
一个类要能够使用外观代理,需要遵守两个协议,一个是UIAppearance,一个是UIAppearanceContainer协议。
1. UIAppearance协议
该协议定义了4个方法,用于获取类的外观代理,分别对应4种外观代理。
2. UIAppearanceContainer协议
该协议定义了一个常量UI_APPEARANCE_SELECTOR,用来标识外观代理可以调用的方法。既然外观代理是用来管理类的默认外观的,那么能够被它调用的方法,或者说它需要调用的方法,往往就是类的某个外观特征的setter和getter方法。也就是说,为一个方法设定UI_APPEARANCE_SELECTOR标签,就表示这个方法可以为外观代理所用。当然,方法的格式是有规定的,基本格式和一般属性的setter和getter方法格式一样,在这个基础上可以增加更多的参数。参数的类型只能是NSInteger或NSUInteger,否则会报错。
三、外观代理的工作时间
官网上简洁的表述:
所有通过外观代理对默认外观进行的修改,只会在view layout time 时生效。在此之后,仍可以通过对象的属性和方法来覆盖此默认外观。
我的详细解释:
我们可以随时让外观代理修改默认外观,准确的理解是让外观代理记录好我们对默认外观的定制要求。但是外观代理只会在view layout time时工作一次,也就是让默认外观生效。在此之后,调用外观代理来修改默认外观,是不会即时生效的,换言之,必须要等到下次view layout time的时候才会让这个新定制的默认外观生效。什么时候是view layout time?从运行的效果来看,我暂且理解为加载window(或从其他window返回到本window)的时候,如果有更准确的理解,望赐教。
默认外观对类的所有的对象起作用(所谓默认,就是这个意思)。如果要在默认外观的基础上对某个对象的外观进行修改,就和平常一样,调用这个对象有关外观设置的方法即可。
四、外观代理的获取及使用
下面详细了解UIAppearance协议中定义的4种获取外观代理的方法,及其对应的4种外观代理:
1.全局外观代理
功能:代理中的Big Boss,它可以管理控件在任何情况下的默认外观。
获取代理的方法:+ ( instancetype )appearance
比如:要获取UINavigationBar的全局外观代理:
UINavigation *appearanceProxy=[UINavigation appearance];
2.只负责指定场合的外观的外观代理
功能:管理控件被包含在指定的类(及其子类)中时的默认外观;管理控件被包含在指定的视图层级关系中时的默认外观。
获取代理的方法:
+ ( instancetype )appearanceWhenContainedIn:(Class<UIAppearanceContainer>)ContainerClass, …
- 1
解释:
这个方法的参数指定了某个类或者多个类。参数个数不定,最后一个参数必须是nil。
例子:
比如,获取 作用于“放在UIToolbar类中的UIBarButtonItem对象”的外观代理,这样写:
[UIBarButtonItem appearanceWhenContainedIn: [UIToolbar class],nil];
- 1
注意:
此时除了放在UIToolbar类中的UIBarButtonItem对象会受这个外观代理管理以外,放在UIToolbar的子类中的UIBarButtonItem对象也会受这个代理管理。
如果要指定层级关系,则传入一个层级关系的对象。
上面说的方法从iOS 5.0开始启用,到iOS 9.0丢弃,iOS 9.0及以后用下面的方法替代:
+(instancetype)appearanceWhenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes
- 1
比如,设置导航栏被放在UISplitViewController或者UITabBarController里面时的默认BarTintColor:
[[UINavigationBar appearanceWhenContainedInInstancesOfClasses: @[[UISplitViewController class], [UITabBarController class]]] setBarTintColor:myColor];
- 1
3.只负责控件在某种系统环境下的外观的外观代理
功能:管理类在某种系统环境下(比如横屏、竖屏等)的默认外观。系统环境由UITraitCollection对象来指定。(查看UITraitCollection Class Reference)
获取代理的方法:
+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait
- 1
4.只负责控件在某种系统环境、某种场合下的外观的外观代理
功能:管理控件在某种系统环境、某种场合下的默认外观
获取代理的方法:
+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedIn:(Class<UIAppearanceContainer>)ContainerClass
- 1
以上方法从5.0开始启用,在iOS 9.0被丢弃,iOS 9.0及以后用下面这个方法替代:
+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes