一个完善的iOS敏捷开发框架 — ZZFLEX

847 阅读8分钟

ZZFLEX是一个基于UIKit/Foundation开发的iOS敏捷开发框架,包含常用控件的链式API拓展和一个命令式列表控制器,使用“模块化”的设计思路,致力于大大减小UI开发和维护的难度和工作量。

如何使用

1、直接导入(可体验DEMO):

Github: github.com/tbl00c/ZZFL…

2、CocoaPods方式:

pod 'ZZFLEX'

功能模块

一、Foundation相关拓展

目前有NSMutableAttributedString和NSMutableParagraphStyle的链式API拓展,使用方法如下:

/// 创建style
NSMutableParagraphStyle *style = NSMutableParagraphStyle.zz_create.lineSpacing(5).paragraphSpacing(20).alignment(NSTextAlignmentCenter).lineBreakMode(NSLineBreakByWordWrapping).object;
/// 编辑style
style.zz_setup.headIndent(20).tailIndent(20);

/// 创建富文本
NSMutableAttributedString *attrStr = NSMutableAttributedString.zz_create(@"富文本字符串").backgroundColor([UIColor redColor]).font([UIFont systemFontOfSize:32]).foregroundColor([UIColor redColor]).object;
/// 编辑富文本(拼接图片、字符串、富文本等)
attrStr.zz_setup.paragraphStyle(style).appendString(@"拼接字符串").appendImage([UIImage imageNamed:@"要拼接的图片名称.png"], CGRectMake(0, 0, 20, 20));
/// 设置富文本属性(带range)
attrStr.zz_setup.fontWithRange([UIFont boldSystemFontOfSize:12], NSMakeRange(0, 10)).foregroundColorWithRange([UIColor blueColor], NSMakeRange(0, 10));

二、UIKit普通控件拓展

2.1 视图控件链式API拓展:

UIView+ZZFLEX为UIKit中的常用控件增加了链式API的拓展,引入它后,我们可以直接为view addButtonaddLabeladdImageView等。然后通过链式API,可更加连贯快捷的进行控件的属性设置、Masonry布局和事件处理。

以给视图添加button为例说明:

// 添加视图时设置tag,便于调试定位
UIButton *button = self.view.addButton(1)
.title(@"Hello").titleHL(@"world")
// 设置字体,各状态字色、背景色
.titleFont([UIFont systemFontOfSize:12])
.titleColor([UIColor blackColor]).titleColorHL([UIColor redColor]).titleColorDisabled([UIColor grayColor])
.backgroundColor([UIColor whiteColor]).backgroundColorHL([UIColor yellowColor])
// 设置圆角、阴影
.cornerRadius(5.0f).shadow(CGSizeZero, 5.0f, [UIColor grayColor], 1.0)
// 设置事件
.eventTouchDown(^ (UIButton *sender) {
    NSLog(@"touch down");
})
.eventTouchUpInside(^ (UIButton *sender) {
    NSLog(@"touch inside");
})
// 设置约束(Masonry)
.masonry(^ (UIButton *sender, MASConstraintMaker *make) {
    make.left.top.mas_equalTo(0);
    make.size.mas_equalTo(CGSizeMake(80, 30));
})
.view;

可以看出,链式API十分的简洁高效,在大大缩减代码行数的同时、提高了代码的可读性。它使得同一控件的代码逻辑得以集中,我们称之为“控件的模块化”。

UIView+ZZFLEX是使用***Objective-C的泛型***实现的,可以无视继承关系、随意顺序设置控件属性。

如需对控件的属性进行编辑,可以这样写:

button.zz_setup.title(@"hello").titleFont([UIFont boldSystemFontOfSize:32]);

如需单独创建一个控件,不添加到视图上:

UIButton *button = UIButton.zz_create(2).title(@"world").titleFont([UIFont boldSystemFontOfSize:32]).view;

目前,UIView+ZZFLEX已添加链式API的控件有:

UIView, UIImageView, UILabel, UIControl, UITextField, UIButton, UISwitch, UIScrollView, UITextView, UITableView, UICollectionView

2.2 分割线、圆角链式API拓展

/*
 添加分割线
 type:支持top、bottom、left、right、centerX、centerY,同一view可执行多个addSeparatorz操作
 beginAt:开始位置偏移,默认0
 endAt: 结束位置偏移,默认0;也可设置length属性代替
 offset: 具type类型偏移,默认0(-10为距离底端10)
 */
self.view.addSeparator(ZZSeparatorPositionTop).color([UIColor grayColor]).beginAt(15).endAt(-15).offset(-10);
self.view.removeSeparator(ZZSeparatorPositionTop);

/*
 设置圆角
 cornor: 支持topLeft、topRight、bottomLeft、bottomRight、top、bottom、left、right、all,支持逻辑或
 */
self.view.setCornor(ZZCornerPositionLeft | ZZCornerPositionTop).radius(5.0f).color([UIColor grayColor]).borderWidth(1);
self.view.removeCornor();

三、命令式的列表控制器 · ZZFLEXAngel

