使用 UISwitch 显示和隐藏 UITableViewCell 太快崩溃

发布于 2025-01-03 09:24:34 字数 1230 浏览 0 评论 0原文

我有一个 UITableView 向用户呈现一些设置。除非 UISwitch 处于“打开”位置,否则某些单元格会被隐藏。我有以下代码:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return switchPush.on ? 6 : 1;
}

// Hooked to the 'Value Changed' action of the switchPush
- (IBAction)togglePush:(id)sender {
    NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:0];
    for(int i = 1; i <= 5; i++) {
        [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
    }

   [tableView beginUpdates];
    if(switchPush.on) {
        [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
    } else {
        [tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
    }
   [tableView endUpdates];
}

这按预期工作,直到我快速连续切换 UISwitch 两次(通过双击),在这种情况下应用程序崩溃,并显示

Invalid table view update. The application has requested an update to the table view that is inconsistent with the state provided by the data source.

我知道这是由 numberOfRowsInSection 的错误返回值引起的当单元格动画仍在播放时,开关返回到其原始位置。我尝试禁用切换并将代码挂接到其他事件处理程序下,但似乎没有什么可以阻止崩溃。使用 reloadData 而不是动画也可以解决问题,但我更喜欢漂亮的动画。

有人知道实现这个的正确方法吗?

I've got a UITableView that presents some settings to the user. Some cells are hidden unless a UISwitch is in the 'On' position. I've got the following code:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return switchPush.on ? 6 : 1;
}

// Hooked to the 'Value Changed' action of the switchPush
- (IBAction)togglePush:(id)sender {
    NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:0];
    for(int i = 1; i <= 5; i++) {
        [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
    }

   [tableView beginUpdates];
    if(switchPush.on) {
        [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
    } else {
        [tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
    }
   [tableView endUpdates];
}

This works as expected, until I switch the UISwitch twice in rapid succession (by double tapping), in which case the application crashes with a

Invalid table view update. The application has requested an update to the table view that is inconsistent with the state provided by the data source.

I know that it is caused by the wrong return value of numberOfRowsInSection as the switch is back in its original position while the cell animation is still playing. I've tried disabling the toggle and hooking the code under other event handlers but nothing seems to prevent the crash. Using reloadData instead of the animation also solves to problem but I would prefer the nice animations.

Does anybody know a correct way of implementing this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

以酷 2025-01-10 09:24:34

该问题的另一个(更优雅的)解决方案是:

我以这种方式修改了 Alan MacGregor - (IBAction)SwitchDidChange:(id)sender 方法:

- (IBAction)SwitchDidChange:(UISwitch *)source {
     if (_showRows != source.on) {
         NSArray *aryTemp = [[NSArray alloc] initWithObjects:
                    [NSIndexPath indexPathForRow:1 inSection:0],
                    [NSIndexPath indexPathForRow:2 inSection:0],
                    [NSIndexPath indexPathForRow:3 inSection:0],
                    [NSIndexPath indexPathForRow:4 inSection:0],nil];
         [_tblView beginUpdates];
         _showRows = source.on;
         if (_showRows) {
             [_tblView insertSections:aryTemp withRowAnimation:UITableViewRowAnimationFade];
         }
         else {
             [_tblView deleteSections:aryTemp withRowAnimation:UITableViewRowAnimationFade];
         }
         [_tblView endUpdates];
     }
}

其他部分保持不变。

Another (more elegant) solution at the problem is this:

I modified the Alan MacGregor - (IBAction)SwitchDidChange:(id)sender method in this way:

- (IBAction)SwitchDidChange:(UISwitch *)source {
     if (_showRows != source.on) {
         NSArray *aryTemp = [[NSArray alloc] initWithObjects:
                    [NSIndexPath indexPathForRow:1 inSection:0],
                    [NSIndexPath indexPathForRow:2 inSection:0],
                    [NSIndexPath indexPathForRow:3 inSection:0],
                    [NSIndexPath indexPathForRow:4 inSection:0],nil];
         [_tblView beginUpdates];
         _showRows = source.on;
         if (_showRows) {
             [_tblView insertSections:aryTemp withRowAnimation:UITableViewRowAnimationFade];
         }
         else {
             [_tblView deleteSections:aryTemp withRowAnimation:UITableViewRowAnimationFade];
         }
         [_tblView endUpdates];
     }
}

The other parts stay unchanged.

懷念過去 2025-01-10 09:24:34

只需将交换机的 enabled 属性设置为 NO 即可,直到更新完成。

Simply set the enabled property of your switch to NO until the updates are done.

任谁 2025-01-10 09:24:34

我遇到了这个问题,避免崩溃的方法是不显式使用 uiswitch,而是将信息中继到布尔值中,这就是我的做法。

将布尔值添加到实现文件的顶部

bool _showRows = NO;

更新您的 uiswitch 代码

- (IBAction)SwitchDidChange:(id)sender {

NSArray *aryTemp = [[NSArray alloc] initWithObjects:[NSIndexPath indexPathForRow:1 inSection:0],
                    [NSIndexPath indexPathForRow:2 inSection:0],
                    [NSIndexPath indexPathForRow:3 inSection:0],
                    [NSIndexPath indexPathForRow:4 inSection:0],nil];

if (_showRows) {
    _showRows = NO;
    _switch.on = NO;
    [_tblView  deleteRowsAtIndexPaths:aryTemp withRowAnimation:UITableViewRowAnimationTop];
}
else {
    _showRows = YES;
    _switch.on = YES;
    [_tblView insertRowsAtIndexPaths:aryTemp withRowAnimation:UITableViewRowAnimationBottom];
}
}

最后更新您的 numberOfRowsInSection

- (NSInteger)tableView:(UITableView *)tableView 
 numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {

    if (_showRows) {
        return 5;
    }
    else {
        return 1;

    }      
}
return 0;  
}

I had this issue on mine and the way to avoid the crash is to not explicitly use the uiswitch, instead relay the information into a boolean, heres how I did it.

Add a boolean to the top of your implementation file

bool _showRows = NO;

Update your uiswitch code

- (IBAction)SwitchDidChange:(id)sender {

NSArray *aryTemp = [[NSArray alloc] initWithObjects:[NSIndexPath indexPathForRow:1 inSection:0],
                    [NSIndexPath indexPathForRow:2 inSection:0],
                    [NSIndexPath indexPathForRow:3 inSection:0],
                    [NSIndexPath indexPathForRow:4 inSection:0],nil];

if (_showRows) {
    _showRows = NO;
    _switch.on = NO;
    [_tblView  deleteRowsAtIndexPaths:aryTemp withRowAnimation:UITableViewRowAnimationTop];
}
else {
    _showRows = YES;
    _switch.on = YES;
    [_tblView insertRowsAtIndexPaths:aryTemp withRowAnimation:UITableViewRowAnimationBottom];
}
}

And finally update your numberOfRowsInSection

- (NSInteger)tableView:(UITableView *)tableView 
 numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {

    if (_showRows) {
        return 5;
    }
    else {
        return 1;

    }      
}
return 0;  
}
祁梦 2025-01-10 09:24:34

即使控件的值实际上没有更改,也会发生 UIControlEventValueChanged 事件。因此,即使开关的值没有改变,togglePush 也会被调用。当您快速切换开关时,您可能并不总是从 on > >关闭>上 >关闭等可以关闭>上 >上 >离开。

所以发生的事情是你连续得到两个 on 导致两个 insertSections 一个接一个。这显然很糟糕。

要解决此问题,您需要记住按钮的先前状态(可能在 ivar 中),并且仅在新值 (source.on) 与先前值不同时才执行插入(或删除)。

UIControlEventValueChanged events occur even when a control's value doesn't actually change. so togglePush gets called even when the value of the switch doesn't change. when you quickly toggle the switch, you might not always go from on > off > on > off, etc. it's possible to go off > on > on > off.

so what's happening is that you're getting two ons in a row causing two insertSections one after the other. which is obviously bad.

to fix this, you need to remember what the previous state of the button was (in an ivar, maybe) and only perform the insert (or delete) if the new value (source.on) is different from the previous value.

错々过的事 2025-01-10 09:24:34

所以我也有这个问题。更改值后,您必须立即禁用 UISwitch。然后,您可以在 performBatchUpdates 中进行更新(iOS 11 及更高版本!)。更新完成后,您可以在回调中启用 UISwitch。

   //...method with value change or other control event:
    
    yourUISwitch.enabled = NO;
    
    //...method with your tableview inserts or deletes:
    
    [self.tableView beginUpdates];
    [self.tableView performBatchUpdates:^{
        [self.tableView insertRowsAtIndexPaths:yourIndexPath withRowAnimation:UITableViewRowAnimationFade];
    } completion:^(BOOL finished) {
        if(finished){
            yourUISwitch.enabled = YES;
        }
    }];
    
    [self.tableView endUpdates];

So I also had this issue. You have to disable the UISwitch as soon as change your value. Then you do your updates inside the performBatchUpdates(iOS 11 and above!). As soon as your updates are finished you enable the UISwitch in the callback.

   //...method with value change or other control event:
    
    yourUISwitch.enabled = NO;
    
    //...method with your tableview inserts or deletes:
    
    [self.tableView beginUpdates];
    [self.tableView performBatchUpdates:^{
        [self.tableView insertRowsAtIndexPaths:yourIndexPath withRowAnimation:UITableViewRowAnimationFade];
    } completion:^(BOOL finished) {
        if(finished){
            yourUISwitch.enabled = YES;
        }
    }];
    
    [self.tableView endUpdates];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文