美文网首页
UITableView代码分离以及UITableViewCell

UITableView代码分离以及UITableViewCell

作者: Randy1993 | 来源:发表于2017-12-20 16:58 被阅读39次

    我的代码分离

    先说明一个前提:这里并没有严格的MVVM或者其他的设计模式。但是也能很好的做到分离控制器的代码、表格的代理方法被复用。

    实现初衷

    1. 将业务逻辑与控制器进行分离,一些通用的逻辑都能够被复用进行自动的处理,如无数据的处理等。
    2. 表格控制器的代理事件整个项目只有一份。

    文件介绍

    BaseTableViewCellProtocol  // 声明cell、分区头尾视图进行数据配置的方法,子类cell进行覆写即能完成cell的配置
    
    BaseTableViewCell // 基类cell,所有的cell需要继承他,并用其类方法获取复用标识
    
    BaseTableViewSectionHeaderFooterView // 基类分区头尾视图,所有的自定义分区头尾视图需要继承他,并用其类方法获取复用标识
    
    BaseTableViewDisplayModel // 控制表格的具体显示,每一个对象代表一个分区,其内部的数组为cell的数据源,子类可以继承,完成自定义的样式
    
    BaseTableViewModel // 实现了表格的代理方法,其内部的数组为整个表格的数据源,即一个或多个BaseTableViewDisplayModel对象,子类可以继承,完成自定义的逻辑
    
    

    实现后的部分代码

    创建BaseTableViewModel
    @interface TestViewController1 ()
    
    /// 表格视图
    @property (nonatomic, weak) UITableView *tableView;
    /// viewModel
    @property (nonatomic, strong) TestViewModel1 *viewModel;
    
    @end
    
    @implementation TestViewController1
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 设置标题
        self.navigationItem.title = NSStringFromClass(self.class);
        
        // 创建表格
        _tableView = [CreateControlUtil tableViewWithFrame:self.view.bounds style:UITableViewStylePlain];
        [_tableView registerClass:TestTableViewCell.class forCellReuseIdentifier:[TestTableViewCell reusedIndentifire]];
        [self.view addSubview:_tableView];
        
        // 创建ViewModel
        _viewModel = [[TestViewModel1 alloc] initWithTableView:_tableView];
        
        // 设置刷新
        WeakBlock(weakSelf);
        _tableView.yt_RefreshHeaderBlock = ^{
            weakSelf.tableView.pageIndex = 0;
            
            [weakSelf.viewModel beginRequestData];
        };
        
        [_tableView yt_headerBeginRefreshing];
    }
    
    @end
    
    自行创建的ViewModel代码:
    @implementation TestViewModel1
    #pragma mark - OverLoad
    /**
      该方法覆写父类的实现,子类无需声明
    */
    - (void)beginRequestData {
        [SVProgressHUD show];
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            for (int i = 0; i < 5; i++) {
                BaseTableViewDisplayModel *displayModel = [[BaseTableViewDisplayModel alloc] init];
                
                // 设置cell,这里是固定的高度
                [displayModel.innerDatas addObjectsFromArray:@[@"Cell1", @"Cell2", @"Cell3"]];
                displayModel.cellClassName = @"TestTableViewCell";
                displayModel.cellHeight = 50;
                
                // 设置分区头
                displayModel.sectionHeaderHeight = 40.0;
                displayModel.sectionHeaderData = [NSString stringWithFormat:@"分区头%d", i];
                displayModel.sectionHeaderClassName = @"TestSectionHeaderFooterView";
                
                // 设置分区尾
                displayModel.sectionFooterHeight = 40.0;
                displayModel.sectionFooterData = [NSString stringWithFormat:@"分区尾%d", i];
                displayModel.sectionFooterClassName = @"TestSectionHeaderFooterView";
                
                [self.tableViewDatas addObject:displayModel];
            }
            
            [self.tableView doneLoadingData:5];
        });
    }
    
    @end
    
    Cell的配置
    /**
     覆写父类的方法
     */
    - (void)setCellWidthData:(id)data {
        /// 你在设计cell的时候,应该预先知道数据源的类型
        if (![data isKindOfClass:NSString.class]) return;
        
        _testLabel.text = data;
        [self setSeparatorLeftInset:20.0 rightInset:20.0];
    }
    
    也可以不创建ViewModel,使用BaseTableViewModel即可,只是把ViewModel中的逻辑放到控制器中,按照规定添加数据即可。

    使用规则

    1. 注册cell:使用基类的获取复用标识的类方法进行初始化。
    2. 初始化ViewModel:使用TableView进行初始化(虽然不太想ViewModel引用UI层的内容)。
    3. 创建一个displayModel对象:一个分区一个对象,并添加至ViewModel的tableViewDatas数组中。
    4. 设置cell:设置高度(分为固定和非固定)、设置类名、设置数据源(displayModel的innerDatas数组)。
    5. 设置分区头分区尾:设置高度、设置类名、设置数据源。

    Cell的高度问题

    首先一个小小的建议:label的高度完全可以固定为fontSize - 1,当然对于fontSize实时变化的就另当别论了。

    cell的高度一般都是可以固定的,直接给displayModel的cellHeight赋值即可,如果是动态高度需要将高度计算好并添加到displayModel的cellHeights数组当中去。

    强烈建议使用XIB布局cell,这种方式布局cell比较简单方便,最主要的是可以通过约束完成cell高度的自动计算。

    Cell高度的自动计算

    你的cell必须使用约束进行布局,并进行如下设置:

    tableView.estimatedRowHeight = 85.0; // 这是一个预估值,预估值越精确效果越好
    tableView.rowHeight = UITableViewAutomaticDimension;
    

    添加约束需要注意的点:
    1、如果写了表格获取cell高度的代理方法,返回一个负数是最好的(亲测),如果返回正数可能导致高度计算不准确。
    2、为了系统能够正确的计算出cell的高度,你必须设置连续的约束让cell被完全填充,并且子视图距离cell上下左右的边距都需要被指定。

    如何完成如下两个例子中cell高度的计算呢:
    例1.png
    例2.png
    例1:

    1、为ImageView添加好约束:上边距、居中、宽、高。
    2、为Name添加好约束:上边距、居中、宽、高。
    3、为Label添加好约束:上边距、下边距、左右边距。 高度由系统计算

    至此完成了约束,距离cell上下左右的边距都已经被指定。

    例2:

    1、为Label1添加好约束:上边距、左右边距。高度由系统计算
    2、为Label2添加好约束:上边距、左右边距。高度由系统计算
    2、为Label3添加好约束:上下边距、左右边距。高度由系统计算

    但是走到了这里会发现XIB直接报错了:


    错误.png

    这里涉及到另外两个概念:intrinsic ContentCHCR。可以直接看我的总结。
    另外贴出两篇文章:官方介绍实践教程。虽然有了这两篇文章,但可能还是不知道怎么用。

    对于具有intrinsic Content属性的视图在添加约束的时候,可以只设置point而不用设置size,系统会自动根据intrinsic Content进行size的计算。比如UILabel的intrinsic Content根据文本进行计算、UIButton的根据标题文本进行计算、UIImageView的根据image的尺寸进行计算。所以上面大部分的label我都没有添加高度的约束。
    如果UILabel要根据文本自动进行宽高的计算,那么这个过程中势必会进行label的拉伸或者压缩,而且拉伸和压缩又分为水平方向和垂直方向。每一个视图都具备Content-Hugging-Priority和Compression-Resistance-Priority(CHCR)属性决定着它本身被拉伸、压缩的优先级。
    Content-Hugging为了阻止视图被拉伸,值越大则会越晚被拉伸。Compression-Resistance为了阻止视图被压缩,值越大则会越晚被压缩。这两个值可以直接在XIB中进行设置。
    那么问题来了,如果存在多个需要被拉伸、压缩的视图,那么这视图之间就存在竞争关系,系统必须知道哪个视图先被拉伸压缩、哪个视图后被拉伸压缩。

    回到例子当中来,例子1并不存在竞争关系,所以在设置完约束后并不报错,而例子2中的3个label之间存在竞争关系。例子2中的3个label均需要在垂直方向上做拉伸,那么必须明确他们之间的优先级,理想的拉伸顺序是Label1、Label2、Label3。
    在XIB中可以看到如下面板:


    image.png

    我们现在只需要修改Content-Hugging-Priority-Vertical的值来控制拉伸顺序就好,例如将3个label的该值分别设置为200、220、230。运行程序,发现完美的解决了高度问题。 但是告诉大家一个不幸的消息是:通过控制CHCR在iOS9以前是行不通的,但是即便这样,当你明白了其中的原理,我相信cell的高度计算早已不是问题。

    Demo

    关于Demo,如果有需要,我会给出。

    相关文章

      网友评论

          本文标题:UITableView代码分离以及UITableViewCell

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