UITableView你用的烦了吗

本文旨在提供UITableView的另外一种写法,提高工作效率。
源码地址:https://github.com/wochen85/FKTableView

对做iOS开发的同学来说,写UI的过程中,最熟悉的、用的最多的也莫过于UITableView了吧,相信大家对这货的使用也已经炉火纯青:

  1. alloc 一个实例
  2. 塞到view中
  3. 指定datasource
  4. 书写datasource的协议方法,返回cell
  5. 定义一个MyTableViewCell的类,它继承自UITableViewCell

于是你写下了形如以下的代码:

- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc]initWithFrame:CGRectZero style:UITableViewStylePlain];
        _tableView.dataSource = self;
    }
    return _tableView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.tableView];
    [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(UIEdgeInsetsZero);
    }];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.dataArr.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *identifier = @"mycell";
    MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    
    if (!cell) {
        cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
    }
    
    [cell bindData:self.dataArr[indexPath.row]];
    
    return cell;
}

限于篇幅,上文没有贴出MyTableViewCell的代码,自行脑补吧。

大概经过以上几步,一个UITableView便会呈现在手机上,牛逼。

那么让我们进一步往下看,如果产品经理要求tableview的某一行可以点击触发一个子业务。你肯定会想:擦,这个难不倒我,我可是熟练工。于是你写下了如下的代码:

  • 首先,你给tableview指定了一个代理:
- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc]initWithFrame:CGRectZero style:UITableViewStylePlain];
        _tableView.dataSource = self;
        _tableView.delegate = self;
    }
    return _tableView;
}
  • 然后,写下了代理类方法如下:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    //巴拉巴拉
}

完美,打完收工。

可是产品经理还是不省心啊,大中午的,你刚打开王者荣耀,游戏还在检测更新,他又来烦你了,这次是要在tableview的每一行都加一个按钮,然后点击这个按钮可以处理相应的业务(比如订单的支付按钮?)。
这时你已经有点不爽了,但是你还是能搞定,这难不倒你。于是你关掉王者荣耀,整理了一下思路:

  1. 在MyTableViewCell里加一个button
  2. 给这个button来一个addTarget,指定action
  3. 在button的action中去做事情
  4. 3中提到的事情需要通过block回调到viewcontroller中去进行,以保证cell的业务无关性
  5. 在viewcontroller中实现cell的block,进行相关业务执行

嗯,就是这样,虽然有点烦,尤其是写那个鸟block,但是你还是写下了如下的代码:

  • 首先,你在你的MyTableViewCell中定义了一个属性:
@property (nonatomic, copy) void(^payBlock)(void);
  • 然后,在你的ViewController的cellFor函数中加了一句:
cell.payBlock = ^{
    //布拉布拉
};

限于篇幅,省略了cell里面的代码修改。

到了这里,你松了一口气,这下应该完美了吧,一看小米手环4彩屏版,靠,午休时间就剩半小时了,赶紧睡个觉。

可是万万没想到啊,产品经理又来了,他说,这些订单要分块,每一块有个标题,标题颜色还不一样,然后标题旁边还有一个操作按钮,点了这个按钮之后要对这块订单进行批量支付。

你以为这就完了?当然没有,还要求订单列表的上方要展示一个活动横幅,用于公司爆款产品的推荐,横幅里面也有一个按钮,点这个按钮,可以跳转到相应的产品页面。

相信到了这里,你心中已经有一两只草泥马在奔跑(注意文明),但是你还是强忍着怒气,又开始了整理思路:

  • 首先,要把tableview改成分组模式
  • 要添加numberOfSectionsInTableView和- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section方法,里面又要用到dequeueReusableHeaderFooterViewWithIdentifier方法等
  • 要写一个MySectionView,用作viewForHeaderInSection的返回
  • 在在MySectionView里写button,然后就是block回调给viewcontroller

这还没完,你还没整理那个横幅的思路呢,要写形如self.tableHeaderView = [MyBanner new];的代码,要处理这个view的高度,里面的按钮操作,又要写block往外面回调。