ZZFLEXAngel是一个列表页控制器,支持UITableView和UICollectionView,可大幅降低复杂列表界面实现和维护的难度。

3.1 初始化Angel

其初始化原理为将tableView/collectionView(下统称为hostView)的dataSource和delegate指向angel,同时angel将弱引用hostView,代码如下:

// tableView
UITableView *tableView = self.view.addTableView(0).view;
ZZFLEXAngel *angel = [[ZZFLEXAngel alloc] initWithHostView:tableView];

// collectionView
UICollectionView *collectionView = self.view.addCollectionView(1).view;
ZZFLEXAngel *angel2 = [[ZZFLEXAngel alloc] initWithHostView:collectionView];

3.2 页面容器层(hostView)

使用她,我们通常几乎无需关心和实现hostView的各种代理方法。她的设计使得列表页的构建就如同拼图一般,只需要一件件的add需要的模块,我们想要的界面就绘制出来了,代码如下

- (void)reloadListUI
{
    // 清空所有数据
    self.clear();
    
    // 添加section 1
    {
        // section 标识
        NSInteger sectionTag = 1;
        /*
         * 添加section
         * sectionInsets: section边距
         * minimumLineSpacing: 行间距
         * minimumInteritemSpacing: 列间距
         */
        self.angel.addSection(sectionTag).sectionInsets(UIEdgeInsetsMake(15, 15, 15, 15)).minimumLineSpacing(15).minimumInteritemSpacing(15);
        
        /// 设置section header,footer亦然
        self.angel.setHeader([UICollectionReusableView class]).toSection(sectionTag);
        
        /*
         * 往section添加cell
         * withDataModel:cell数据模型
         * selectedAction:cell点击事件
         * eventAction: cell内部事件,业务方自行设置和调用,eventType为事件类型、model是事件数据
         * viewTag: cell标识,便于调试,或用于查找定位或作为插入其他cell的锚点
         */
        self.angel.addCell([UICollectionViewCell class]).toSection(sectionTag).withDataModel(@"hello")
        .selectedAction(^ (id model) {
            NSLog(@"cell 点击事件");
        })
        .eventAction(^ id(NSInteger eventType, id model) {
            NSLog(@"cell 内部事件,类型:%ld, 模型: %@", eventType, model);
            return nil;
        })
        .viewTag(10001);
        
        /*
         * 往section批量添加cell
         * withDataModelArray:cell数据模型数组,cell个数由数组元素个数决定
         * delegate:为cell设置代理
         */
        self.angel.addCells([UICollectionViewCell class]).toSection(sectionTag).withDataModelArray(@[@"1", @"2", @"3"])
        .selectedAction(^ (id model) {
            NSLog(@"cell 点击事件,数据模型为:%@", model);
        }).delegate(self);
    }
    
    // 刷新数据
    [self.angel reloadView];
}

在ZZFLEXAngel中,我们不在使用sectionIndex/indexPath确定section/cell的位置,转而使用更唯一的sectionTag/viewTag代替(如有必要)。因为前者本质上是一个很不确定的数据、它会随着界面的变化而发生改变,很多与tableView/collectionView相关的崩溃也都与此有关。

p.s. ZZFLEXAngel仅是操作和组织列表数据源,界面更新需调用列表reload方法。

3.3 元素层

和之前不同的是,所有添加到ZZFLEXAngel中的cell、header、footer需要额外实现一个协议—ZZFlexibleLayoutViewProtocol用于跟Angel交互:

/**
 * 所有要加入ZZFLEXAngel、ZZFLEX*ViewController的view/cell都要实现此协议
 *
 * 除获取大小/高度两个方法需要二选一之外,其余都可按需选择实现
 */

@protocol ZZFlexibleLayoutViewProtocol <NSObject>

@optional;
/**
 * 获取cell/view大小,与viewHeightByDataModel二选一
 * 仅 CollectionView 可选择使用
 *
 * 调用时机:添加到ZZFlexibleLayoutViewController或ZZFLEXAngel时,如实现仅调用一次后,大小会缓存在ViewModel中。
 * 其他:如需更新视图大小,需手动调用update方法
 * 小Tips:0至-1表示比例,如size.width=-0.5时,表示视图的宽度为列表宽度的50%
 */
+ (CGSize)viewSizeByDataModel:(id)dataModel;
/**
 * 获取cell/view高度,与viewSizeByDataModel二选一
 * CollectionView、TableView 均可选择使用
 *
 * 调用时机:添加到ZZFlexibleLayoutViewController或ZZFLEXAngel时,如实现仅调用一次后,高度会缓存在ViewModel中。
 * 其他:如需更新视图高度,需手动调用update方法
 * 小Tips:CollectionView也可用此方法,宽度默认为-1,即列表宽度
 */
+ (CGFloat)viewHeightByDataModel:(id)dataModel;

/**
 * 设置cell/view的数据源
 *
 * 调用时机:cellForRowAtIndexPath或者cellForItemAtIndexPath,如实现每次都会调用
 * 小Tips:如果模型未变化时不需要更新UI,建议在此方法执行时做判断直接return
 */
- (void)setViewDataModel:(id)dataModel;

