消除字符串作为重用标识符(AnyClass 问题)

huangapple go评论65阅读模式
英文:

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文档说明:

所有类类型隐式遵循的协议。

我理解协议ReuseIdentifiableAnyClass是完全不同的。我尝试像这样声明我的协议:

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) }
}

这将使 UITableViewCellUITableViewHeaderFooterView 遵循您的协议:

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&lt;T: UITableViewCell&gt;(_ : T.Type) {
        register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
    }

    func register&lt;T: UITableViewHeaderFooterView&gt;(_: T.Type) {
        register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
    }

    func dequeueReusableCell&lt;T: UITableViewCell&gt;(with identifier: String, for indexPath: IndexPath) -&gt; T {
        guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? T else {
            fatalError(&quot;Error dequeuing cell with identifier: &quot; + identifier)
        }
        return cell
    }

    func dequeueReusableCell&lt;T: UITableViewCell&gt;(for indexPath: IndexPath, cellType: T.Type = T.self) -&gt; T {
        let dequeueCell = dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath)
        guard let cell = dequeueCell as? T else {
            fatalError(&quot;Error dequeuing cell with identifier: &quot; + cellType.reuseIdentifier)
        }
        return cell
    }

    func dequeueReusableHeaderFooterView&lt;T: ReuseIdentifiable&gt;() -&gt; T {
        guard let cell = dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T else {
            fatalError(&quot;Error dequeuing HeaderFooter with identifier: &quot; + T.reuseIdentifier)
        }
        return cell
    }
}

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

huangapple
  • 本文由 发表于 2023年6月13日 03:15:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76459684.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定