包含 UISwitch 的 UITableView 单元格更新不一致

Posted

技术标签:

【中文标题】包含 UISwitch 的 UITableView 单元格更新不一致【英文标题】:UITableView cells containing a UISwitch update inconsistently 【发布时间】:2018-11-13 01:51:18 【问题描述】:

我有一个UITableView,其中每个单元格都包含一个UISwitch。该开关应该将单元格中命名的内容切换为活动或不活动,并且当用户切换开关时它可以完美地工作。

但是,我想到了在选择整个表格行时也进行切换的好主意。理论上很简单吧?就更新数据模型而言,确实如此。

但是,无论出于何种原因让我在过去 2 小时内感到困惑,只有单元格 #3 中的开关会更新以反映我重新加载表数据时的状态变化。无论我在表中有多少行,它总是只有第三个单元格有效。如果少于 3 个单元格,则没有单元格起作用。

我已经在其中转储了大量NSLog 调用,它们反映了我一直期望的正确数据状态,但它们只是没有反映在第一次加载之后的切换中。如果我离开视图控制器然后回来,开关都已正确设置...

我只知道这将是我在某个地方做过的一些愚蠢的事情,因为它只在一个实例中起作用,但对于我的生活我看不到它:(

你可以吗?

-(void)viewDidLoad

    [super viewDidLoad];

    [_tableAddPeople setDelegate:self];
    [_tableAddPeople setDataSource:self];


-(void)viewWillAppear:(BOOL)animated

    [_tableAddPeople reloadData];


-(void)listSwitchChanged:(id)sender

    UISwitch * switchControl = sender;

    Person * person = [SAVE_DATA getPersonWithIndex:(int)switchControl.tag];

    if (switchControl.on)
    
        [SAVE_DATA activePeopleAdd:person.tag];
    
    else
    
        [SAVE_DATA activePeopleRemove:person.tag];
    



#pragma mark UITableViewDataSource Delegate

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

    return [SAVE_DATA peopleCount];


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

    UITableViewCell * cell = [_tableAddPeople dequeueReusableCellWithIdentifier:@"AddPeopleCell"];

    UILabel * nameLabel = (UILabel *)[cell.contentView viewWithTag:1];
    UISwitch * activeSwitch = (UISwitch *)[cell.contentView viewWithTag:2];

    Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

    [nameLabel setText:[person name]];

    BOOL active = [SAVE_DATA activePeopleContains:person.tag];
    NSLog(@"Row for %@ is %@", person.name, (active? @"ON" : @"OFF"));
    [activeSwitch setOn:active animated:YES];
    [activeSwitch addTarget:self action:@selector(listSwitchChanged:) forControlEvents:UIControlEventValueChanged];
    activeSwitch.tag = (int)[indexPath row];

    return cell;


-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath

    return NO;


-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath

    return NO;



#pragma mark UITableView Delegate

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

    Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

    if ([SAVE_DATA activePeopleContains:person.tag])
    
        NSLog(@"Removing %@", person.name);
        [SAVE_DATA activePeopleRemove:person.tag];
    
    else
    
        NSLog(@"Adding %@", person.name);
        [SAVE_DATA activePeopleAdd:person.tag];
    

    [_tableAddPeople reloadData];
    //dispatch_async(dispatch_get_main_queue(), ^[tableView reloadData];);

【问题讨论】:

【参考方案1】:

您的问题是您将开关 tag 属性用于两件事:

    在视图层次结构中识别它(您已分配值“2”) 确定在哪一行更改了开关

因为您将indexPath.row 分配给tag,所以在索引路径不是2(即第三行)的情况下,[cell.contentView viewWithTag:2]; 将在重新使用单元格时返回nil

您还遇到了单元格重复使用的问题,因为您要多次添加开关操作处理程序。

我建议你采用以下方法:

    使用属性公开您的单元格的子视图并将dequeueReusableCell 的结果转换为您的UITableViewCell 子类,以便您无需使用标签即可访问它们

    建立您的UITableViewCell 子类作为开关操作处理程序。

    在单元格和视图控制器之间实现委托模式,以便单元格可以通知视图控制器开关已更改。单元格可以将self 传递给委托方法。在委托方法中,您可以使用indexPathForCell 来确定受影响的行。

    不要仅仅因为一行发生了变化就重新加载整个 tableview。仅重新加载受影响的行。

【讨论】:

以上是关于包含 UISwitch 的 UITableView 单元格更新不一致的主要内容,如果未能解决你的问题,请参考以下文章

静态 UITableViewCell 中的 UISwitch 生成错误

UITableView 奇怪的行为

如果我在每个表格视图单元格中添加一个 UISwitch 控件,我如何知道它属于哪个单元格?

将 UISwitch 添加到 TableView 中的一个单元格

如何以编程方式切换uiswitch按钮

SWIFT:从自定义单元格中的 UIswitch 展开/折叠自定义单元格