美文网首页
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