关闭

爬爬爬之路:UI(九) UITableView(一) MVC模式M层的应用

标签: uitableviewuitableviewcelluiiOS
407人阅读 评论(0) 收藏 举报
分类:

UITableView

UITableView是继承自UIScrollView的滚动表视图. 且只能纵向滚动
UITableView主要由Section(分区)和row(行数)两个NSInteger类型和UITableViewCell(单元格, 用于设置每行的具体内容的封装类)3个数据结构构成

UITableViewCell是系统已经封装好的一个类, 它里面自带了一些控件, 比如UIImageView, UILabel等
row属于Section管理的属性. 可以通过定制分区数, 且给每个分区定制行数 来完成一个UITableView的基本视图构成.
Seciton默认为1. 每个Section都可以有自己的分区头和分区尾. 分区头和可以是一个简单的字符串title, 也可以是一个视图. 分区头和尾默认是无的.

除了每个分区的分区头和分区尾, TableView也可以有自己的总的表头和表尾.

当UITableView是添加在UINavigationController管理的ViewController上时,UITableView会自动在顶部留有一块空白区域分配给导航栏, 它的大小不需要程序员过度的考虑. 我们可以放心将UITableView的大小设置成满屏大小

UITableView

UITableView常用的初始化方法:

- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style;

UITableViewStyle对应的枚举类型:

typedef NS_ENUM(NSInteger, UITableViewStyle) {
    UITableViewStylePlain,                  // 平铺
    UITableViewStyleGrouped                 // 自带分组的
};

平铺的UITableView会忽略分区的间隔, 将各行连在一起. 可以通过给分区的分区头和分区尾赋值来实现分区之间的间隔
自带分组的样式在未设置分区头分区尾的时候会自动将各分区分离.

核心属性

  1. @property (nonatomic, assign) id <UITableViewDataSource> dataSource;
  2. @property (nonatomic, assign) id <UITableViewDelegate> delegate;

UITableView的界面铺设和响应功能几乎都是通过这两个属性对应的代理方法来完成的
关于这两个协议下文会介绍

常用属性

  1. @property (nonatomic) UITableViewCellSeparatorStyle separatorStyle;
    作用: 用来定制每行的分隔线样式
  2. @property (nonatomic, retain) UIView *tableHeaderView;
    作用: 用来定制表头 tableHeaderView的高度影响表头, 但坐标和宽度都不影响
  3. @property (nonatomic, retain) UIView *tableFooterView;
    作用: 用来定制表尾 tableFooterView的横坐标和高度影响表头, 纵坐标和宽度不影响

