簡體   English   中英

Swift協議關聯的類型和繼承約束

[英]Swift protocol associated types and inheritance contraints

我想用UICollectionViewController構建一個iOS應用,該應用總是每行具有相同數量的單元格。 因為我不想讓UICollectionViewController處理太多事情,所以我重構了代碼並實現了有趣的事情,例如protocol associatedtype和泛型。 現在,我的應用程序由4個不同的.swift文件組成。


1. CustomFlowLayout.swift

CustomFlowLayout是一個簡單的子類UICollectionViewFlowLayout ,讓我們來設置其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:)以便每行顯示正確的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)
    }

}

3. ColumnFlowLayoutable.swift

ColumnFlowLayoutable協議的目的是確保符合該協議的任何類都具有columnDataSourcecustomFlowLayout屬性,其中columnDataSource類型的type參數與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子類CustomFlowLayout ,並且仍然可以使用。 但是,我不能將其與ColumnDataSource子類一起使用

如果我嘗試通過使用CollectionViewControllerColumnDataSource的子類(例如, class SubColumnDataSource: ColumnDataSource<CustomFlowLayout> )來構建項目,則Xcode會引發以下構建時錯誤消息:

類型“ CollectionViewController”不符合協議“ ColumnFlowLayoutable”

為了允許CollectionViewController使用ColumnDataSource子類,我必須在ColumnFlowLayoutable協議中進行哪些更改?

創建具有與您的布局類型關聯的類型的數據源協議:

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> { /* ... */ }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM