我想构建一个带有UICollectionViewController
的iOS应用,每个行的单元格数始终相同。因为我不希望我的UICollectionViewController
处理太多事情,所以我重构了我的代码并实现了有趣的内容,例如protocol associatedtype
和泛型类型。现在,我的应用程序由4个不同的.swift
文件组成。
CustomFlowLayout.swift
CustomFlowLayout
是UICollectionViewFlowLayout
的一个简单子类,它允许我们通过依赖注入设置其minimumInteritemSpacing
,minimumLineSpacing
和sectionInset
属性,这要归功于便利初始化程序
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")
}
}
ColumnDataSource.swift
ColumnDataSource
是NSObject
的子类,符合UICollectionViewDataSource
,UICollectionViewDelegate
和UICollectionViewDelegateFlowLayout
。它的实现collectionView(_:layout:sizeForItemAt:)
是为了显示每行UICollectionViewCell
的正确数量。另请注意,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)
}
}
ColumnFlowLayoutable.swift
ColumnFlowLayoutable
协议的目的是确保符合条件的任何类具有columnDataSource
和customFlowLayout
属性columnDataSource
类型参数匹配customFlowLayout
类型。
import UIKit
protocol ColumnFlowLayoutable {
associatedtype FlowLayoutType: UICollectionViewFlowLayout
var columnDataSource: ColumnDataSource<FlowLayoutType> { get }
var customFlowLayout: FlowLayoutType { get }
}
CollectionViewController.swift
CollectionViewController
是UICollectionViewController
的子类,符合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
的子类。
如果我尝试在ColumnDataSource
中使用class SubColumnDataSource: ColumnDataSource<CustomFlowLayout>
的子类(例如CollectionViewController
)来构建项目,Xcode会抛出以下构建时错误消息:
输入&#39; CollectionViewController&#39;不符合协议&#39; ColumnFlowLayoutable&#39;
我需要在ColumnFlowLayoutable
协议中更改哪些内容才能允许CollectionViewController
使用ColumnDataSource
的子类?
答案 0 :(得分:4)
使用布局类型的关联类型创建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> { /* ... */ }