As we know when using UITableView or UICollectionView we register reusable cell classes and then we dequeue this reusable cells to make list/collection working more efficiently with large dataset.
Is it possible to use such UITableViews or UICollectionViews with SwiftUI Content in any approach in such way to not deteriorate efficiency of this lists/collections?
I consider such examples:
UICollectionView cells
private func cellProvider(collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? { print("Providing cell for \(indexPath)") guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HostingControllerCollectionViewCell<AnyView>.reuseIdentifier, for: indexPath) as? HostingControllerCollectionViewCell<AnyView> else { fatalError("Coult not load cell!") } cell.host(content(indexPath, item)) return cell }class HostingControllerCollectionViewCell : UICollectionViewCell {
weak var controller: UIHostingController<Content>? func host(_ view: Content, parent: UIViewController? = nil) { if let controller = controller { controller.rootView = view controller.view.layoutIfNeeded() } else { let controller = UIHostingController(rootView: view) self.controller = controller controller.view.backgroundColor = .clear layoutIfNeeded() parent?.addChild(controller) contentView.addSubview(controller.view) controller.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ controller.view.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor), controller.view.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor), controller.view.topAnchor.constraint(equalTo: self.contentView.topAnchor), controller.view.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor) ]) if let parent = parent { controller.didMove(toParent: parent) } controller.view.layoutIfNeeded() } }}
UITableViewCell
class HostingTableViewControllerCell: UITableViewCell {
private weak var controller: UIHostingController<Content>? func host(_ view: Content, parent: UIViewController) { if let controller = controller { controller.rootView = view controller.view.layoutIfNeeded() } else { let swiftUICellViewController = UIHostingController(rootView: view) controller = swiftUICellViewController swiftUICellViewController.view.backgroundColor = .clear layoutIfNeeded() parent.addChild(swiftUICellViewController) contentView.addSubview(swiftUICellViewController.view) swiftUICellViewController.view.translatesAutoresizingMaskIntoConstraints = false contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1.0, constant: 0.0)) contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1.0, constant: 0.0)) contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 0.0)) contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: 0.0)) swiftUICellViewController.didMove(toParent: parent) swiftUICellViewController.view.layoutIfNeeded() } }}
or I have other solutions just with replacing subviews of contentView
class HostingTableViewCell: UITableViewCell {
func host<Content: View>(rootView: Content) {
self.contentView.subviews.forEach { $0.removeFromSuperview() }
let view = UIHostingController(rootView: rootView).view
view?.backgroundColor = .clear
self.contentView.addSubview(view!)
view!.preservesSuperviewLayoutMargins = true
view!.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view!.leadingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.leadingAnchor),
view!.trailingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.trailingAnchor),
view!.topAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.topAnchor),
view!.bottomAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.bottomAnchor)
])
}
}
Maybe should I register multiple HostingCells with specific view type and instead of replacing entire view try to somehow setTitle() setImage() on this SwiftUI content views.
private func cellProvider(collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? {
print("Providing cell for \(indexPath)")
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HostingControllerCollectionViewCell<CustomView>.reuseIdentifier, for: indexPath) as? HostingControllerCollectionViewCell<CustomView> else {
fatalError("Could not load cell!")
}
if cell.hostingController == nil {
cell.host(Text("LOL"))
} else {
cell.hostingController.rootView
cell.controller?.rootView?.setTitle("Text")
}
return cell
}
struct CustomView: View {
@State var text: String
func setTitle(_ text: String) { self.text = text }
}
But here question. How I could register multiple SwiftUI content views like CustomView1, CustomView2, CustomView3?
maybe at constraction time of add
CollectionView(reusableCells: [CustomView1.self, CustomView2.self],...)
and then in configureCollectionView() add
collectionView.register(HostingControllerCollectionViewCell.self, forCellWithReuseIdentifier: CustomViewX.reuseIdentifier)
but is this last suggested approach better and how I could pass View struct types and store then off different view type without not erasing type to AnyView
UPDATE
If I added to current soluton 100k items then app works very very slows, views are not layout properly and it seems to crash very frequently. So it seems that cells aren't reused properly.
static func generateCategories() -> [Item] {
var items = [Item]()
for i in 0..<100000 {
items.append(.category(category: Category(id: "\(i)", title: "Category \(i)")) )
}
return items
}