通常我们在创建UI的时候,会初始化UI对象,然后设置一大堆的属性,如果给控件添加事件,所添加的selector还要分开来写;以一个button来为例,如果一个页面内有很多的Button,每个button都添加一个selector,这样积累多了,不仅代码不美观,在维护上也很头痛。所以我们引入链式编程,实现UI创建时候,通过点语法来创建。
本篇文章代码https://github.com/zfc769956454/ChainedDemo(下载下来运行ChainedDemoUI控件链式创建这个target)
1. 链式编程
说到链式编程,做过iOS开发的都知道,我们在做UI布局使用的masonry,或者SDAutoLayout都是使用了链式编程的思想。其核心思想就是某个对象A,里面有很多的函数,每个函数的返回值是个block,block的返回值还是该对象A。
2. UI链式创建
接下来就是本文的核心,先上一段代码(以button为例),然后我们再分析实现过程
UIButton *button = [UIButton ZFC_ButtonChainedCreater:^(ZFC_ButtonChainedCreater *creater) {
creater.backgroundColor([UIColor cyanColor])
.tag(3)
.frame(CGRectMake(getCenterX(100), CGRectGetMaxY(imageView.frame) + 20, 100, 40))
.titleLabelFont([UIFont systemFontOfSize:15])
.title(@"未选中按钮", UIControlStateNormal)
.title(@"未选中按钮", UIControlStateSelected)
.titleColor([UIColor lightGrayColor], UIControlStateNormal)
.titleColor([UIColor redColor], UIControlStateSelected)
.layerCornerRadius(5)
.layerBorderWidthAndBorderColor(1, [UIColor blueColor])
.addIntoView(self.view)
.actionBlock(^(UIButton *button) {
button.selected = !button.selected;
NSLog(@"-------点击了button--------");
});
}];
在这段代码中,我们可以看到,一个button的创建全是.语法出来的,而且包含了button常用的属性比如frame、title、backgroundColor等等,而且button的点击事件也是和button的创建在一块,看起来不仅简洁,而且维护起来很方便。
3. 过程分析
基于以上代码,我们来分析一下。
3.1 我们先从.语法开始分析,首先creater.backgroundColor([UIColor cyanColor]),create是一个对象,通过.语法调用,那么可以确定backgroundColor是其属性,属性调用.语法要么是setter方法、要么是getter方法,此处就是getter方法
3.2 backgroundColor([UIColor cyanColor])后面是一个()调用,[UIColor cyanColor]是个实参,()调用的我们常用的是一个函数,另外一个就是block,上一步我们已经知道了backgroundColor是一个属性的getter方法,我们知道普通的属性(比如name是named)的getter方法是没有参数的,所以我们排除了它是一个普通的属性,那么它就是一个block作为对象的属性
3.3 在backgroundColor([UIColor cyanColor])后面我们可以看到它又调用了一个.tag(3),说明backgroundColor([UIColor cyanColor])的返回值还是一个creater对象
3.4 通过上面的分析我们可以很清晰的看出backgroundColor是一个block,参数是UIColor *类型,返回值是create对象,这不正是链式编程的核心思想嘛。
3.5 我们来看看源码中的定义
@property (nonatomic,copy,readonly)ZFC_ButtonChainedCreater *(^backgroundColor)(UIColor *backgroundColor);
基于以上分析,我们都可以将button常用的属性通过这种方式实现
4. 难点
基于上面的过程,我们可以把button的常用属性通过链式编程的方式很快的写出来,但是button的点击事件我们怎么把它和链式编程联系起来呢?
4.1 我们找到切入点,链式编程嘛,那么我们还是仿造上面的方式写出一个actionBlock的属性
@property (nonatomic,copy,readonly)ZFC_ButtonChainedCreater *(^actionBlock)();
4.2 上面一步我们已经设置了button的block,但是我们的真实目的是将button的事件也转成链式调用,不要再通过addTarget的方式,将selector和button分开。其实通过我们日常开发的经验来说,button的target就是一个函数,说到函数我们完全可以用block来代替(block的本质就是一个函数),所有我们就传递一个block作为actionBlock的参数,所以变成下面的
@property (nonatomic,copy,readonly)ZFC_ButtonChainedCreater *(^actionBlock)(ZFC_CreaterButtonActionBlock actionBlock)
ZFC_CreaterButtonActionBlock是我定义的一个typedef,如下
typedef void(^ZFC_CreaterButtonActionBlock)(UIButton *button);
4.3 基本工作已经做完了,接下来就是我们怎么把button的点击事件转化成actionBlock了,这里我通过在创建button的时候,调用一个函数,函数的参数是一个blockA,然后在函数内部为button添加target-selector,并通过runtime的方式关联这个blockA,在button的target方法里面执行blockA,这样就把button的target事件传递到了button创建的地方,然后在这个地方调用actionBlock(),这样就把事件通过block的方式传递出去,主要代码
- (UIButton *)chainedButton {
if(_chainedButton == nil){
_chainedButton = [ZFC_CusNoHightedButton new];
//执行函数 ->参数是个blockA
[_chainedButton creater_actionBlock:^ (UIButton *button){
//blockA的回调
if(self.keepActionBlock){ //keepActionBlock就是上面的actionBlock
self.keepActionBlock(button);
}
} controlEvent:UIControlEventTouchUpInside];
}
return _chainedButton ;
}
- (ZFC_ButtonChainedCreater *(^)(ZFC_CreaterButtonActionBlock))actionBlock {
return ^ZFC_ButtonChainedCreater *(ZFC_CreaterButtonActionBlock actionBlock) {
if(actionBlock){
self.keepActionBlock = actionBlock;
}
return self;
};
}
-(void)creater_actionBlock:(ZFC_CreaterButtonActionBlock)byValueBlock controlEvent:(UIControlEvents )event {
if(byValueBlock){
objc_setAssociatedObject(self, @selector(buttonClickAction:), byValueBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
[self addTarget:self action:@selector(buttonClickAction:) forControlEvents:event];
}
- (void)buttonClickAction:(UIButton *)button {
ZFC_CreaterButtonActionBlock byValueBlock = objc_getAssociatedObject(self, _cmd);
if(byValueBlock){
byValueBlock(button);
}
}
以上就是整个button的链式创建的主要步骤,随便提一下ZFC_ButtonChainedCreater这里是给button创建了一个分类,参数就是ZFC_ButtonChainedCreater这个类,返回值是对应的UI控件的类
5. 其它UI控件的链式创建
//view
UIView *chainedView = [UIView ZFC_ViewChainedCreater:^(ZFC_ViewChainedCreater *creater) {
creater.backgroundColor([UIColor cyanColor])
.frame(CGRectMake(getCenterX(100), 100, 100, 40))
.tag(1)
.isUserTapTapGesture(YES)
.addIntoView(self.view)
.layerCornerRadius(5)
.tapBlock(^(UIView *view) {
NSLog(@"-------点击了view--------");
});
}];
//label
UILabel *label = [UILabel ZFC_LabelChainedCreater:^(ZFC_LabelChainedCreater *creater) {
creater.backgroundColor([UIColor cyanColor])
.frame(CGRectMake(getCenterX(100), CGRectGetMaxY(chainedView.frame) + 20, 100, 40))
.tag(2)
.text(@"我是文字")
.textColor([UIColor blackColor])
.font([UIFont systemFontOfSize:15])
.textAlignment(NSTextAlignmentCenter)
.numberOfLines(1)
.addIntoView(self.view)
.layerCornerRadius(5)
.tapBlock(^(UILabel *label) {
NSLog(@"-------点击了label--------");
});
}];
//imageView
UIImageView *imageView = [UIImageView ZFC_ImageViewChainedCreater:^(ZFC_ImageViewChainedCreater *creater) {
creater.backgroundColor([UIColor cyanColor])
.frame(CGRectMake(getCenterX(100), CGRectGetMaxY(label.frame) + 20, 100, 40))
.tag(2)
.image([UIImage imageNamed:@"test.jpg"])
.layerCornerRadius(5)
.addIntoView(self.view)
.tapBlock(^(UIImageView *imageView) {
NSLog(@"-------点击了imageView--------");
});
}];
//textField
UITextField *textField = [UITextField ZFC_TextFieldChainedCreater:^(ZFC_TextFieldChainedCreater *creater) {
creater.backgroundColor([UIColor cyanColor])
.tag(4)
.frame(CGRectMake(getCenterX(100), CGRectGetMaxY(button.frame) + 20, 200, 40))
.placeholder(@"请输入文字...")
.font([UIFont systemFontOfSize:15])
.keyboardType(UIKeyboardTypeDefault)
.clearButtonMode(YES)
.layerBorderWidthAndBorderColor(1, [UIColor blackColor])
.layerCornerRadius(5)
.addIntoView(self.view);
}];
//textView
[UITextView ZFC_TextViewChainedCreater:^(ZFC_TextViewChainedCreater *creater) {
creater.backgroundColor([UIColor cyanColor])
.tag(5)
.placeholder(@"请输入文字...")
.font([UIFont systemFontOfSize:15])
.layerBorderWidthAndBorderColor(1, [UIColor blackColor])
.frame(CGRectMake(getCenterX(100), CGRectGetMaxY(textField.frame) + 20, 200, 100))
.addIntoView(self.view);
}];
//tableView
self.tableView = [UITableView ZFC_TableViewChainedCreater:^(ZFC_TableViewChainedCreater *creater) {
creater.frameAndStyle(self.view.bounds, UITableViewStylePlain)
.backgroundColor([UIColor whiteColor])
.tag(1)
.separatorStyleAndColor(UITableViewCellSeparatorStyleSingleLine, [UIColor blueColor])
.separatorInset(UIEdgeInsetsMake(0, 30, 0, 30))
.rowHeight(60)
.sectionFooterHeight(40)
.sectionFooterHeight(30)
.tableHeaderView(headerView)
.tableFooterView(footerView)
.addIntoView(self.view)
;
}];
//collectionView
self.collectionView = [UICollectionView ZFC_CollectionViewChainedCreater:^(ZFC_CollectionChainedCreater *creater) {
creater.layout_minimumLineSpacing(10)
.layout_minimumInteritemSpacing(15)
.layout_itemSize(CGSizeMake(100, 100))
.layout_headerReferenceSize(CGSizeMake(50, 50))
.layout_footerReferenceSize(CGSizeMake(50, 50))
.layout_scrollDirection(UICollectionViewScrollDirectionVertical)
.layout_sectionHeadersPinToVisibleBounds(YES)
.layout_sectionHeadersPinToVisibleBounds(YES)
.frame(self.view.bounds)
.tag(2)
.backgroundColor([UIColor whiteColor])
.addIntoView(self.view);
}];
6. 总结
以上就是UI的链式创建部分,基于这种思想,对于UITableView/UICollectionView的代理方式进行提取,形成一套链式调用,详情参见:OC 链式编程第二部分 - 调用篇,而且对于复杂tableView的调用也进行了代理方法的抽取,详情参见OC 链式编程第三部分 - 复杂tableView的抽取。最后,我把这三篇文章整理了一套小工具库ZFCChainedCreater,支持pod导入pod 'ZFCChainedCreater', '~> 1.0.2',github地址:https://github.com/zfc769956454/ZFCChainedCreater