[写]UITableView之分割

UITableView是开发中经常用到的控制之一,但是每次实现起来都是大同小异。特别是UITableViewDelegateUITableViewDataSource 这两个协议基本每次都是粘贴复制。再者加上实现加载数据,下拉刷新以及上拉加载更多等逻辑代码,那么在控制器中的代码量就会很大,对于后期的维护和后来者看代码加大了难度。其实可以把UITableView模块化。

人狠话不多,直接来:

首先我们来看UITableViewDataSource ,常用的代理方法如下:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section

前面两个方法是代理中必须实现的,后面的是经常用到的。

分割数据源

考虑到代理中必须返回row以及cell。然后这里我们还要考虑到多个section的情况,所以这里数据源需要是一个二维数组,数组装的是sectionModel

@interface SLTableViewSectionModel : NSObject

/// UITableDataSource 协议中的 titleForHeaderInSection 方法可能会用到
@property (nonatomic, copy) NSString *headerTitle;

/// UITableDataSource 协议中的 titleForFooterInSection 方法可能会用到
@property (nonatomic, copy) NSString *footerTitle;

/// 数据model数组
@property (nonatomic, strong) NSMutableArray *listModels;

- (instancetype)initWithModelArray:(NSMutableArray *)listModels;

@end

看到这个对象里面有一个listModels,它是一个数组,里面是每行cell对应的model数据,所以我们还需要一个基类BaseListModel

@interface SLBaseListModel : NSObject
//子类需要添加数据model属性
///cell 高度
@property (nonatomic, assign) CGFloat cellHeight;

///初始化model 需要在子类重写
- (instancetype)initWithData:(NSDictionary *)data;

@end

创建cell数据model的时候,需要继承上面的类,这样方面后面通过model类型返回对应的cell class

数据类型被统一了,那么我们就可以创建一个UITableViewDataSource基类:

///cell block 用于传递cell的按钮点击事件
typedef void (^SLTableViewCellBlock)(id cell, id item);


@protocol LslTableVDataSource <UITableViewDataSource>

@optional

//方便tableview delegate 调用以下方法 和 子类重写
- (SLBaseListModel *)tableView:(UITableView *)tableView objectForRowAtIndexPath:(NSIndexPath *)indexPath;

- (Class)tableView:(UITableView *)tableView cellClassForObject:(SLBaseListModel *)model;

@end

@interface SLTableViewDataSouce : NSObject
<
LslTableVDataSource
>

///section 二维数组
@property (nonatomic, strong) NSMutableArray    *sections;

///cell block
@property (nonatomic, copy) SLTableViewCellBlock CellBlock;


- (instancetype)initWithCellBlock:(SLTableViewCellBlock)cBlock;

- (void)clearAllModel;

///普通table 一个section 添加数据listModel
- (void)nomalAppendModel:(NSArray *)models;

///多个section 组合好了的数据model  直接赋值给sections

我们可以直接在基类里面实现UITableViewDataSource的方法:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.sections ? [self.sections count] : 0;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section < [self.sections count]) {
        SLTableViewSectionModel *sectionModel = self.sections[section];
        return [sectionModel.listModels count];
    }
    return 0;
}

下面我们再看看返回cell的实现方法:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    SLBaseListModel *listModel = [self tableView:tableView objectForRowAtIndexPath:indexPath];
    
    Class class = [self tableView:tableView cellClassForObject:listModel];
    
    NSString *className = [NSString stringWithUTF8String:class_getName(class)];
    SLBaseTableViewCell *cell = (SLBaseTableViewCell *)[tableView dequeueReusableCellWithIdentifier:className];
    if (!cell) {
        cell = [[class alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:className];
    }
    
    //初始化cell数据
    [cell initWithData:listModel];
    
    
    if (self.CellBlock) {
        self.CellBlock(cell,listModel);
    }
    return cell;
}

上面的实现主要是通过indexPath找到对应的listModel,然后通过listModel找到对应的cell。就是方法:

- (SLBaseListModel *)tableView:(UITableView *)tableView objectForRowAtIndexPath:(NSIndexPath *)indexPath;

- (Class)tableView:(UITableView *)tableView cellClassForObject:(SLBaseListModel *)model;

可以看到这两个方法是写在protocol里面的,并且继承自UITableViewDataSource,这样是为什么呢?- (instancetype)initWithCellBlock:(SLTableViewCellBlock)cBlock这个方法又是干什么的呢?后面会讲,继续往下看。

