美文网首页iOS视图程序员iOS Developer
iOS实录1:UITableView构建UI界面

iOS实录1:UITableView构建UI界面

作者: 南华coder | 来源:发表于2017-04-01 17:58 被阅读563次

    [这是第一篇]

    导语:UITableView是iOS项目中使用相关广泛的UI组件,本文主要从构建界面方案的实现TableViewCell之间的通信Native动态化页面的野望(挖坑)三部分讨论了如何使用TableView构建比较常见的UI界面。

    一、概述

    1、问题#####
    • 在iOS的开发中,有大量的UI工作,这些UI工作简言之就是在画“页面”,“页面”的复杂度有高有低,复杂度高的比如绘制直播画面(聊天列表、点赞飞花、送礼物、广告UI等),复杂度低的比如绘制一个关于页面,一个logo和几行文本就搞定了。

    • 而我们在实际工作中遇到的UI工作大部分是中等复杂度的页面,比如某某列表页、某某详情页,在绘制页面的同时,还需要响应页面上的操作。

    • 其实,很多页面都是可以利用UITableView来构建出来的。

    2、优势#####

    采用分而治之的办法,将一个整体页面划分成若干部分,每一个部分就相当一类Cell,其实这么做是有好处的,好处如下:

    • 提高部分UI组件的复用性。尤其在同一个App中,甚至一个App中的同一功能模块中,有些UI是相同的,是可以复用的。

    • 支持数据驱动UI的展示,在页面数据变化的情况下,UI也能发生对应的变化。

    • 在绘制同一个页面时候,有利于多人合作,每个人可以负责一个页面中不同的部分,且在开发过程中,不受其他人的工作进度影响。

    3、解决方案
    • 封装TableViewController,凡是想使用UITableView构建页面的Controller都需要继承该类。

    • 支持UI组件响应的的处理。

    • 使用通知中心或其他方案来完成Cell之间的通信。

    • 支持UITableView的上拉加载和下拉刷新,一般列表页面比较需要这两个功能。

    二、 构建界面方案的实现####

    1、QSTableViewCell 和 QSTableViewCellModel
    • QSTableViewCell是对UITableViewCell的封装,而QSTableViewCellModel是对QSTableViewCell上数据和UI响应动作的描述。

    • QSTableViewCell定义了其子类需要实现的办法,如如数据更新布局,cellHeight处理,点击cell的处理等主要方法。

       #pragma mark - QSTableViewCell
      @interface QSTableViewCell : UITableViewCell
      
      #pragma mark - 子类需重写
      - (void)layoutWithModel:(id)model;
      
      + (CGFloat)cellHeightWithModel:(id)model;
      
      - (void)onTapCellAction;
      
      @end
      
    • QSTableViewCellModel定义了对应Cell类的类名、展示需要的数据, 以及响应UI操作点击的动作,我们将响应UI操作的动作封装在Block中。

      #pragma mark - QSTableViewCellModel
      @interface QSTableViewCellModel : NSObject
      
      @property (nonatomic,copy)NSString  *cellClassName;
      @property (nonatomic,copy)QSTableViewCellActionBlock tapCellBlock;
      @property (nonatomic,assign)CGFloat cellHeight;
      @property (nonatomic,strong)id userInfo;
      
      @end
      

    说明:cellClassName属性保存对应的Cell类的类名,可以利用反射来实现cell的创建。

    2、实现UITableView代理方法

    选择在TableViewController中实现UITableView的UITableViewDelegate、UITableViewDataSource的代理方法。在TableViewController中有顶一个dataSource数组,这个数组中存放的各个Cell对应的CellModel,利用cellModel中的信息,完成Cell的绘制和响应处理。以cell的创建、cell高度获取为例。

    • 根据cellModel来实现heightForRowAtIndexPath代理

      - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
      
         QSTableViewCellModel *cellModel = [self.dataSource objectAtIndex:indexPath.section];
         return [self.cellFactory cellHeightForData:cellModel];
      }
      
    • 根据cellModel来完成cell的创建和更新

      - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
      
           QSTableViewCellModel *cellModel = [self.dataSource objectAtIndex:indexPath.section];
           QSTableViewCell *cell = [self.cellFactory cellInstanceForData:cellModel];
           [cell layoutWithModel:cellModel];
           return cell;
        }
      

      说明:在cellForRowAtIndexPath:代理方法中实现了cell的创建、复用、数据更新,添加cell上的响应动作处理。

    • QSTableViewCellFactory的职责:负责cell高度的计算、创建和复用

      //QSTableViewCellFactory的定义
      @interface QSTableViewCellFactory : NSObject
      
      - (instancetype)initWithTableView:(UITableView *)tableView;
      
      - (CGFloat)cellHeightForData:(id)data;
      
      - (QSTableViewCell *)cellInstanceForData:(id)data;
      
      @end
      

    说明:self.cellFactory就是QSTableViewCellFactory对象,达到解耦的目的,使得Controller中代码更简洁。

    3、上拉加载和下拉刷新

    利用MJRefresh来实现TableView的上拉加载和下拉刷新。

    • 设置上拉加载

      - (void)setupRefreshFooter{
      
           //上拉加载更多
          @weakify(self);
           MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
                @strongify(self);
                    [self refreshDataWithStyle:QSRefreshTableViewDataStyleLoadMore];
            }];
      
            [footer setTitle:@"已显示全部"forState:MJRefreshStateNoMoreData];
      
            footer.refreshingTitleHidden = YES;
            self.tableView.mj_footer = footer;
            [footer.stateLabel setTextColor:[UIColor lightGrayColor]];
        }
      
    • 设置下拉刷新

        - (void)setupRefreshHeader{
      
            //下拉刷新
            @weakify(self);
            MJRefreshHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
               [self refreshDataWithStyle:QSRefreshTableViewDataStylePull];
            }];
            self.tableView.mj_header = header;
        }
      
    • 结束刷新

      - (void)endRefreshingWithMoreData:(BOOL)hasMoreData{
      
          if (self.needPullRefresh) {
              [self.tableView.mj_header endRefreshing];
          }
      
          if (self.needPullToLoadMore) {
              if (hasMoreData) {
                  [self.tableView.mj_footer endRefreshing];
              }else{
                  [self.tableView.mj_footer endRefreshingWithNoMoreData];
              }
          }
      }
      

    总结: 使用UITableView构建页面,Controller继承自定义的TableViewController,使用Cell来定义视图,CellModel来定义视图上的内容和动作,然后将CellModel放入TableViewController的dataSource数组中,即可。

    三、TableViewCell之间的通信####

    在使用TableView组织页面的时候,有个非常值得考虑的问题,那就是TableViewCellCell之间的的通信。

    1、概述#####
    • 采用分而治之的思路,将一个大的视图拆分成若干个TableViewCell。而在业务上,这几个TableViewCell视图是相互关联的。如点击某子视图,另一个子视图的数据展示发生变化。这就导致了需要选择方案去实现部分TableViewCell之间的通信。

    • 早期方案是选用了通知中心

    • 通知中心不仅可以在不同的UITableViewCell之间传递数据的,还降低了代码耦合。但是这也导致了TableViewCell中大量存在注册观察,移除观察这样的代码(移除的工作还非常重要)。最关键的是,通知中心传递的参数不可以灵活地使用model对象。

    • 后期方案是QSMessageCenter,这是从业务出发,实现的通知中心的替换方案。

    • QSMessageCenter主要实现了不同对象之间的数据传递;传递的参数可以是任何的对象,包括数据model,数组,字典等;注册后不需要手动移除; 使用简单和代码耦合低。

    2、通信方案的使用#####

    通信方案使用了QSMessageCenter,主要使用步骤如下:

    • 在观察者中注册,指定receiverKey

      [self registerMessageReceiverWithKey:receiverKey];
      
    • 发送消息,指定receiverKey,当注册方法和发送消息中的receiverKey相同,才可以把消息发送给观察者

      [self sendMessage:message messageId:messageId receiverKey:receiverKey];
      
    • 在观察者类中实现qsReceiveMessage:messageId:方法

      - (void)qsReceiveMessage:(id)message messageId:(NSString *)msgId{
          //可以根据msgId去做不同处理
      }
      

    说明:QSMessageCenter的具体详情参考iOS实录7:iOS通知中心的替换方案

    四、Demo展示####

    • 根据方案,定义两类Cell和CellModel,可以实现如下的列表页。
    列表页效果图.png
    • 根据方案,复用列表页的一类Cell和CellModel,新增一类Cell和CellModel,可以实现如下的详情页。
    详情页效果图

    总结:这样的方案,为实现列表页、详情页省去了大量的时间,开发者只需要集中精力做好Cell的绘制、CellModel的定义即可。

    五、Native动态化页面的野望(挖坑)

    H5页面有个很大的优点,就是可以不用发版就可以更新页面内容;如果可以利用TableView实现动态化的Native页面,岂不是很完美的事情。然后,现实的需求和业务比较复杂多变,实现这样的方案,投入和产出比是多少,能不能达到目标,都不好判断。但是针对某些特性的场景,实现页面的动态化数据更新还是可以的。

    1、数据描述界面的各个组成#####

    使用的是JSON格式数据,由后台返回,如下所示(部分JSON数据):

    {
       "body" :[{
          "type": 0, 
           "content": "http://xxxxxxxxxxxxxxxxxxxxx.jpg",
           "target":"appName://previewSingleImage"
        },
        {
            "type": 1, 
            "content": "H5页面有个很大的优点,就是可以不用发版就可以更新页面内容;如果可以利用TableView实现动态化的Native页面,岂不是很完美的事情....",
            "target":"appName://lookAllContent"
        },
        {
            "type": 2, 
            "content": "http://xxxxxxxxxxxxxxxxx.mp4",
            "target":"appName://playInFullScreen"
        }
        //其他略
      ]
    //其他略
    }
    

    说明1:type定义的是内容的类型,如0是图片,1是文本,2是视频;content定义的是内容;tagert定义的是点击内容的行为;tagert值的是App中自己定义的协议,如appName://previewSingleImage表明点击图片Cell预览全图,appName://playInFullScreen是点击视频Cell,全屏播放视频。

    2、动态化UI方案#####

    有两种方案可以选:

    1)在Native实现盛装不同数据类型的容器Cell,和Cell的响应处理行为。

    说明:该方案没有深入下去,因为TableView处理的视图比较简单,而且还给后台带来比较大的负担(本来后台开发资源就紧张),后期考虑使用CollectionView来实现特定场景下较复杂的动态化界面。

    2)将数据和展示需求拼接成H5代码,然后由WebView渲染出来,结合JS和WebViewJavascriptBridge处理Cell的响应处理行为。

    说明:该方案是目前采纳的方法,一定程度上实现了页面的动态化,比直接加载H5页面快很多。但是也仅仅是适用于某些固定展示行为的模块,如文章详情页等。

    3、 总结#####

    End

    • 源码QSUseTableViewDemo

    • 我是一个iOS开发程序猿,至今(2017年7月)正式做iOS开发已有1年多点的光景。iOS实(践)录系列是自己的一点开发心得。这个系列是17年4月1日开始写的,不仅是总结自己开发中经验,也是对自己的鞭策。

    相关文章

      网友评论

      • Simba_LX:动态化可以考虑部分页面使用weex开发
      • Mr__gao:感觉封装tableview比tableviewcontroller会好点
        南华coder:@Mr__gao 是的,更灵活

      本文标题:iOS实录1:UITableView构建UI界面

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