你甚至能够想到,也许产品经理还会提在某个cell后面随机插入一条广告的cell等等变态需求,你的心情为此低落到了极点。

想到这里,尽管思路还是很清晰,但是你有没有感觉,你已经不太想写这些代码了,总感觉很烦,但是又说不清楚为什么。

那么好,下面我们来梳理一下所遇到的问题。

以上是常规写法,相信很多开发者也是这么做的。但是这里存在几个问题:

  1. 要手动指定datasource和delegate
  2. viewcontroller要遵从UITableViewDataSource和UITableViewDelegate这两个协议
  3. 要实现UITableViewDataSource和UITableViewDelegate的一堆协议方法,而且每写一个列表页面,都要写这种方法,十分冗余,烦不烦?
  4. cell、sectionview、headerView中的操作要用block往外回调,我想问问喜欢写block的有几个
  5. 如果是那种数据源不同的tableview,需要临时去掉某个cell,或新增一个cell,或调整两个cell的显示顺序,你至少需要改动两个方法:- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath,如果是分组类型的tableview,你还要改- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView,如果每个cell有点击操作,你还要改- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath,呼,好多,改不好就出错了。
  6. 逻辑分层很模糊,有的人直接把业务逻辑写在cell里,你说是对是错,这个我觉得见仁见智了,也有人在cell里直接定义一个类方法,直接返回cell,像这样:
    + (instancetype) cellForTableView:(UITableView*) tableView model:(MyOrderModel*)model
  7. 如果要添加tablewview的header,需要自己去考虑它的高度,因为它看起来总不是那么听话。
  8. 一旦业务逻辑改变,我上面枚举的3~7都可能会改变

那么,如何解决这些问题呢?

答案:https://github.com/wochen85/FKTableView

前置条件:

  1. 对RAC有一丢丢了解,不了解也没关系~~
  2. 没了

Round 1 核心函数

都在UITableView+FKExtension.h这个头文件里了:

@interface UITableView (FKExtension)
-(void) fk_configRowModels:(NSArray*) rowModels;
-(void) fk_configSectionModels:(NSArray*) sectionModels;
-(void) fk_configHeader:(nullable FKViewModel*) headerModel height:(CGFloat) height;
-(void) fk_configFooter:(FKViewModel*) footerModel height:(CGFloat) height;
@end

以上函数,暂不做解释说明,后面会一步步用到。

Round 2 简单示例

假设我们要做一个设置页面,它长这样:

1563419692489.jpg

按以下步骤即可:

  1. 创建tablewview
@interface SettingViewController ()
@property (nonatomic, strong) UITableView* tablewView;
@end

- (UITableView *)tablewView
{
    if (!_tablewView)
    {
        _tablewView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
        _tablewView.tableFooterView = [UIView new];
    }
    return _tablewView;
}

注意,此处SettingViewController不需要遵从UITableViewDataSource和UITableViewDelegate这两个协议

  1. 把tablewView加入view,进行布局
- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"设置";
    [self configTableView];
}

- (void) configTableView
{
    [self.view addSubview:self.tablewView];
    [self.tablewView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(UIEdgeInsetsZero);
    }];
}

至此,和你以前的操作步骤应该相差不大。

  1. 由于列表的前3项布局一样,所以可以用同一个cell:
@interface TextSettingCell()
@property (nonatomic, strong) UILabel* txtLabel;
@end

@implementation TextSettingCell
- (UILabel *)txtLabel
{
    if (!_txtLabel)
    {
        _txtLabel = [UILabel new];
    }
    return _txtLabel;
}

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self)
    {
        [self initSubViews];
        self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    return self;
}

-(void) initSubViews
{
    [self.contentView addSubview:self.txtLabel];
    [self.txtLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(10);
        make.top.mas_equalTo(10);
        make.bottom.mas_equalTo(-10);
        make.right.mas_equalTo(-10);
    }];
}
  1. 和3差不多,创建一个switch的cell,用于夜间模式切换
