UIAppearance

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 只是起到一个代理作用,在特定的时机,让代理替所有实例做同样的事。
posted @ 2022-11-30 18:09  CH520  阅读(30)  评论(0编辑  收藏  举报