分割代理

代理里面最重要的也就是返回cell高度:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath ;

首先我们首先需要创建个controller:

@interface SLBaseTableViewController : UIViewController
<
UITableViewDelegate
>

/// UITableView
@property (nonatomic, strong) UITableView       *baseTableView;

/// UITableViewStyle 默认 UITableViewStylePlain
@property (nonatomic, assign) UITableViewStyle  tableViewStyle;

/// 分割线 默认 UITableViewCellSeparatorStyleSingleLine
@property (nonatomic, assign) UITableViewCellSeparatorStyle tableViewCellSeparatorStyle;

/// 背景色 默认白色
@property (nonatomic, strong) UIColor           *tableViewBackgroundColor;

/// header 默认nil
@property (nonatomic, strong) UIView            *headerView;

/// footer 默认nil
@property (nonatomic, strong) UIView            *footerView;

///SLTableViewDataSouce
@property (nonatomic, strong) SLTableViewDataSouce   *dataSource;

/// 加载更多view
@property (nonatomic, strong) SLTableLoadMoreView    *loadMoreView;

/// 是否拥有下拉刷新 默认 NO
@property (nonatomic, assign) BOOL               bNeedRefreshAction;

/// 是否拥有上拉加载更多 默认 NO
@property (nonatomic, assign) BOOL               bNeedLoadMoreAction;

/// 是否刷新加载数据 默认 NO
@property (nonatomic, assign) BOOL               bRefresh;


/// 设置table
- (void)createTableView;

/// 设置TableDatasource
- (void)initTableDatasource;

/// 初始化下拉刷新
- (void)initMJRefresh;

/// 初始化上拉加载
- (void)initLoadMore;

/// 刷新加载数据
- (void)beginRefresh;

/// 加载更多数据
- (void)loadMoreData;

这个类里面添加了tableView,属性以及刷新、加载更多方法。我们还是来一步一步的看实现吧:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    //获取table datasource
    id<LslTableVDataSource> dataSource = (id<LslTableVDataSource>)tableView.dataSource;
    
    SLBaseListModel *listModel = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
    Class cls = [dataSource tableView:tableView cellClassForObject:listModel];
    
    if (listModel.cellHeight == 0.0f) { // 没有高度缓存
        listModel.cellHeight = [cls cellHeight:listModel];
    }
    return listModel.cellHeight;
}

这个方法实现通过datasource调用到方法,找到对应的cell 返回高度。这里把cell的高度计算放到了cell中,这样可以减少控制器的代码量以及优化tableView

cell点击:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    id<LslTableVDataSource> dataSource = (id<LslTableVDataSource>)tableView.dataSource;
    
    SLBaseListModel *listModel = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
    
    [self clickedTableCell:listModel];
    
}

看看- (instancetype)initWithCellBlock:(SLTableViewCellBlock)cBlock这个方法是干什么的:

    __weak typeof(self) weakSelf = self;
    SLTableViewCellBlock cellBlock = ^(DemoTableViewCell *cell, DemoListModel *model) {
        //可以遍历cell上的按钮 将按钮事件传递到控制器
        [weakSelf forCellBtn:cell];
        
    };

- (void)forCellBtn:(DemoTableViewCell *)cell {
    for (UIView *view in cell.contentView.subviews ) {
        if ([view isKindOfClass:[UIButton class]]) {
            UIButton *btn = (UIButton *)view;
            [btn addTarget:self action:@selector(clickedCellBtn:) forControlEvents:UIControlEventTouchUpInside];
        }
    }
}

- (void)clickedCellBtn:(UIButton *)btn {
    //处理点击事件
    NSLog(@">>>clicked cell btn");
}

当然最重要的是:

self.baseTableView.dataSource = self.dataSource;
分割网络加载

我们先搞一个基类:

@protocol ListRequestDelegate <NSObject>

///请求数据成功
- (void)requestDidSuccess:(NSArray *)listModels loadMore:(BOOL)bHaveMoreData;

///请求失败
- (void)requestDidFail:(NSDictionary *)error;

@end

@interface SLBaseTableListRequest : SLBaseRequest

///接口路径
@property (nonatomic, copy) NSString    *dataPath;

///页数 默认1
@property (nonatomic, assign) NSUInteger currentPage;

///每页条数 默认10
@property (nonatomic, assign) NSUInteger rows;

///请求代理
@property (nonatomic, weak) id<ListRequestDelegate> delegate;


