简体   繁体   中英

When to call tableView.selectRow to avoid UITableViewAlertForCellForRowAtIndexPathAccessDuringUpdate

In implementing support for multiple selection of rows of a UITableView , I'm using a private variable rowSelectionState which is managed during didSelectRow and didDeselectRow . The current state of rowSelectionState is then used to control display of the properly selected rows to the user via calls to configureCellSelectionState() cellForRowAt . Technically as of iOS 14.4, this does what it is supposed to do, with the caveat that iOS dumps a bunch of UITableViewAlertForCellForRowAtIndexPathAccessDuringUpdate warnings in XCode.

    private var rowSelectionState = [IndexPath: Bool]()

    func configureCellSelectionState(_ indexPath: IndexPath) {
        guard tableView.isEditing else { return }
        if rowSelectionState[indexPath] ?? false {
            tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
        } else {
            tableView.deselectRow(at: indexPath, animated: false)
        }
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if tableView.isEditing {
            rowSelectionState[indexPath] = true
        }
    }
    
    override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        if tableView.isEditing {
            rowSelectionState[indexPath] = false
        }
    }

    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        
        if !editing {
            rowSelectionState.removeAll()
        }
    }

Calling configureCellSelectionState() inside of cellForRowAt is how this was originally written.

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: rowCellId, for: indexPath)
        configureCellSelectionState(indexPath)
        return cell
    }

After taking closer notice of the stream of warnings, some searching StackOverflow led me to call configureCellSelectionState() inside of willDisplay rather than cellForRowAt . Unfortunately, the warnings are still there.

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        configureCellSelectionState(indexPath)
    }

Example warning:

2021-02-09 09:12:14.119983-0500 XXX [55266:1705708] [Assert] 
Attempted to call -cellForRowAtIndexPath: on the table view while it was in the process of updating its visible cells, which is not allowed. 
Make a symbolic breakpoint at UITableViewAlertForCellForRowAtIndexPathAccessDuringUpdate to catch this in the debugger and see what caused this to occur. 
Perhaps you are trying to ask the table view for a cell from inside a table view callback about a specific row? 
Table view: <UITableView: 0x7b7800045c00; frame = (0 0; 390 844); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7b0c000fee50>; layer = <CALayer: 0x7b08002be520>; contentOffset: {0, -143}; contentSize: {390, 264}; adjustedContentInset: {143, 0, 143, 0}; dataSource: <XXX.YYYTableViewController: 0x7b640006b800>>

So the question is, what is the "proper" way to implement this kind of feature while avoiding the UITableViewAlertForCellForRowAtIndexPathAccessDuringUpdate warning?

Instead of calling tableView.selectRow or tableView.deselectRow while displaying the cell, call it instead from viewDidLoad for your view controller. Or perhaps after setting the value of rowSelectionState , if that happens after loading the view controller already.

The issue is that you're inside a UITableView delegate method when you're trying to change the selection state, so it's not going to work properly.

Note also that you can check the indexPathsForSelectedRows property on UITableView so that when you do make changes to rowSelectionState , you can just update the selection state of only those rows that need it. But again, do this not when you're in the middle of a UITableView delegate method.

Also, from the looks of it, you're trying to use rowSelectionState to track which ones are already in a selected state, so maybe you need to just reference the indexPathsForSelectedRows property.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM