美文网首页
008-多布局TableView与复用View的问题

008-多布局TableView与复用View的问题

作者: Yasic | 来源:发表于2017-11-26 19:19 被阅读53次

    多布局 TableView 与复用 View 的问题

    1. 多布局 TableView

    UITableView 可以用来展示一系列的数据,即使数据表达样式不尽相同,也可以将不同样式的 TableViewCell 放在一个 TableView 中进行展示。

    多布局 TableView 的基本思路如下:

    • 用一个基础 model 类表达抽象的 cell 数据,其中包含 type 字段作为区分布局的依据
    • 为不同 type 的数据创建不同的 TableViewCell 类
    • 给 TableView 注册多个 TableViewCell 类和 CellReuseIdentifier
    • 将一组 model 作为数据源,在 UITableViewController 返回 Cell 实例的方法中根据 type 创建和返回不同的 Cell 实例

    2. 复用 View

    其中要解决的主要问题是如果是与用户有交互的(这种情况很常见),一旦 View 被复用就可能发生用户输入数据丢失或重复等问题,解决方法是实时将数据源的数据进行同步更新,然后对于复用的 view 保证从数据源获取最新的数据。

    在这里以一个通讯录编辑页面的例子作为说明,我们要在一个 TableView 中加入包括 UITextField 、分割单元、选择器等在内的多种布局 Cell。

    首先定义一个数据模型 Model

    typedef enum
    {
        TextFieldType,
        SeparatorType,
        SelectorType
    }MenuType;
    
    @interface PhoneBookDetailMenuModel : NSObject
    
    @property(strong, nonatomic) NSString *name;
    @property(assign, nonatomic) MenuType cellType;
    @property(strong, nonatomic) NSString *textInfo;
    
    
    @end
    

    name 用于区分各个 model,同时作为 UITextField 的placeholder。cellType 是一个类型为 MenuType 的枚举,包括三种枚举值。textInfo 是 textField 的 text 值,初始为空。

    接下来定义了两种 Cell 布局。

    • TextFieldCell

      #import <UIKit/UIKit.h>
      
      @interface TextFieldCell : UITableViewCell
      
      @property(strong, nonatomic) UITextField *input;
      
      @end
      
      #import "TextFieldCell.h"
      #import "Masonry.h"
      
      #define kWidth [UIScreen mainScreen].bounds.size.width
      #define kHeight [UIScreen mainScreen].bounds.size.height
      
      @implementation TextFieldCell
      
      - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
      {
          self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
          if (self)
          {
              CGRect frame = CGRectMake(16, 16, kWidth - 32, 32);
              _input = [[UITextField alloc] initWithFrame:frame];
              _input.borderStyle = UITextBorderStyleRoundedRect;
              [self.contentView addSubview:_input];
          }
          return self;
      }
      
      @end
      
    • SelectorCell

      #import <UIKit/UIKit.h>
      
      @interface SelectorCell : UITableViewCell
      
      @property(strong, nonatomic) UILabel *titleLabel;
      @property(strong, nonatomic) UIButton *selector;
      
      @end
      
      #import "SelectorCell.h"
      
      #define kWidth [UIScreen mainScreen].bounds.size.width
      #define kHeight [UIScreen mainScreen].bounds.size.height
      
      @implementation SelectorCell
      
      - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
      {
          self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
          if (self)
          {
              CGRect labelFrame = CGRectMake(16, 16, kWidth/2, 32);
              _titleLabel = [[UILabel alloc] initWithFrame:labelFrame];
              _titleLabel.textAlignment = NSTextAlignmentLeft;
              [self.contentView addSubview:_titleLabel];
              
              _selector = [[UIButton alloc] initWithFrame:CGRectMake(kWidth/2, 16, kWidth/2 - 16, 32)];
              _selector.contentHorizontalAlignment = NSTextAlignmentRight;
              [self.contentView addSubview:_selector];
          }
          return self;
      }
      
      - (void)awakeFromNib {
          [super awakeFromNib];
          // Initialization code
      }
      
      - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
          [super setSelected:selected animated:animated];
      
          // Configure the view for the selected state
      }
      
      @end
      

    然后是对 TableViewController 的初始化工作。

        _menuModelArray = [NSMutableArray arrayWithCapacity:10];
        
        self.tableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
        self.tableView.delegate = self;
        self.tableView.dataSource = self;
        self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        
        PhoneBookDetailMenuModel *nameModel = [[PhoneBookDetailMenuModel alloc] init];
        nameModel.name = @"name";
        nameModel.cellType = TextFieldType;
        nameModel.textInfo = @"";
        [_menuModelArray addObject:nameModel];
        
        PhoneBookDetailMenuModel *phoneNumberModel = [[PhoneBookDetailMenuModel alloc] init];
        phoneNumberModel.name = @"phoneNumber";
        phoneNumberModel.cellType = TextFieldType;
        phoneNumberModel.textInfo = @"";
        [_menuModelArray addObject:phoneNumberModel];
        
        PhoneBookDetailMenuModel *separatorModel = [[PhoneBookDetailMenuModel alloc] init];
        separatorModel.name = @"separator";
        separatorModel.cellType = SeparatorType;
        separatorModel.textInfo = @"";
        [_menuModelArray addObject:separatorModel];
        
        PhoneBookDetailMenuModel *addressModel = [[PhoneBookDetailMenuModel alloc] init];
        addressModel.name = @"address";
        addressModel.cellType = TextFieldType;
        addressModel.textInfo = @"";
        [_menuModelArray addObject:addressModel];
        
        PhoneBookDetailMenuModel *emailModel = [[PhoneBookDetailMenuModel alloc] init];
        emailModel.name = @"email";
        emailModel.cellType = TextFieldType;
        emailModel.textInfo = @"";
        [_menuModelArray addObject:emailModel];
        
        PhoneBookDetailMenuModel *remarksModel = [[PhoneBookDetailMenuModel alloc] init];
        remarksModel.name = @"remarks";
        remarksModel.cellType = TextFieldType;
        remarksModel.textInfo = @"";
        [_menuModelArray addObject:remarksModel];
        
        PhoneBookDetailMenuModel *genderModel = [[PhoneBookDetailMenuModel alloc] init];
        genderModel.name = @"Gender";
        genderModel.cellType = SelectorType;
        genderModel.textInfo = @"Male";
        [_menuModelArray addObject:genderModel];
        
        PhoneBookDetailMenuModel *birthDateModel = [[PhoneBookDetailMenuModel alloc] init];
        birthDateModel.name = @"BirthDate";
        birthDateModel.cellType = SelectorType;
        birthDateModel.textInfo = @"1990-01-01";
        [_menuModelArray addObject:birthDateModel];
        
        PhoneBookDetailMenuModel *ageModel = [[PhoneBookDetailMenuModel alloc] init];
        ageModel.name = @"Age";
        ageModel.cellType = SelectorType;
        ageModel.textInfo = [NSString stringWithFormat:@"%ld", [self calculateAge:birthDateModel.textInfo]];
        [_menuModelArray addObject:ageModel];
        
        for (int i = 0; i < 20; i++)
        {
            PhoneBookDetailMenuModel *model = [[PhoneBookDetailMenuModel alloc] init];
            model.name = [NSString stringWithFormat:@"Test%d", i];
            model.cellType = TextFieldType;
            model.textInfo = @"";
            [_menuModelArray addObject:model];
        }
        
        [self.tableView registerClass:[TextFieldCell class] forCellReuseIdentifier:textFieldIdentifier];
        [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:separatorIdentifier];
        [self.tableView registerClass:[SelectorCell class] forCellReuseIdentifier:selectorIdentifier];
    

    这里我们去除了 TableView 默认的分割线,为了测试还加入了20个测试的 TextField。

    接下来要对数据源和委托方法进行复写,重点是其中的 (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 方法

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        MenuType type = [_menuModelArray[indexPath.row] cellType];
        if (type == TextFieldType) //输入框类型
        {
            TextFieldCell *cell = [tableView dequeueReusableCellWithIdentifier:textFieldIdentifier forIndexPath:indexPath];
            cell.input.placeholder = @""; //清除可能存在的数据
            cell.input.text = @""; //清除可能存在的数据
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
            if ([[_menuModelArray[indexPath.row] textInfo] isEqualToString:@""])
            {
                cell.input.placeholder = [_menuModelArray[indexPath.row] name];
            }
            else
            {
                cell.input.text = [_menuModelArray[indexPath.row] textInfo];
            }
            cell.input.tag = indexPath.row; //按照 tag 值在 UIControlEventEditingChanged 监听函数中更新对应的 model
            [cell.input addTarget:self action:@selector(inputChanged:) forControlEvents:UIControlEventEditingChanged];
            return cell;
        }
        if (type == SeparatorType) //分割单元
        {
            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:separatorIdentifier forIndexPath:indexPath];
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
            return cell;
        }
        if (type == SelectorType) //选择器单元
        {
            ···
            ···
            return cell;
        }
        return [UITableViewCell new];
    }
    

    首先根据 indexPath 的 row 值可以获取到数据源数组中对应的model,从而得知 type 值,根据 type 值生成或从已有的 view 中复用对应的 cell,然后清除其中数据。

    清除数据的步骤必须要做,否则就会出问题。比如这里,接下来会按照 model 的 textInfo 属性确定是给 cell 的 TextField 设置 placeholder 还是 text,但是如果复用的 view 本身就有 text,再赋值 placeholder 是不会清除 text 的,就会发生数据复用的问题。

    清除数据后设置 TextField 的值,然后要对 Cell 的 TextField 设置 tag 值,从而按照 tag 值在 UIControlEventEditingChanged 监听函数中更新对应的 model。

    监听函数 inputChanged 如下

    - (void)inputChanged:(UITextField *)targetField
    {
        ((PhoneBookDetailMenuModel *)_menuModelArray[targetField.tag]).textInfo = targetField.text;
    }
    

    主要是根据 tag 值从数据源数组中找到对应的 model,然后更新其中的 textInfo 属性,从而保证数据源数据是最新的,这样就不会出现复用 view 时数据出错的情况了。

    相关文章

      网友评论

          本文标题:008-多布局TableView与复用View的问题

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