@interface SwitchSettingCell()
@property (nonatomic, strong) UILabel* txtLabel;
@property (nonatomic, strong) UISwitch* switcher;
@end

@implementation SwitchSettingCell

- (UILabel *)txtLabel
{
    if (!_txtLabel)
    {
        _txtLabel = [UILabel new];
    }
    return _txtLabel;
}

- (UISwitch *)switcher
{
    if (!_switcher)
    {
        _switcher = [UISwitch new];
    }
    return _switcher;
}

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self)
    {
        [self initSubViews];
    }
    return self;
}

-(void) initSubViews
{
    [self.contentView addSubview:self.txtLabel];
    [self.contentView addSubview:self.switcher];
    [self.txtLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(10);
        make.top.mas_equalTo(10);
        make.bottom.mas_equalTo(-10);
        make.right.mas_equalTo(self.switcher.mas_left);
    }];
    
    [self.switcher mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.mas_equalTo(-10);
        make.top.mas_equalTo(10);
        make.bottom.mas_equalTo(-10);
    }];
}

到了这里,仍然还是熟悉的味道,你肯定忍不住想去ViewController里写UITableViewDataSource的协议方法了,是不是?但是你要记住,我们在第一步里并没有设置tablewView的数据源。

  1. boss登场,定义TextSettingCellModel类
@interface TextSettingCellModel : FKCellModel
@property (nonatomic, copy) NSString* txt;
- (instancetype)initWithText:(NSString*) txt;
@end

@implementation TextSettingCellModel
- (instancetype)initWithText:(NSString*) txt
{
    self = [super init];
    if (self) {
        _txt = txt;
    }
    return self;
}
@end

注意这货的命名,它只是在TextSettingCell类名后面加上“Model”字样。这是FKTableView的默认命名约定,当然,你也可以不这么写,后面会说。

  1. 同理,定义SwitchSettingCellModel类
@interface SwitchSettingCellModel : FKCellModel
@property (nonatomic, copy) NSString* txt;
@property (nonatomic) BOOL switchOn;
- (instancetype)initWithText:(NSString*) txt switchOn:(BOOL)switchOn;
@end

@implementation SwitchSettingCellModel
- (instancetype)initWithText:(NSString*) txt switchOn:(BOOL)switchOn
{
    self = [super init];
    if (self) {
        _txt = txt;
        _switchOn = switchOn;
    }
    return self;
}
@end
  1. 在TextSettingCell里处理数据绑定
  • 包含#import #import "TextSettingCellModel.h"头文件
  • 包含了FKTableView.h头文件后,敲击“FK”字样,则会联想出父类的(void)fk_bindModel:(id)model函数
  • 修改函数参数类型为TextSettingCellModel,并完成绑定数据的代码:
- (void)fk_bindModel:(TextSettingCellModel*)model
{
    self.txtLabel.text = model.txt;
}
  1. 同理,SwitchSettingCell也做数据绑定
- (void)fk_bindModel:(SwitchSettingCellModel*)model
{
    self.txtLabel.text = model.txt;
    self.switcher.on = model.switchOn;
}
  1. 现在可以来处理ViewContrller了,先包含头文件#import ,然后在原来的configTableView函数里,加入以下代码:
    //编辑资料
    TextSettingCellModel* editProfileCellModel = [[TextSettingCellModel alloc] initWithText:@"编辑资料"];
    //账号和隐私设置
    TextSettingCellModel* accountCellModel = [[TextSettingCellModel alloc] initWithText:@"账号和隐私设置"];
    //黑名单
    TextSettingCellModel* blackListCellModel = [[TextSettingCellModel alloc] initWithText:@"黑名单"];
    //夜间模式
    SwitchSettingCellModel* nightModeCellModel = [[SwitchSettingCellModel alloc] initWithText:@"夜间模式" switchOn:NO];
    
    [self.tablewView fk_configRowModels:@[editProfileCellModel, accountCellModel, blackListCellModel, nightModeCellModel]];