常用方法:

  1. 更新TableView方法
    - (void)reloadData;
    作用是用来刷新UITableView的. 当UITableView的数据源发生变化的时候, 调用本方法用来让UITableView显示变更的新数据.
  2. 获取重用池里的单元格方法
    - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
    获取重用池中标识符为identifier的单元格(UITableViewCell)
  3. 点击某行以后取消该行的选中状态

    - (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated;
    

这里再介绍一个专门用于UITableView定位到每行的索引对象NSIndexPath
NSIndexPath 常用的两个属性是
@property(nonatomic,readonly) NSInteger section;
@property(nonatomic,readonly) NSInteger row;
section 用来记录当前的分区下标
row 用来记录当前分区下对应的行坐标
分区和行下标都是从0开始计算的.

UITableViewDelegate

常用方法介绍

  1. 设置行高的方法

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
    
  2. 设置每个Section分区头高度的方法

    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
    

    只有设置了分区头高度以后才能定制分区头的具体样式

  3. 设置每个Section分区头的具体样式

    - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section; 

    当返回的UIView对象的高度和设置的分区头高度不一致的时候, 且返回的UIView对象的高度不参与影响分区头高度的定制

  4. 设置每个Section分区尾高度的方法

    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
    

    只有设置了分区尾的高度以后才能定制分区尾的具体样式

  5. 设置每个Section分区尾的具体样式

    - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;
    

    同上

  6. 响应点击table上某一行的方法

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
    

UITableViewDataSource

必须实现的两个方法

  1. 设置每个分区的行数

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
    
  2. 设置每行的具体内容(利用UITableViewCell单元格)

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

常用方法介绍

  1. 设置分区数

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
    
  2. 设置分区头标题

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

    ps. 这里设置的是分区的title, 和UITableViewDelegate中定制分区头的方法不同, 且使用本方法之前不需要先设置分区头的高度

  3. 设置分区尾标题

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

    同上

  4. 添加跳转到分区的快捷按键方法

    - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView; 

    本方法是给定一组数据 TableView右侧显示出一列用于快捷跳转的按钮, 通常数据的内容和各分区标题一致. 不一致的时候依然会按照按键下标的顺序跳转到相应下标的Section.超过Section数量的下标的按键不会被响应. 且本列按键是添加在UITableView上的, 不会随着画面移动

UITableViewCell

单元格. TableView每行的具体展示内容都是由UITableViewCell控制的
它是系统封装好的专门用于显示UITableView内容的类

常用属性

  1. 图片展示
    @property (nonatomic, readonly, retain) UIImageView *imageView
    用于在每行的左侧展示图片
  2. 主标题文本
    @property (nonatomic, readonly, retain) UILabel *textLabel
    用于显示主要信息
  3. 副标题文本
    @property (nonatomic, readonly, retain) UILabel *detailTextLabel
    用于显示辅助信息
  4. 单元格被选中样式
    @property (nonatomic) UITableViewCellSelectionStyle selectionStyle;
    用来定制被选中状态下单元格的颜色
  5. 辅助视图图标
    @property (nonatomic) UITableViewCellAccessoryType accessoryType;
    用来定制右侧辅助视图区的样式, 该样式是系统已经预设好的
  6. 赋值视图展示区
    @property (nonatomic, retain) UIView *accessoryView;
    用来定制右侧赋值视图区的自定义样式

初始化方法

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier

新建一个指定样式的单元格, 并给这个单元格设置一个标识符
样式如下

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,     只有简单的一个textLabel和imageView
    UITableViewCellStyleValue1,  拥有左右侧label 右侧文字可以变蓝 用于系统设置界面
    UITableViewCellStyleValue2, 拥有左右测label 左侧文字为蓝色 用于iPhone上联系人界面
    UITableViewCellStyleSubtitle      左侧文字可分为上下 同于ipod
};

这里介绍一下TableViewCell的重用机制.

当数据量极大的时候, 可能需要创建数量极大的TableVIewCell
此时若是真的创建这么多的TableViewCell, 会导致应用加载速度十分慢, 更有可能导致内存溢出 从而使得程序闪退

重用机制正是用于应对本现象的一种处理手段
它的原理如下:
不论数据量多大, 同时在屏幕上显示的的数据都是有限的. 当某一个cell在滑动过程中完全消失在屏幕上的时候, 系统会自动将它回收到一个可变集合中 这个可变集合叫做重用池. 当下一个cell将要出现的时候, 系统会先在重用池中搜索指定类型的cell. 若有则直接将其取出, 并给其重新赋值, 然后显示. 若无, 则再重新创建一个.

判断cell是否可以重用是通过标识符identiter来识别的.
具体写法如下:

    static NSString *identifier = @"MyCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleSubtitle) reuseIdentifier:identifier] autorelease];
    }

这里需要程序员自己对identifier进行一个良好的区分.
数据不同, 但是显示内容的格式是相同的cell公用同一个标识符.
当显示内容不同的时候需要用不同的标识符.

在一个cell创建之前 先需要从重用池中寻找是否有符合cell想要显示的内容排版的cell.
没有的话再新建一个新的cell. 这样就避免建了太多格式重复的cell导致程序卡顿或者崩溃的情况.


MVC中M层的应用

当一个界面铺设的时候, 具体显示的内容应该是由M层赋予的.
而数据一般都是通过服务器也就是后台发送而来的.
服务器发送数据通常都是以这样的形式:

{
    A:[
        {
        "name":"aaa"
         "age":"18"
      "gender":"女"},
        {
        "name":"bbb"
         "age":"22"
      "gender":"男"}
        ],
    B:[
        {
        "name":"ccc"
         "age":"24"
      "gender":"男"}
        ],
    C:[
        {
        "name":"abc"
         "age":"15"
      "gender":"女"}
        ] 
}

