0

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.

Alexey_BH
  • 101
  • 7
  • I think you want a generic. – matt Jun 12 '23 at 19:20
  • I tried it before... unfortunately, the problem remains the same. – Alexey_BH Jun 12 '23 at 19:28
  • register(type(of: cellClass).self, forCellWithReuseIdentifier: type(of: cellClass).reuseIdentifier) tells error that this is ambiguous. – Alexey_BH Jun 12 '23 at 19:37
  • 1
    I personally use `func register(cell: T.Type) {register(cell, forCellWithReuseIdentifier: T.reuseIdentifier) }`, and in use it'd be `collectionView.register(GroupsViewCell.self)` and I make `extension GroupsViewCell: ReuseIndetifiable {}` – Larme Jun 12 '23 at 19:46
  • Thank you, I tried your code, but the problem remains.. may be something is wrong with my swift or settings? I'm usingSwift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100), target: arm64-apple-darwin22.5.0 – Alexey_BH Jun 12 '23 at 19:52
  • I used Generic which you didn't used in your sample, that's the diff. I think my code is enough in itself. For the dequeue it's `extension UICollectionView { func dequeueReusableCell(indexPath: IndexPath) -> T { guard let cell = dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T else { fatalError("Oops, couldn't dequeue cell of type \(T.self)") } return cell }}` – Larme Jun 12 '23 at 19:59
  • Thank you, but there are no issues with dequeueReusableCell.. It compiles ok in my case as well as in yours. The problem is in register function... I tried exactly your code of register func, but it tells me the same error. – Alexey_BH Jun 12 '23 at 20:10
  • @Larme if you mean that both functions should be implemented the same as in your example, I also tried doing this... Unfortunately, getting the same error... – Alexey_BH Jun 12 '23 at 20:14
  • 1
    Why this thread is downvoted? :-( I did my best to solve the problem by myself before posting it, also I didn't find the same questions were asked here... – Alexey_BH Jun 12 '23 at 20:41

1 Answers1

1

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.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Thank you so much, Leo! Everything works fine.. But can you please explain why I can't pass UICollectionVie conforming to ReuseIdentificable in extension, but I can elsewhere. Didn't catch that thing - and why I can't conform my ReuseIdentificable to AnyClass? Thank you. I will appreciate so much for explanation or any references on that. Unfortunately, Apple documentation on AnyClass hardly contains anything detailed. – Alexey_BH Jun 13 '23 at 05:42
  • Also, I noticed an issue with dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) function. Swift ignores its default value (T.self) asking this argument to be explicitly set (in that case all works fine). Trying to figure out why. – Alexey_BH Jun 13 '23 at 07:41
  • 1
    Well it is a generic method you need to cast the resulting to the desired type or explicitly set it. `let cell: YourCustomCell = tableView.dequeueReusableCell(for: indexPath)` – Leo Dabus Jun 13 '23 at 14:30