至此,这个页面就算完成了,为了力求讲的明细,所以步骤列的过于多,其实总结下来只有如下几步骤:

  • 创建cell
  • 创建cellModel
  • cell绑定cellModel
  • viewcontroller中进行fk_configRowModels

下面,让我们再来看看这么做的好处在哪里。

  • 不需要指定viewcontroller遵从UITableViewDataSourceUITableViewDelegate协议
  • 不需要手动指定tableview的dataSourcedelegate
  • 不需要实现UITableViewDataSource和UITableViewDelegate的一堆协议方法

那么dataSource和delegate到底是谁呢,没有这两货,tableview自己可玩不转啊,没错,就是要让它自己玩转,自己的事情自己去做去吧,具体可以看下UITableView+FKExtension.m文件。

Round 3 处理cell的点击事件

你会不会又不由自主的想到要实现- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath方法了?

现在只需要去改cellModel,来订阅它的selectedSignal信号:

    //编辑资料
    TextSettingCellModel* editProfileCellModel = [[TextSettingCellModel alloc] initWithText:@"编辑资料"];
    [editProfileCellModel.selectedSignal subscribeNext:^(FKCellModel * _Nullable x) {
        [SVProgressHUD showInfoWithStatus:@"你点击了\"编辑资料\""];
    }];
    //账号和隐私设置
    TextSettingCellModel* accountCellModel = [[TextSettingCellModel alloc] initWithText:@"账号和隐私设置"];
    [accountCellModel.selectedSignal subscribeNext:^(FKCellModel * _Nullable x) {
        [SVProgressHUD showInfoWithStatus:@"你点击了\"账号和隐私设置\""];
    }];
    //黑名单
    TextSettingCellModel* blackListCellModel = [[TextSettingCellModel alloc] initWithText:@"黑名单"];
    [blackListCellModel.selectedSignal subscribeNext:^(FKCellModel * _Nullable x) {
        [SVProgressHUD showInfoWithStatus:@"你点击了\"黑名单\""];
    }];

有没有这种感觉,cell的数据存储,和操作存储都放到了cellModel里去,其实我们的tableview要做的事情无非也就这两样:展示数据(固定的或者从网络获取的)、对用户的操作做出响应。

优点:

  • 不用再去实现- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath这个方法

Round 4 处理cell内部的控件事件

这里便以“夜间模式”这个cell的UISwitch控件的切换作为演示。

  1. 加入_switcher的事件监听,这是不可避免的
