swiftuitableviewuicollectionviewreuseidentifier

Getting rid of strings as reuse identifiers (AnyClass issue)


Using string identifiers for reuse cells cause a lot of problems... I tried getting rid of them (in way described here: https://medium.com/bleeding-edge/nicer-reuse-identifiers-with-protocols-in-swift-97d18de1b2df):

protocol ReuseIdentifiable {
    static var reuseIdentifier: String { get }
}

extension ReuseIdentifiable {
    static var reuseIdentifier: String {
        String(describing: Self.self)
    }
}
 
extension UICollectionViewCell: ReuseIdentifiable {}
extension UITableViewCell: ReuseIdentifiable {}

It works fine:

   override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(
        withReuseIdentifier: GroupsViewCell.reuseIdentifier,
        for: indexPath
    )
    return cell
}



override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.register(
            GroupsViewCell.self,
            forCellWithReuseIdentifier: GroupsViewCell.reuseIdentifier
        )
        
        collectionView.delegate = self
        collectionView.dataSource = self
        
        collectionLayout.layoutItems(in: self.collectionView)

    }

But I tried to go a bit further - by using functions that take only cell class and do all the stuff:

  extension UICollectionView {

        // Error: no exact matches in call to instance method 'register'...
        func register(_ cellClass: ReuseIdentifiable) {
            register(
                cellClass.self,
                forCellWithReuseIdentifier: type(of: cellClass).reuseIdentifier
            )
        }
        
        // All ok
        func dequeueReusableCell(_ ofType: ReuseIdentifiable,
                                 for indexPath: IndexPath) -> UICollectionViewCell {
            return dequeueReusableCell(
               withReuseIdentifier: type(of: ofType).reuseIdentifier, for: indexPath)
            }

    }

Apple documentation tells that:

The protocol to which all class types IMPLICITLY conform.

I understand that protocol ReuseIdentifiable and AnyClass are really different guys.. I tried to declare my protocol like this:

protocol ReuseIdentifiable: AnyClass { // code here }

but it causes error:

Inheritance from non-protocol, non-class type 'AnyClass' (aka 'any AnyObject.Type')

How can I pass my cellClass to UICollectionView.dequeueReusableCell function?

Because in any other place except extension it is being passed successfully:

override func viewDidLoad() {
    super.viewDidLoad()

    collectionView.register(
        GroupsViewCell.self,
        forCellWithReuseIdentifier: GroupsViewCell.reuseIdentifier
    )
    
    collectionView.delegate = self
    collectionView.dataSource = self
    
    collectionLayout.layoutItems(in: self.collectionView)

}

Thank you for support.


Solution

  • You can accomplish what you want as follow:

    public protocol ReuseIdentifiable: UIView {
        static var reuseIdentifier: String { get }
    }
    

    Note that Self prefix inside a static property extension is redundant therefore can be omitted.

    public extension ReuseIdentifiable {
        static var reuseIdentifier: String { .init(describing: self) }
    }
    

    This will make UITableViewCell and UITableViewHeaderFooterView conform to your protocol:

    extension UITableViewCell: ReuseIdentifiable { }
    extension UITableViewHeaderFooterView: ReuseIdentifiable { }
    

    Now you can create the generic methods extending UITableview as follow:

    public extension UITableView {
        func register<T: UITableViewCell>(_ : T.Type) {
            register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
        }
    
        func register<T: UITableViewHeaderFooterView>(_: T.Type) {
            register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
        }
    
        func dequeueReusableCell<T: UITableViewCell>(with identifier: String, for indexPath: IndexPath) -> T {
            guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? T else {
                fatalError("Error dequeuing cell with identifier: " + identifier)
            }
            return cell
        }
    
        func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath, cellType: T.Type = T.self) -> T {
            let dequeueCell = dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath)
            guard let cell = dequeueCell as? T else {
                fatalError("Error dequeuing cell with identifier: " + cellType.reuseIdentifier)
            }
            return cell
        }
    
        func dequeueReusableHeaderFooterView<T: ReuseIdentifiable>() -> T {
            guard let cell = dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T else {
                fatalError("Error dequeuing HeaderFooter with identifier: " + T.reuseIdentifier)
            }
            return cell
        }
    }
    

    The UICollectionView methods implementation will leave for the OP as an excercise.