Search code examples
swiftswiftuiuikit

UIHostingConfiguration breaks @State var


I am using UIHostingConfiguration to embed SwiftUI Views in a UICollectionView like this:

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)

cell.contentConfiguration = UIHostingConfiguration {
   CellView()
}

Any @State var declarations in CellView or its subviews are being initialized to essentially random values. This seems to completely break the way @State works in SwiftUI. I have been able to reproduce it in this pretty simple sample project. Here is a video illustrating the problem. The count for each cell should be initialized to 0, but incrementing the count in any cell causes other cells to display a non-zero count when they are allocated.

I think this happens because when the collection view dequeues a reusable cell it is mem-copying another cell's view and the @State var holds the value from the previous cell. This is how collection view cells work in UIKit, but UICollectionViewCells have a prepareForReuse function which is supposed to clean up and reinitialize cell state, but I can't fine a similar mechanism in SwiftUI.

How can I reinitialize @State vars in my Views whenever the collection view is dequeuing a cell, and not unnecessarily at other times (like View.init which is called much more frequently)?

EDIT: Ideally I'm looking for a solution that doesn't require code in every SwiftUI View that is in a UIHostingConfiguration, since that will be easy for humans forget.


Solution

  • One solution is to break UICollectionView's cell reuse by using unique identifiers for every cell like this:

    let cellReuseID = model.identifier ?? "error"
    collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellReuseID)
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseID, for: indexPath) 
    

    This ensures @State variables play by the rules and are initialized to the value you specify before they are displayed every time. Obviously this incurs a performance cost, but in my case the difference is not noticeable. For me this is preferable to playing whack-a-mole with this bug every time someone adds a @State var to any view in our complex cell hierarchy and forgets about this problem.