/**
 * 设置cell/view的delegate对象
 *
 * 调用时机:cellForRowAtIndexPath或者cellForItemAtIndexPath,如实现每次都会调用
 */
- (void)setViewDelegate:(id)delegate;

/**
 * 设置cell/view的actionBlock
 *
 * 调用时机:cellForRowAtIndexPath或者cellForItemAtIndexPath,如实现每次都会调用
 */
- (void)setViewEventAction:(id (^)(NSInteger actionType, id data))eventAction;

/**
 * 当前视图的indexPath,所在section元素数(目前仅cell调用)
 *
 * 调用时机:cellForRowAtIndexPath或者cellForItemAtIndexPath,如实现每次都会调用
 * 小Tips:可用于UI差异化设置等,不建议cell持有indexPath,因为可能会经常变
 */
- (void)onViewPositionUpdatedWithIndexPath:(NSIndexPath *)indexPath sectionItemCount:(NSInteger)count;

@end

cell/view实现这个协议的目的和好处有两个:

1、框架层得以统一处理collectionView与cell/header/footer的交互;

2、方便进行整体的性能优化,如缓存view/header/footter计算大小的方法的数据。

3.4 继承ZZFLEXAngel

如果ZZFLEXAngel提供的API尚不足以使用或容器层逻辑较为复杂,可以继承ZZFLEXAngel实现自己的Angel,在子类中可以直接重构/实现hostView的代理方法,如有super记得调用super,否则链式API可能失效。

3.5 其它列表控制API

- (void)reloadListUI
{
    // 清空所有数据
    self.clear();
    
    // 高级用法
    {
        NSInteger sectionTag = 2;
        
        self.angel.addSection(sectionTag);
        /*
         * 往section添加cell
         * configAction: cell手动配置方法(切面方法,调用时机cellForRowAtIndexPath或者cellForItemAtIndexPath)
         * viewSize:直接指定cell大小
         */
        self.angel.addCell([UICollectionViewCell class]).toSection(sectionTag)
        .configAction(^ (UICollectionViewCell *cell, id model){
            NSLog(@"自定义配置方法");
            [cell setBackgrounColor:[UIColor redColor]];
        })
        .viewSize(CGSizeMake(100, 100));
    }
    
    // 拓展用法
    {
        // 更改section配置
        self.sectionForTag(1).sectionInsets(UIEdgeInsetsMake(10, 0, 0, 0)).minimumInteritemSpacing(10);
        
        // 插入cell
        self.angel.insertCell([UICollectionViewCell class]).toSection(1).beforeCell(10001);
        self.angel.insertCell([UICollectionViewCell class]).toSection(1).afterCell(10001);
        self.angel.insertCells([UICollectionViewCell class]).toSection(1).toIndex(0).withDataModelArray(@[@"1", @"2"]);
        
        // 删除cell
        self.angel.deleteCell.byViewTag(10001);
        self.angel.deleteCell.byDataModel(@"1");
        
        // 更新cell,通常用于触发cell高度重新计算(默认高度仅计算一次,之后使用缓存)
        self.angel.updateCell.byViewTag(10001);
        
        // section操作,items表示cell+header+footer
        self.angel.sectionForTag(1).clearCells();
        self.angel.sectionForTag(1).clearItems();
        NSMutableArray *data = self.sectionForTag(1).dataModelArray;
      
      	// 获取section信息
      	NSInteget sectionIndex = self.angel.sectionForTag(1).index;
      	NSArray *sectionDataModelArry = self.angel.sectionForTag(sectionType).dataModelArray;
    }
    
    // 刷新数据
    [self.angel reloadView];
}

3.6 其它应用

  • Cell模板库:ZZFLEXAngel是的cell得以模块化,因此可将一些通用cell抽为公用模板;
  • 页面动态化:通过下发页面配置文件,配合业务方cell模板实现;

ZZFLEXAngel目前主要支持的功能汇总:

添加 插入 获取 批量添加 批量插入 批量获取 编辑 删除 清空子数据 更新高度
section ✔️ ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
cell ✔️ ✔️ ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
header/footer ✔️ ✔️ ✔️ ✔️

四、ZZFLEXTableViewController和ZZFLEXCollectionViewController

ZZFLEXTableViewViewController基于UITableView和ZZFLEXAngel的VC级实现;

ZZFLEXCollectionViewController基于UICollectionView和ZZFLEXAngel的VC级实现;

直接继承即可,用于快速构建列表页。

在vc子类中可直接重构tableView代理或collectionView代理中的方法,记得调用super(如存在),否则链式API可能失效。

五、ZZFLEXRequestQueue

一些复杂的页面中会存在多个异步数据请求(net、db等),然而同时发起的异步请求,其结果的返回顺序是不确定的,这样会导致UI展示顺序的不确定性,很多情况下这是我们不希望看到的。

ZZFLEXRequestQueue的核心思想是“将一次数据请求的过程封装成对象”,它可以保证在此业务场景下,按队列顺序加载展示UI。

详见Demo。

自动代码生成

ZZUIHelpler 已支持自动生成ZZFLEX代码

详见 github.com/tbl00c/ZZUI…