背景
在日常的业务迭代开发工作中,UI 开发占据了我们很大一部分时间,这部分工作的流程大概是:
new
各种 UI 控件- 各种 UI 控件属性赋值
addSubview:
- 添加布局约束
举个例子,我们在 Controller 的 View 上加一个 Button,一般我们的写法如下:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1. 一般写法
UIButton *btn = [[UIButton alloc] init];
btn.center = CGPointMake(self.view.center.x, self.view.center.y + 50);
[self.view addSubview:btn];
[btn setTitle:@"Click Me!" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.backgroundColor = [UIColor whiteColor];
btn.layer.cornerRadius = 20;
btn.layer.shadowOffset = CGSizeMake(0, 1);
btn.layer.shadowColor = [UIColor blackColor].CGColor;
btn.layer.shadowOpacity = 0.1;
btn.layer.shadowRadius = 1;
[btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnClick:(UIButton *)btn {
// Button Click Action
}
代码显然不够优雅, 一堆属性赋值看着让人窒息。这个时候我们通常的做法是为 UI 控件的生成写一个工厂方法,这样在生成控件的时候直接调用工厂方法来快速生成,能够在一定程度上减少一些重复性代码。代码改进之后可能长这样
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1. 改进之后的写法
UIButton *btn = [UIButton buttonInView:self.view
title:@"Click Me!"
target:self
action:@selector(btnClick:)];
btn.center = CGPointMake(self.view.center.x, self.view.center.y + 50);
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.backgroundColor = [UIColor whiteColor];
btn.layer.cornerRadius = 20;
btn.layer.shadowOffset = CGSizeMake(0, 1);
btn.layer.shadowColor = [UIColor blackColor].CGColor;
btn.layer.shadowOpacity = 0.1;
btn.layer.shadowRadius = 1;
}
- (void)btnClick:(UIButton *)btn {
// Button Click Action
}
但是工厂方法不够灵活,参数相对固定,如果有新的属性想加入到工厂方法中去,就要重新再声明一个工厂方法,如此,在写代码的时候你将会在一堆代码提示中挑花眼。有没有更优雅的方式呢?
链式语法
说到链式语法,我猜每一个 iOSer 最先想到的应该是 Masonry 这个开源库了吧,Masonry 是一个自动布局框架,对苹果原生的 NSAutoLayout
进行了封装,使得我们能够以一种更优雅的方式来写布局约束,借鉴 Masonry 这种链式语法的方式,自己封装了一个 UI 生成框架 CHUIPropertyMaker
。
先看看使用 CHUIPropertyMaker 如何一个 Button:
#import "ViewController.h"
@import CHUIPropertyMaker;
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 使用 CHUIPropertyMaker 声明
UIButton *newBtn = [[UIButton alloc] init];
[newBtn ch_makeButtonProperties:^(CHButtonPropertyMaker *make) {
make.title(@"Click Me").forState(UIControlStateNormal);
make.titleColor(UIColor.blackColor).forState(UIControlStateNormal);
make.cornerRadius(20);
make.shadow(UIColor.blackColor, 0.1, CGSizeMake(0, 1), 1);
make.superView(self.view);
make.action(@selector(btnClick:)).withTarget(self).forEvent(UIControlEventTouchUpInside);
} constrains:^(MASConstraintMaker *make) {
make.width.equalTo(@100);
make.height.equalTo(@40);
make.top.equalTo(self.timeLabel.mas_bottom).offset(12);
make.centerX.equalTo(self.view);
}];
}
- (void)btnClick:(UIButton *)btn {
// Button Click Action
}
有没有稍微优雅那么一丢丢呢?
实现原理
对于链式语法的实现,其实就是使用到了 block
。
生成控件时,最先调用
- (void)ch_makeProperties:(void (^)(CHViewPropertyMaker *))properties
constrains:(void (^)(MASConstraintMaker *))constrains;
方法,这个方法声明在 UIView 的分类 UIView+PropertyMaker.h
中,通过调用 propertys
来为 UI 控件做属性赋值,调用 constrains
来为控件做布局约束。当回调 propertys
时,传了一个 CHViewPropertyMaker
的实例对象。
@interface CHViewPropertyMaker : NSObject
@property (nonatomic, weak, readonly) UIView *view;
- (instancetype)initWithView:(UIView *)view;
/// 设置父控件
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^superView)(UIView *superView);
/// 是否响应交互
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^userInteractionEnabled)(BOOL enable);
/// 添加阴影
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^shadow)(UIColor *shadowColor, CGFloat shadowOpacity, CGSize shadowOffset, CGFloat shadowRadius);
/// 设置背景颜色
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^backgroundColor)(UIColor *backgroundColor);
/// 设置圆角
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^cornerRadius)(CGFloat cornerRadius);
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^clipToBounds)(BOOL clipToBounds);
/// 添加点击手势事件
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^tapGestureAction)(id target, SEL selector);
/// 添加长按手势事件
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^longPressGestureAction)(id target, SEL selector);
/// 设置Frame
@property (nonatomic, copy, readonly) CHViewPropertyMaker *(^frame)(CGFloat x, CGFloat y, CGFloat width, CGFloat height);
@end
CHViewPropertyMaker
的声明如上,里面有一堆 block 属性,和一个初始化方法,- (instancetype)initWithView:(UIView *)view
这个初始化方法用来绑定控件,并且持有该控件的弱引用,至于为什么是弱引用,自然是避免循环引用了。
当我们通过点语法去调用每个属性的 get 方法时,实际内部就是在对控件的对应属性赋值。
- (CHViewPropertyMaker * _Nonnull (^)(BOOL))userInteractionEnabled {
return ^CHViewPropertyMaker *(BOOL enable) {
self.view.userInteractionEnabled = enable;
return self;
};
}
结语
以上对 UIPropertyMaker 的简单使用做了说明,并且简单阐述了下实现原理,源代码可以访问 CHUIPropertyMaker
如果想集成使用可以通过 Cocoapods
来集成使用
pod 'CHUIPropertyMaker'