///加载数据
- (void)loadData:(BOOL)bRefresh;


@end

.m主要实现

- (void)loadData:(BOOL)bRefresh {
    [super loadData:bRefresh];
    if (bRefresh) {
        self.currentPage = 1;
    } else {
        self.currentPage ++;
    }
    
    [self loadData];
}

- (void)loadData {
    
    __weak typeof (self) weakSelf = self;
    
    [AFNetHttpManager postWithUrl:[self requestUrl] params:[self requestAllArgument] success:^(id result) {
        
        [weakSelf dicToModel:result[@"tngou"]];
        
    } fail:^(NSDictionary *errorInfo) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(requestDidFail:)]) {
            [self.delegate requestDidFail:errorInfo];
        }
    }];
}

- (void)dicToModel:(NSArray *)list {
    NSMutableArray *listModel = [[NSMutableArray alloc] init];
    for (NSDictionary *dic in list) {
        DemoListModel *model = [[DemoListModel alloc] initWithData:dic];
        [listModel addObject:model];
    }
    
    BOOL bMore = NO;
    if ([list count] == self.rows) {
        bMore = YES;
    }
    
    if (self.delegate && [self.delegate respondsToSelector:@selector(requestDidSuccess:loadMore:)]) {
        [self.delegate requestDidSuccess:listModel loadMore:bMore];
    }
    
}

这样在控制器中只需要实现代理:

- (void)requestDidSuccess:(NSArray *)listModels loadMore:(BOOL)bHaveMoreData
- (void)requestDidFail:(NSString *)error 

就OK了。
吃个🌰:

- (void)beginRefresh {
    self.bRefresh = YES;
    [self.loadMoreView stopLoadMore];
    [self.listRequst loadData:self.bRefresh];
}

- (void)loadMoreData {
    self.bRefresh = NO;
    [self.loadMoreView startLoadMore];
    [self.baseTableView.mj_header endRefreshing];
    [self.listRequst loadData:self.bRefresh];
}
- (void)requestDidSuccess:(NSArray *)listModels loadMore:(BOOL)bHaveMoreData {
    //请求 数据成功
    //如果是刷新,清除之前的数据
    if (self.bRefresh) {
        [self.dataSource clearAllModel];
        [self.baseTableView.mj_header endRefreshing];
        
        if (bHaveMoreData && self.bNeedLoadMoreAction) {
            //添加加载更多
            [self initLoadMore];
        } else {
            self.baseTableView.tableFooterView = [UIView new];
        }
    } else {
        //加载更多
        [self.loadMoreView stopLoadMore];
    }
    
    [self.dataSource nomalAppendModel:listModels];
    
    [self.baseTableView reloadData];
}

- (void)requestDidFail:(NSString *)error {
    
    //请求 数据失败
    [self.baseTableView.mj_header endRefreshing];
    [self.loadMoreView stopLoadMore];
}

实现了table的下拉刷新和加载更多数据,看着是不是比没分割之前的代码清爽得一逼。虽然整体的代码量并没减少多少,但是分割之后看着鼻子是鼻子,眼睛是眼睛,这样才是一个正常的人。
(其实还有很多tableView功能可以进行分割,比如:cell的编辑,也不复杂!有兴趣的老铁可以试试)

代码不分多少,只分思想。

详情见 Demo:SLTableView
如果你觉得对你有用,请star!

以上仅限个人愚见,欢迎吐槽!

参考:@bestswifter 如何写好一个UITableView

后续有时间会写tableView高度自适应的文章,欢迎关注!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 概述在iOS开发中UITableView可以说是使用最广泛的控件,我们平时使用的软件中到处都可以看到它的影子,类似...
    liudhkk阅读 8,981评论 3 38
  • 序引 本系列文章将介绍iOS开发中的UITableView控件,将会分成四篇文章完整的讲述UITableView的...
    yetCode阅读 2,258评论 3 40
  • 版权声明:未经本人允许,禁止转载. 1. TableView初始化 1.UITableView有两种风格:UITa...
    萧雪痕阅读 2,904评论 2 10
  • 掌握 设置UITableView的dataSource、delegate UITableView多组数据和单组数据...
    JonesCxy阅读 1,106评论 0 2
  • 因为业务需要会经常修改一些配置文件,而后台比较简陋,没有操作记录。如果改错了会比较麻烦。 修改前先自己手动备份,然...
    观星阅读 933评论 0 0