像这样一层字典套一层数组, 再套一层字典的形式. 数据发杂的可能还有更多层
通常最外层和最内层都是字典.

最内层的字典保存的就是我们需要显示在界面上的内容
通常我们需要层层推进获得最内层的字典, 再将这个字典的内容转化成Model层数据.
Model层数据通常是用类的对象来保存
然后再将数据一层层按原来的顺序还原. 最后保存在一个数据字典中. 为了便于操作通常会选择将该字典设为属性.
Model层数据 其实也就是OC中的类. 这是这些类通常只需要声明属性, 还有很少的数据处理的方法, 没有那么多复杂的功能方法.

这里我们将数据请求获得的未经处理的字典暂称为源数据或者数据源字典, 将重新包装好的字典成为数据模型字典.

源数据字典和数据模型字典的差别就在于最内层保存的数据类型不同, 源数据字典最内层保存的是一个字典, 数据模型字典最内层保存的是一个数据模型(类对象)
比如以下代码就是将一组简单的数据重新封装成一个数据模型字典.

- (void)setUpData {
    // 从文件中读取数据
    NSString *path = [[NSBundle mainBundle] pathForResource:@"Class25ContactList" ofType:@"plist"];
    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:path];

    // 获得数据模型, 并重新包装数据
    self.dataDic = [NSMutableDictionary dictionary];
    for (NSString *key in dic) {
        NSMutableArray *values = [NSMutableArray array];
        NSArray *array = dic[key];
        for (NSDictionary *dicModel in array) {
            Student *student = [[Student alloc] init];
            [student setValuesForKeysWithDictionary:dicModel];
            [values addObject:student];
        }
        [self.dataDic setObject:values forKey:key];
    }

    // 将所有key排序后保存
    self.sortedAllKeys = [dic.allKeys sortedArrayUsingSelector:@selector(compare:)];

}

这里需要关注的有几点. 首先遍历字典, 层层剥离出最终数据.
但是最重要的还是需要构建数据模型(类)
类中需要声明属性. 且属性名字必须和请求的源数据最内层字典的key一一对应.也就是属性名和key值要一致.
这是因为我们在将数据写入数据对象的时候 需要用到KVC的setValuesForKeysWithDictionary方法.
且为了防止赋值错误, 在类中必须实现KVC的异常处理方法
setValue: forUndefinedKey:
一是为了防止属性名拼写错误, 一是为了处理数据源内层字典的key用了系统关键字.
都需要在异常处理方法中有相应的异常处理方案, 否则上述两种情况发生了其中一种就会导致程序崩溃.

等到数据模型字典构建完成后, 就可以对

谈谈本人对M层的粗解

这里谈谈为什么要这么大动干戈的构建数据模型, 再将其复原.
我认为好处大致有以下几点

一. 数据清晰

使得数据清晰, 便于查找.
试想当我们需要用到一个数据的时候, 是重新去请求数据, 查看源数据的方便, 还是查看Model类的属性方便?

这还只是对于一个界面而言
当多个界面的数据混杂的时候, 这种情况会非常头疼.

二. 赋值方便

还有更重要的一点是在界面间传值的时候, 比如当下一个界面需要用到本界面请求的时, 是传数据类方便还是传字典方便呢?

如果传的是字典, 在对界面控件赋值的时候, 我们需要获得某个具体数据的时候只能对着网址或者数据请求结果慢慢的找到数据对应的key, 再重新通过key获得对应的value. 而若是传的是数据对象, 只需要点出属性即可.

三. 维护方便

当后台数据有微调的时候, 我们只需要修改数据模型的类的属性, 然后在Controller中找到对应类对象进行微调即可, 若是字典的话 可能一个界面不止一个字典, 要从各种字典中区分出目标字典还是蛮有难度的.

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:18439次
    • 积分:648
    • 等级:
    • 排名:千里之外
    • 原创:46篇
    • 转载:4篇
    • 译文:0篇
    • 评论:6条
    文章分类