- (UISwitch *)switcher
{
    if (!_switcher)
    {
        _switcher = [UISwitch new];
        [_switcher addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
    }
    return _switcher;
}

-(void) switchChanged:(UISwitch*) sender
{
    
}

到这里你是不是又想在switchChanged这个函数里去写block了?现在不要这么做做,因为定义block毕竟是很蛋疼的事情,所以你可以像下面这样做。

  1. SwitchSettingCellModel里定义一个RACSubject
@property (nonatomic, strong) RACSubject* switchChangeSignal;

- (RACSubject*)switchChangeSignal
{
    if (!_switchChangeSignal)
    {
        _switchChangeSignal = [RACSubject subject];
    }
    return _switchChangeSignal;
}

有没有发现和前面用到的selectedSignal很像,他们的确是一样的用法,只不过selectedSignal这货是每个cellModel都可能需要支持的(毕竟大部分的cell都有可能要支持列表项点击),所以我把它放到了基类FKCellModel中去。

  1. 把cell中UISwitch的点击事件通过上面定义的switchChangeSignal信号发送出去
-(void) switchChanged:(UISwitch*) sender
{
    SwitchSettingCellModel* cellModel = (SwitchSettingCellModel*)self.fk_viewModel;
    [cellModel.switchChangeSignal sendNext:@(sender.isOn)];
}

fk_viewModel是每个UIView+FKExtension扩展的一个属性,它的类型就是你前文书写fk_bindModel函数时的参数类型。

  1. 在viewcontroller中来监听cellModel的这个switchChangeSignal信号
    //夜间模式
    SwitchSettingCellModel* nightModeCellModel = [[SwitchSettingCellModel alloc] initWithText:@"夜间模式" switchOn:NO];
    [nightModeCellModel.switchChangeSignal subscribeNext:^(NSNumber * _Nullable x) {
        [SVProgressHUD showInfoWithStatus:x.boolValue?@"夜间模式 打开":@"夜间模式 关闭"];
    }];

下面再来列举下有哪些优点:

  • 不用在cell中去定义block,而是将cell内部的事件处理抛给cellModel,cellModel再通过信号传递给viewcontroller。

Round 5 应对业务改变

  1. cell的顺序改变

比如“黑名单”要和“编辑资料”换个位置
我们先看看老式写法要做什么

首先,他要去改- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath这个函数,然后通过indexPath来判断哪一行显示哪一个,很容易出错。

其次,因为他的点击事件是在- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath这个函数里来写的,所以这里也要改,还是通过indexPath来判断哪一行被点击了,一不小心,又出错了。

那么,FKTableView怎么做呢?
你只需要更改下fk_configRowModels这个函数的参数数组顺序即可。

[self.tablewView fk_configRowModels:@[blackListCellModel, accountCellModel, editProfileCellModel, nightModeCellModel]];

是不是很爽?还不会出错,你不用去考虑点击事件,因为他已经被封装在cellModel里了,不论它被放到了tableview的哪个位置,它的点击事件都会跟着走。

  1. 删除某个cell

你可能已经想到了,和cell顺序改变没啥两样,你只需要把要删除的cell对应的cellModel从数组重删除即可,比如要删除“夜间模式”:

[self.tablewView fk_configRowModels:@[editProfileCellModel, accountCellModel, blackListCellModel]
  1. 新增一个cell

不再赘述,直接给fk_configRowModels的参数增加一个cellModel即可。

Round 6 设置section的header和footer

比如,需要把“编辑资料”和“账号和隐私设置”放入一个section,而“黑名单”和“夜间模式”放入另一个的section。长得是下面这样的:

1563434035685.jpg

你只需在viewcontroller中要把原来的

[self.tablewView fk_configRowModels:@[editProfileCellModel, accountCellModel, blackListCellModel, nightModeCellModel]];

替换成:

    FKSectionHeaderFooterConfig* basicSectionHeadConfig = [[FKSectionHeaderFooterConfig alloc] initWithHeight:40 headFooterModel:nil];
    FKSectionModel* basicSectionModel = [[FKSectionModel alloc] initWithRowModels:@[editProfileCellModel, accountCellModel] headConfig:basicSectionHeadConfig footConfig:nil];
    FKSectionHeaderFooterConfig* otherSectionHeadConfig = [[FKSectionHeaderFooterConfig alloc] initWithHeight:40 headFooterModel:nil];
    FKSectionModel* otherSectionModel = [[FKSectionModel alloc] initWithRowModels:@[blackListCellModel, nightModeCellModel] headConfig:otherSectionHeadConfig footConfig:nil];
    [self.tablewView fk_configSectionModels:@[basicSectionModel, otherSectionModel]];

搞定!
产品说,要给第一个section加个标题,叫“基本设置”,第二个section加个标题,叫“其他设置”,长得这个鬼样:

1563434709216.jpg

那么你只需要给FKSectionHeaderFooterConfig指定一下headFooterModel参数,具体如下:

    FKHeaderFooterCommonModel* basicSectionHeaderModel = [[FKHeaderFooterCommonModel alloc] initWithText:[[NSAttributedString alloc] initWithString:@"基本设置"] bgColor:[UIColor clearColor] textAlignment:NSTextAlignmentCenter];
    FKSectionHeaderFooterConfig* basicSectionHeadConfig = [[FKSectionHeaderFooterConfig alloc] initWithHeight:40 headFooterModel:basicSectionHeaderModel];
    FKSectionModel* basicSectionModel = [[FKSectionModel alloc] initWithRowModels:@[editProfileCellModel, accountCellModel] headConfig:basicSectionHeadConfig footConfig:nil];
    
    FKHeaderFooterCommonModel* otherSectionHeaderModel = [[FKHeaderFooterCommonModel alloc] initWithText:[[NSAttributedString alloc] initWithString:@"其他设置"] bgColor:[UIColor clearColor] textAlignment:NSTextAlignmentCenter];
    FKSectionHeaderFooterConfig* otherSectionHeadConfig = [[FKSectionHeaderFooterConfig alloc] initWithHeight:40 headFooterModel:otherSectionHeaderModel];
    FKSectionModel* otherSectionModel = [[FKSectionModel alloc] initWithRowModels:@[blackListCellModel, nightModeCellModel] headConfig:otherSectionHeadConfig footConfig:nil];
    
    [self.tablewView fk_configSectionModels:@[basicSectionModel, otherSectionModel]];

FKHeaderFooterCommonModel是考虑到tableview会经常用到这种只带有一个背景色和一串文本的sectionheader、sectionheader而内置到框架里的,至于其它类型的sectionheader、sectionheader可以参考FKHeaderFooterCommonModelFKHeaderFooterCommon的写法自己去自定义,另外github上工程代码也有提供样例(MyBookSectionHeadFootModel类和BookSectionHeadFoot类),这里不再赘述。

另外,FKHeaderFooterCommonModel这个类也提供了一个clickSignal来发送section的点击事件。
你可以这么用:

[basicSectionHeaderModel.clickSignal subscribeNext:^(id  _Nullable x) {
        [SVProgressHUD showInfoWithStatus:@"你点击了基本设置"];
    }];

Round 7 cellModel和cell不遵守类命名约定

前面提到过,cell与cellModel要遵守命名约定,具体来说就是cell如果是xxx,那么对应的model就应该是xxxModel(注意M大写)。
但是存在这么一种情况,你就是不想这么写,或者粗心大意写错了,那么可以在cellModel中重写一下- (NSString *)nibOrClassName这个方法,返回cell对应的nib或者class的名称。

比如,你的cell是SettingCell,而你的cellModel不小心写成了MySettingCellModel,那么只要在MySettingCellModel.m中:

- (NSString *)nibOrClassName
{
    return NSStringFromClass([SettingCell class]);
}

Round 8 设置tablewview的header和footer

使用UITableView+FKExtension分类的如下两个方法:

-(void) fk_configHeader:(nullable FKViewModel*) headerModel height:(CGFloat) height;
-(void) fk_configFooter:(FKViewModel*) footerModel height:(CGFloat) height;

这里的FKViewModel你应该很熟悉了,前面一直用的FKCellModel就是继承它的。使用基本类似,不再赘述。


总结:

FKTableView依赖FKTableCollectionExtensionBase,这是FKTableView和FKCollectionView公共功能的提取(FKCollectionView有时间我会单独写文章介绍),FKTableCollectionExtensionBase中有一个类FKViewModel,它是FKTableView赖以生存的基础,前面已经看到,我们的ViewController只跟Model打交道(CellModel、SectionHeadFootModel、TableHeadFootModel等),虽然我们也创建了对应的Cell、SectionHeadFoot、TableHeadFoot等UIView的子类,但是并没有直接去创建这些类的实例。就是基于FKViewModel这个类的nibOrClassName属性。

附注:

细心的朋友可能注意到,前面一直没有提到关于cell的高度的相关介绍,这是基于iOS自带的自动布局来计算的,也就是说在xib、storyboard或者Masonry中去布局cell时,要有一条自上而下的连贯约束,通过这条垂直的连贯约束,cell会被自动“撑高”到它合适的高度。

另外FKTableView同时支持xib和纯代码来书写Cell和View,本文只提供了纯代码的方式,xib的方式,参考工程代码,遇到问题的希望提出issue,喜欢的话麻烦给个小星星,谢谢。

最后,附上另外几个开源链接:

FKCollectionView,简化UICollection的书写逻辑

一行代码书写Restful接口调用

一行代码搞定下拉刷新组件和UIScrollView空页面展现

你可能感兴趣的:(UITableView你用的烦了吗)