1、前言
- 在一些app中会涉及到更改外观设置的功能,最普遍的就是夜间模式和白天模式的切换,而对于外观的更改必定是一个全局的东西。
- 我们可以通过UIAppearance协议的方法来给整个项目中某一类控件添加全局样式,或者项目中某个类的子类控件添加全局样式,使得外观的自定义更加容易实现。
- 举例:
// 自定义导航栏 [UINavigationBar appearance].barTintColor = [UIColor redColor]; // 设置滚动条显示 [[UITableView appearance] setShowsVerticalScrollIndicator:NO]; // 设置背景颜色 [[UITableView appearance] setBackgroundColor:kAppBackGroundColor]; // 设置背景颜色 [[UICollectionView appearance] setBackgroundColor:kAppBackGroundColor]; // 设置背景颜色 [[UICollectionViewCell appearance] setBackgroundColor:[UIColor whiteColor]]; [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]].color = [UIColor whiteColor];
- 这样使用appearance的好处就显而易见了,因为这个设置是一个全局的效果,一处设置之后在其他地方都无需再设置。实际上,appearance的作用就是统一外观设置。
2、使用说明
-
2.1 什么控件能使用
- 遵循UIAppearance协议的类,才能使用协议中的方法。
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIBarItem : NSObject <NSCoding, UIAppearance> NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, CALayerDelegate>
- 实际上能使用appearance的地方是在方法或者属性后面有UI_APPEARANCE_SELECTOR宏的地方
@property(nonatomic,assign) UIBarStyle barStyle UI_APPEARANCE_SELECTOR - (void)setTitleTextAttributes:(nullable NSDictionary<NSString *,id> *)attributes forState:(UIControlState)state NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
-
2.2 修改系统默认参数
- 让某一类控件同时约束
// UITableView不显示垂直线 [[UITableView appearance] setShowsVerticalScrollIndicator:NO]; // 全部按钮显示白色 [[UIButton appearance] setBackgroundColor:[UIColor whiteColor]];
-
2.3 模仿系统做法
- 如果我们自定义的视图也想要一个全局的外观设置,那么使用UIAppearance来实现非常的方便,接下来就以一个小demo实现。
- 自定义一个继承自UIView的CardView,CardView中添加两个SubView:leftView和rightView,高度和CardView一样,宽度分别占据一半。
- 然后在.h文件中提供修改两个子视图颜色的API,并添加UI_APPEARANCE_SELECTOR宏
@property (nonatomic, strong)UIColor * leftColor UI_APPEARANCE_SELECTOR; @property (nonatomic, strong)UIColor * rightColor UI_APPEARANCE_SELECTOR;
- 在.m文件中重写他们的setter方法设置两个子视图的颜色
- (void)setLeftColor:(UIColor *)leftColor { _leftColor = leftColor; self.leftView.backgroundColor = _leftColor; } - (void)setRightColor:(UIColor *)rightColor { _rightColor = rightColor; self.rightView.backgroundColor = _rightColor; }
- 提供两个VC,在第一个VC的viewDidLoad方法中进行全局的颜色设置
- (void)viewDidLoad { [super viewDidLoad]; [CardView appearance].leftColor = [UIColor redColor]; [CardView appearance].rightColor = [UIColor yellowColor]; }
- 分别在两个VC的touchesBegan方法中初始化和添加CardView视图
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { CardView * cardView = [[CardView alloc]initWithFrame:CGRectMake(20, 100, 200, 100)]; [self.view addSubview:cardView]; }
- 然后运行之后发现两个VC中的CardView的颜色效果是相同的。
-
2.4 根据场景进行定制
-
自定义演示
- UIAppearance既可以修改某一类型控件的全部实例,又可以修改部分实例。
- 比如之前我们在demo中的第一个界面改变CardView的leftColor的全部实例的时候是这样做的
[CardView appearance].leftColor = [UIColor redColor];
- 这是使用了这个API
+ (instancetype)appearance;
- 如果我只想在修改部分实例需要使用另外的API
+ (instancetype)appearanceWhenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes NS_AVAILABLE_IOS(9_0);
- 比如如果第二个VC是以presentViewController的方式跳转的,只想修改第一个界面上的CardView的leftColor可以在上述代码后面增加如下代码:
[CardView appearanceWhenContainedInInstancesOfClasses:@[[UINavigationController class]]].leftColor = [UIColor greenColor];
- 运行之后第一个界面的效果为:
- 第二个则没有变化。
-
修改系统的
- 让一种控件在另一种控件中表现某种属性
// 获取TZImagePickerController 中的UIBarButtonItem类控件,设置 UIBarButtonItem *barItem; if (@available(iOS 9.0, *)) { barItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[TZImagePickerController class]]]; } else { barItem = [UIBarButtonItem appearanceWhenContainedIn:[TZImagePickerController class], nil]; } NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary]; textAttrs[NSForegroundColorAttributeName] = self.barItemTextColor; textAttrs[NSFontAttributeName] = self.barItemTextFont; [barItem setTitleTextAttributes:textAttrs forState:UIControlStateNormal];
-
3、使用注意
- 在通过appearance设置属性的时候,并不会生成实例,立即赋值,而需要视图被加到视图tree中的时候才会在实例生效。
- 所以使用 UIAppearance 只有在视图添加到 window 时才会生效,对于已经在 window 中的视图并不会生效。
- 因此,对于已经在 window 里的视图,可以采用从视图里移除并再次添加回去的方法使得 UIAppearance 的设置生效。
- 没法让已生成的实例立即生效:
- (void)btn_Tap:(UIButton *)gc_btn {
[[UIButton appearance] setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
}
- 可以让已生成的实例立即生效:
- (void)btn_Tap:(UIButton *)gc_btn {
[[UIButton appearance] setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[gc_btn removeFromSuperview];
[self.view addSubview:gc_btn];
}
- 一个实现 UIAppearance 协议的类,都会有一个 _UIApperance 实例,保存着这个类通过 appearance 设置属性的 invocations,在该类被添加或应用到视图树上的时候,它会检查并调用这些属性设置。
- 这样就实现了让所有该类的实例都自动统一属性。appearance 只是起到一个代理作用,在特定的时机,让代理替所有实例做同样的事。