美文网首页
[写]UITableView之分割

[写]UITableView之分割

作者: iOS_Happy70 | 来源:发表于2017-03-18 14:38 被阅读157次

    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高度自适应的文章,欢迎关注!

    相关文章

      网友评论

          本文标题:[写]UITableView之分割

          本文链接:https://www.haomeiwen.com/subject/iosunttx.html