前言
1 - 在一些 app 中会涉及到更改外观设置的功能,最普遍的就是夜间模式和白天模式的切换,而对于外观的更改必定是一个全局的东西。这在 iOS5 以前想要实现这样的效果是比较困难的,但是 iOS5 时 Apple 推出了 UIAppearance,使得外观的自定义更加容易实现
2 - 通常某个 app 都有自己的主题外观,而在自定义导航栏的时候或许是使用到如下面的代码
[UINavigationBar appearance].barTintColor = [UIColor redColor];
appearance 是一个全局的效果,实际上它的作用就是统一外观设置
3 - 是否是所有的控件或者属性都可以这样设置?实际上能使用 appearance 的地方是在方法或者属性后面都带有一个 UI_APPEARANCE_SELECTOR 的宏,如下
1 // 属性 2 @property(nonatomic,assign) UIBarStyle barStyle UI_APPEARANCE_SELECTOR 3 // 方法 4 - (void)setTitleTextAttributes:(nullable NSDictionary<NSString *,id> *)attributes forState:(UIControlState)state NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
如何使用
1 - 如果我们自定义的视图也想要一个全局的外观设置,那么使用 UIAppearancel 来实现非常方便
2 - 代码示例
① 分别在 ViewController 和 SecondViewController 中的 touchesBegan 方法中初始化已经配置好的视图 DemoView
// - DemoView.h
#import <UIKit/UIKit.h> @interface DemoView : UIView // 两个视图:高同父视图,宽各占父视图的一半 @property (nonatomic, strong)UIView *leftView; @property (nonatomic, strong)UIView *rightView; // 修改视图外观(背景颜色) // 修改两个子视图颜色:添加 UI_APPEARANCE_SELECTOR 宏 @property (nonatomic, strong)UIColor *leftColor UI_APPEARANCE_SELECTOR; @property (nonatomic, strong)UIColor *rightColor UI_APPEARANCE_SELECTOR; @end
// - DemoView.m
1 #import "DemoView.h" 2 @implementation DemoView 3 4 - (instancetype)initWithFrame:(CGRect)frame{ 5 self = [super initWithFrame:frame]; 6 if (self) { 7 8 // 创建视图 9 self.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width/2.0, frame.size.height)]; 10 [self addSubview:self.leftView]; 11 12 self.rightView = [[UIView alloc] initWithFrame:CGRectMake(frame.size.width/2.0, 0,frame.size.width/2.0, frame.size.height)]; 13 [self addSubview:self.rightView]; 14 } 15 return self; 16 } 17 18 // 在 setter 方法中配置两个子视图的颜色 19 - (void)setLeftColor:(UIColor *)leftColor { 20 _leftColor = leftColor; 21 self.leftView.backgroundColor = _leftColor; 22 } 23 24 - (void)setRightColor:(UIColor *)rightColor { 25 _rightColor = rightColor; 26 self.rightView.backgroundColor = _rightColor; 27 } 28 29 @end
// - ViewController.m
1 #import "ViewController.h" 2 #import "SecondViewController.h" 3 #import "DemoView.h" 4 @interface ViewController() 5 6 @end 7 8 @implementation ViewController 9 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 self.view.backgroundColor = [UIColor cyanColor]; 13 14 // 全局配置 15 // 修改某一类型控件的全部实例 + (instancetype)appearance; 16 [DemoView appearance].leftColor = [UIColor redColor]; 17 [DemoView appearance].rightColor = [UIColor blueColor]; 18 19 // 下一页 20 [self createNextPageBtn]; 21 22 } 23 -(void)createNextPageBtn{ 24 25 UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; 26 [btn setTitle:@"下一页" forState:UIControlStateNormal]; 27 btn.backgroundColor = [UIColor darkGrayColor]; 28 btn.frame = CGRectMake(40, 50, SCREEN_WIDTH-80, 50); 29 [btn addTarget:self action:@selector(nextPage) forControlEvents:UIControlEventTouchUpInside]; 30 [self.view addSubview:btn]; 31 } 32 33 -(void)nextPage{ 34 35 SecondViewController *secVC = [[SecondViewController alloc] init]; 36 secVC.view.backgroundColor = [UIColor brownColor]; 37 [self.navigationController pushViewController:secVC animated:YES]; 38 39 } 40 41 // 在 touchesBegan 创建视图,注意查看效果(全局配置) 42 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 43 44 DemoView *cardView = [[DemoView alloc] initWithFrame:CGRectMake(40, 120, 200, 100)]; 45 [self.view addSubview:cardView]; 46 } 47 48 @end
// - SecondViewController.m
1 #import "SecondViewController.h" 2 #import "DemoView.h" 3 @implementation SecondViewController 4 5 - (void)viewDidLoad { 6 [super viewDidLoad]; 7 8 } 9 // 在 touchesBegan 创建视图,注意查看效果(全局配置) 10 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 11 DemoView * cardView = [[DemoView alloc] initWithFrame:CGRectMake(40, 120, 200, 100)]; 12 [self.view addSubview:cardView]; 13 } 14 15 @end
运行效果:两个视图控制器中 DemoView 的背景颜色均在全局配置实现
② 当然 UIAppearance 不仅可以修改某一类型控件的全部实例,也可以修改部分实例,开发者只需要使用正确的 API 即可,比如我们修改 ViewController 中的方法
1 // 在 touchesBegan 创建视图,注意查看效果(全局配置) 2 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 3 4 DemoView *cardView = [[DemoView alloc] initWithFrame:CGRectMake(40, 120, 200, 100)]; 5 [self.view addSubview:cardView]; 6 7 // 添加的代码:修改某一控件的部分实例 8 // 添加以下代码后:第一个界面的 DemoView背景颜色由原来的 左红右蓝 变成 左黄右蓝;而第二个界面保持原样 9 // + (instancetype)appearanceWhenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes NS_AVAILABLE_IOS(9_0); 10 [DemoView appearanceWhenContainedInInstancesOfClasses:@[[self class]]].leftColor = [UIColor yellowColor]; 11 }
剖析 UIAppearance
1 - 查看 API 发现 iOS5.0 之后提供的不仅是 UIAppearance,还有另外一个叫做 UIAppearanceContainer 的类(实际上他们都是 protocol )
1 // UIAppearanceContainer 协议并没有任何约定方法,因为它只是作为一个容器 2 // 比如 UIView 实现了 UIAppearance 中的协议,既可以获取外观代理,也可以作为外观容器 3 // 而 UIViewController 则是仅实现了 UIAppearanceContainer 协议,它本身是控制器,并不是 view。但是它作为容器,就可以为 UIView 等服务 4 5 // 事实 UIView 的容器也基本上是 UIView 或 UIViewController,基本不需要自己去实现这两个协议 6 // 对于需要支持使用 appearance 来设置的属性,在属性后增加 UI_APPEARANCE_SELECTOR 宏声明即可 7 @protocol UIAppearanceContainer <NSObject> @end 8 9 10 @protocol UIAppearance <NSObject> 11 // 返回接受外观设置的代理 12 + (instancetype)appearance; 13 14 // 当出现在某个类的出现时候才会改变 15 + (instancetype)appearanceWhenContainedIn:(nullable Class <UIAppearanceContainer>)ContainerClass, ... NS_REQUIRES_NIL_TERMINATION API_DEPRECATED_WITH_REPLACEMENT("appearanceWhenContainedInInstancesOfClasses:", ios(5.0, 9.0)) API_UNAVAILABLE(tvos); 16 17 // 针对不同 trait 下的应用的 apperance 进行简单设定 18 // 这两个 appearanceForTraitCollection方法是用于解决 Size Classes 的问题而诞生的,通过这两个 API 我们可以控制在不同屏幕尺寸下的样式 19 + (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait API_AVAILABLE(ios(8.0)); 20 + (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes API_AVAILABLE(ios(9.0)); 21 // 已经废弃的方法 22 + (instancetype)appearanceWhenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes API_AVAILABLE(ios(9.0)); 23 + (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedIn:(nullable Class <UIAppearanceContainer>)ContainerClass, ... NS_REQUIRES_NIL_TERMINATION API_DEPRECATED_WITH_REPLACEMENT("appearanceForTraitCollection:whenContainedInInstancesOfClasses:", ios(8.0, 9.0)) API_UNAVAILABLE(tvos); 24 25 @end
显然苹果的思路是让 UIAppearance 成为一个可以返回代理的协议,通过它可以把任何配置转发给特定类的实例。这样做的好处就是 UIAppearance 可以处理所有类型的UI控件:无论它是 UIView 的子类、还是包含了视图实例的非 UIView 控件
标签:UIAppearance,self,基础,视图,UIColor,UI,void,DemoView,UIView From: https://www.cnblogs.com/self-epoch/p/16783162.html