英文:
Getting rid of strings as reuse identifiers (AnyClass issue)
问题
使用字符串标识符来重用单元格会导致很多问题...我尝试摆脱它们(以这里描述的方式: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 {}
它运行正常:
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)
}
但我尝试更进一步 - 通过使用仅接受单元格类并执行所有操作的函数:
extension UICollectionView {
    
    // 错误:在调用实例方法“register”时没有精确匹配...
    func register(_ cellClass: ReuseIdentifiable) {
        register(
            cellClass.self,
            forCellWithReuseIdentifier: type(of: cellClass).reuseIdentifier
        )
    }
    
    // 一切正常
    func dequeueReusableCell(_ ofType: ReuseIdentifiable,
                             for indexPath: IndexPath) -> UICollectionViewCell {
        return dequeueReusableCell(
            withReuseIdentifier: type(of: ofType).reuseIdentifier, for: indexPath)
    }
}
Apple文档说明:
所有类类型隐式遵循的协议。
我理解协议ReuseIdentifiable和AnyClass是完全不同的。我尝试像这样声明我的协议:
protocol ReuseIdentifiable: AnyClass { // 在此处编写代码 }
但它引发错误:
从非协议、非类类型“AnyClass”(也称为“any AnyObject.Type”)继承
如何将我的cellClass传递给UICollectionView.dequeueReusableCell函数?因为除了扩展之外的任何其他地方都可以成功传递:
override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.register(
        GroupsViewCell.self,
        forCellWithReuseIdentifier: GroupsViewCell.reuseIdentifier
    )
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionLayout.layoutItems(in: self.collectionView)
}
谢谢您的支持。
英文:
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.
答案1
得分: 1
以下是翻译好的内容:
您可以按照以下方式实现您想要的内容:
public protocol ReuseIdentifiable: UIView {
    static var reuseIdentifier: String { get }
}
请注意,在静态属性扩展中的“Self”前缀是多余的,因此可以省略。
public extension ReuseIdentifiable {
    static var reuseIdentifier: String { .init(describing: self) }
}
这将使 UITableViewCell 和 UITableViewHeaderFooterView 遵循您的协议:
extension UITableViewCell: ReuseIdentifiable { }
extension UITableViewHeaderFooterView: ReuseIdentifiable { }
现在,您可以创建扩展 UITableView 的通用方法,如下所示:
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
    }
}
UICollectionView 的方法实现将留给问题提出者作为练习。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论