iosswiftxcoderestorestate-restoration

UICollectionView state restoration: restore all UICollectionViewCells


I searched a lot through Google and SO, so please forgive me, if this question has already been answered!

The problem: I have a UICollectionView with n UICollectionViewCells. Each cell contains a UIView from a XIB file. The Views are used for data entry, so all cells have a unique reuseIdentifier. Each View has also a unique restorationIdentifier. Everything works in normal usage, but not when it comes to state restoration:

The first 3 or 4 cells are getting restored properly because they are visible on the screen on startup, but the remaining cells, which are not visble, are not getting restored.

Current solution: So I've discovered so far that a View is only restored if it's added to userinterface at startup. My current working solution is to set the height of all cells to 1 in the process of restoring. Now every cell is loaded and all views are restored. When applicationFinishedRestoringState() is called, I reload the CollectionView with the correct height.

Now my question is: I'm not happy with this solution, is there a more clean way to achieve restoring of all the UIViews?


Solution

  • I think you are getting a bit confused between your data model and your views. When first initialised, your table view is constructed from a data model, pulling in stored values in order to populate whatever is in each cell. However, your user does not interact directly with the data model, but with the view on the screen. If the user changes something in the table view, you need to signal that change back up to the view controller so that it can record the change to the data model. This means in turn that if the view needs to be recreated the view controller has the information it needs to rebuild whatever was in the table when your app entered the background.

    I have put together a simple gitHub repository here: https://github.com/mpj-chandler/StateManagementDemo

    This comprises a CustomTableViewController class which manages a standard UITableView populated with CustomTableViewCells. The custom cells contain three switch buttons, allowing the state of each cell to be represented by an array of Boolean values.

    I created a delegate protocol for the cells such that if any of the switches is tripped, a signal is sent back to the view controller:

    protocol CustomTableViewCellDelegate {
        func stateDidChange(sender: CustomTableViewCell) -> Void
    }
    
    // Code in CustomTableViewCell.swift:
    
    @objc fileprivate func switched(sender: UISwitch) -> Void {
    
        guard let index : Int = switches.index(of: sender) else { return }
    
        state[index] = sender.isOn   
    }
    
    // The cell's state is an observed parameter with the following didSet method:
    
    fileprivate var state : [Bool] = Array(repeating: false, count: 3) {
        didSet {
            if state != oldValue, let _ = delegate {
                delegate!.stateDidChange(sender: self)
            }
        }
    }
    

    CustomTableViewController is registered to the CustomTableViewCellDelegate protocol, so that it can record the change in the model as follows:

    // Code in CustomTableViewController.swift
    
    //# MARK:- CustomTableViewCellDelegate methods
    
    internal func stateDidChange(sender: CustomTableViewCell) -> Void {
        guard let indexPath : IndexPath = tableView.indexPath(for: sender) else { return }
        guard indexPath.row < model.count else { print("Error in \(#function) - cell index larger than model size!") ; return }
    
        print("CHANGING MODEL ROW [\(indexPath.row)] TO: \(sender.getState())")
        model[indexPath.row] = sender.getState()
    
    }
    

    You can see here that the function is set up to output model changes to the console.

    If you run the project in simulator and exit to the home screen and go back again you will see the state of the tableView cells is preserved, because the model reflects the changes that were made before the app entered the background.

    Hope that helps.