【问题标题】:Swift protocol associated types and inheritance contraintsSwift 协议相关类型和继承约束
【发布时间】:2017-01-01 17:24:54
【问题描述】:

我想使用UICollectionViewController 构建一个iOS 应用程序,每行始终具有相同数量的单元格。因为我不希望我的UICollectionViewController 处理太多事情,所以我重构了我的代码并实现了有趣的事情,比如protocol associatedtype 和泛型类型。现在,我的应用由 4 个不同的 .swift 文件组成。


1。 CustomFlowLayout.swift

CustomFlowLayoutUICollectionViewFlowLayout 的一个简单子类,借助便利的初始化程序,它允许我们通过依赖注入设置其minimumInteritemSpacingminimumLineSpacingsectionInset 属性。

import UIKit

class CustomFlowLayout: UICollectionViewFlowLayout {

    convenience init(minimumInteritemSpacing: CGFloat = 0,
                     minimumLineSpacing: CGFloat = 0,
                     sectionInset: UIEdgeInsets = .zero) {
        self.init()
        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
    }

    override init() {
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

2。 ColumnDataSource.swift

ColumnDataSourceNSObject 的子类,符合UICollectionViewDataSourceUICollectionViewDelegateUICollectionViewDelegateFlowLayout。它实现了collectionView(_:layout:sizeForItemAt:),以便每行显示正确的UICollectionViewCells 数量。另请注意,ColumnDataSource 是一个泛型类,需要我们在初始化时向其传递类型参数。

import UIKit

class ColumnDataSource<FlowLayoutType: UICollectionViewFlowLayout>: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

    let cellsPerRow: Int

    init(cellsPerRow: Int) {
        self.cellsPerRow = cellsPerRow
        super.init()
    }

    // MARK: - UICollectionViewDataSource

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
    }

    // MARK: - UICollectionViewDelegateFlowLayout

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let flowLayout = collectionView.collectionViewLayout as! FlowLayoutType
        let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + flowLayout.minimumInteritemSpacing * (CGFloat(cellsPerRow) - 1)
        let itemWidth = (collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)
        return CGSize(width: itemWidth, height: itemWidth)
    }

}

3。 ColumnFlowLayoutable.swift

ColumnFlowLayoutable 协议的目的是确保任何符合它的类都具有columnDataSourcecustomFlowLayout 属性,其中columnDataSource 类型的类型参数与customFlowLayout 类型匹配。

import UIKit

protocol ColumnFlowLayoutable {

    associatedtype FlowLayoutType: UICollectionViewFlowLayout
    var columnDataSource: ColumnDataSource<FlowLayoutType> { get }
    var customFlowLayout: FlowLayoutType { get }

}

4。 CollectionViewController.swift

CollectionViewControllerUICollectionViewController 的子类,符合ColumnFlowLayoutable 协议。它还实现了viewWillTransition(to:with:),以处理容器大小的变化。

import UIKit

class CollectionViewController: UICollectionViewController, ColumnFlowLayoutable {

    let columnDataSource = ColumnDataSource<CustomFlowLayout>(cellsPerRow: 2)
    let customFlowLayout = {
        CustomFlowLayout(minimumInteritemSpacing: $0, minimumLineSpacing: $0, sectionInset: UIEdgeInsets(top: $0, left: $0, bottom: $0, right: $0))
    }(10)

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView?.collectionViewLayout = customFlowLayout
        collectionView?.dataSource = columnDataSource
        collectionView?.delegate = columnDataSource
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        collectionView?.collectionViewLayout.invalidateLayout()
    }

}

完整的项目可以在这个 Github 仓库找到:CollectionViewColumnsProtocol


此代码运行良好。我可以将它与CustomFlowLayout 的子类一起使用,它仍然有效。 但是,我不能将它与 ColumnDataSource 的子类一起使用

如果我尝试在CollectionViewController 中使用ColumnDataSource 的子类(例如class SubColumnDataSource: ColumnDataSource&lt;CustomFlowLayout&gt;)来构建项目,Xcode 会抛出以下构建时错误消息:

类型“CollectionViewController”不符合协议“ColumnFlowLayoutable”

为了让CollectionViewController 能够与ColumnDataSource 的子类一起工作,我必须对ColumnFlowLayoutable 协议进行哪些更改?

【问题讨论】:

标签: ios swift generics uicollectionview protocols


【解决方案1】:

为您的布局类型创建一个具有关联类型的 DataSource 协议:

protocol ColumnDataSourceProtocol: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    associatedtype Layout: UICollectionViewFlowLayout
}

使您的 DataSource 基类符合此协议。如果编译器无法推断,您可能需要添加 typealias 来指定关联类型:

class ColumnDataSource<FlowLayoutType: UICollectionViewFlowLayout>: NSObject, ColumnDataSourceProtocol {

    typealias Layout = FlowLayoutType

    // the rest stays the same
}

调整ColumnFlowLayoutable 以关联数据源类型而不是布局类型。将其限制为 ColumnDataSourceProtocol 可让您访问其关联的 Layout 类型:

protocol ColumnFlowLayoutable {

    associatedtype DataSource: ColumnDataSourceProtocol

    var columnDataSource: DataSource { get }
    var customFlowLayout: DataSource.Layout { get }
}

现在你可以继承ColumnDataSource:

class DataSource: ColumnDataSource<CustomFlowLayout> { /* ... */ }

【讨论】:

  • 谢谢。我错过了这一点:协议可以继承自其他协议。
  • 非常感谢,您的回答让我从医生潜水中解脱出来)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多