This article has participated in the third “topic writing” track of the Denver Creators Training Camp. For details, check out: Digg Project | Creators Training Camp third is ongoing, “write” to make a personal impact.

1. Simple implementation and use of UICollectionView

  • I’m going to use it directly hereUIStoryBoardTo create theUICollectionViewController
  • Only two proxy methods are needed in the default layoutcollectionViewThe presentation of
override func collectionView(_ collectionView: UICollectionView.numberOfItemsInSection section: Int) -> Int {
    return dataSource.count
}

override func collectionView(_ collectionView: UICollectionView.cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CollectionViewCell
    DispatchQueue.main.async {
        cell.image.image = self.dataSource[indexPath.row]
    }
    return cell
}
Copy the code
  • But here shows the way the system’s default layout is implementedUICollectionViewDelegateFlowLayoutLay it out, or just get itUICollectionViewControllerthecollectionViewLayoutDo the attribute assignment
func collectionView(_ collectionView: UICollectionView.layout collectionViewLayout: UICollectionViewLayout.minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    5
}

func collectionView(_ collectionView: UICollectionView.layout collectionViewLayout: UICollectionViewLayout.minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
    5
}


func collectionView(_ collectionView: UICollectionView.layout collectionViewLayout: UICollectionViewLayout.insetForSectionAt section: Int) -> UIEdgeInsets{.init(top: 5, left: 5, bottom: 5, right: 5)}func collectionView(_ collectionView: UICollectionView.layout collectionViewLayout: UICollectionViewLayout.sizeForItemAt indexPath: IndexPath) -> CGSize {
    let size = UIImage(named: "\(indexPath.row)")?.size ?? .zero
    let kSize = collectionView.frame.size
    let width = (kSize.width - 15) / 2
    let height = width * 1.25
    return CGSize(width: width, height: height)
}
Copy the code

The data source

  • And, as the name suggeststableViewAgain, you need an array, which is simply the source of the data
  • For example, the number of cells to display, the style to display, move, delete, set header, footer, etc
// The number of sections returned
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;

// The number of cells per seciton
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;

// Cell implementation
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;

// Custom header/footer usage
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
Copy the code

The agent

  • As the name implies, the display cell will display some events that will be processed later
  • Some commonly used, likewillDisplayCellWill display, click, highlight events, etc
// Is the cell clickable
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;

// If this method is implemented, the above method must be implemented
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;

// called when the cell is about to be displayed - similar to the UICollectionView lifecycle that can be seen from the proxy
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(8.0));

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
Copy the code

The customlayout

  • Here I made an implementation of waterfall flow, by definitionlayout
  1. I wrote a protocol to implement each with a back-and-forth callcellLayout information of
protocol CollectionViewLayoutDelegate: NSObject {
    func itemHeight(layout: CollectionViewLayout.indexPath: IndexPath.itemWith: CGFloat) -> CGFloat
    func itemColumnCount(layout: CollectionViewLayout) -> Int
    func itemColumnSpcing(layout: CollectionViewLayout) -> CGFloat
    func itemRowSpcing(layout: CollectionViewLayout) -> CGFloat
    func itemEdgeInsetd(layout: CollectionViewLayout) -> UIEdgeInsets
    // todo .. Of course, you can also customize more agents, see your own needs need ha

}
Copy the code
  1. Write layout from UICollectionViewLayout
  • Core method
// Call, initialize, restore parameters when cell is reused
override func prepare(a){
    super.prepare()
    
    contentHeight = 0

    itemWidth  = ((self.collectionView?.frame.width ?? 0) - CGFloat(itemColumnCount + 1) * itemColumnSpcing) / CGFloat(itemColumnCount)
    colsHeight = Array(repeating: 0.0, count: itemColumnCount)
    var array = [UICollectionViewLayoutAttributes] ()let items = collectionView?.numberOfItems(inSection: 0) ?? 0

    for index in 0..<items {
        if  let attr = layoutAttributesForItem(at: IndexPath(item: index, section: 0)){
            array.append(attr)
        }
    }
    layoutAttributes = array
}

override var collectionViewContentSize: CGSize {
    CGSize(width: 0, height: contentHeight)
}

override func layoutAttributesForElements(in rect: CGRect)- > [UICollectionViewLayoutAttributes]? {
    layoutAttributes
}

// Override the layout attribute # core # for each cell
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

    let arrt = UICollectionViewLayoutAttributes(forCellWith: indexPath)
    var shorHeight = colsHeight.first ?? 0
    var shortCol = 0

    for (index, temp) in colsHeight.enumerated() {
        if shorHeight > temp {
            shorHeight = temp
            shortCol = index
        }
    }

    let x = CGFloat(shortCol + 1) * itemColumnSpcing + CGFloat(shortCol) * itemWidth
    let y = shorHeight + itemColumnSpcing
    let height = delegate?.itemHeight(layout: self, indexPath: indexPath, itemWith: itemWidth) ?? 0

    arrt.frame = .init(x: x, y: y, width: itemWidth, height: height)
    colsHeight[shortCol] = arrt.frame.maxY

    let maxColHeight = colsHeight[shortCol]

    if contentHeight < maxColHeight {
        contentHeight = maxColHeight
    }
    return arrt
}

override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    true
}
Copy the code
  • Implementation of the primary agent
weak var delegate: CollectionViewLayoutDelegate?

var itemColumnCount: Int {
    delegate?.itemColumnCount(layout: self) ?? 2
}

var itemColumnSpcing: CGFloat {
    delegate?.itemColumnSpcing(layout: self) ?? 0
}

var itemRowSpcing: CGFloat {
    delegate?.itemRowSpcing(layout: self) ?? 0
}
Copy the code
  • See demo for details

2. UICollectionView in RxDataSource

import UIKit
import RxDataSources
import RxSwift


class RxCollectionViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var layout: UICollectionViewFlowLayout!

   
    var datas = (0..<39).sorted().map { UIImage(named: "\ [$0)" )}.compactMap { $0 }

    let disposeBag = DisposeBag(a)override func viewDidLoad(a) {

        super.viewDidLoad()

        // Use layout directly, no protocol required
        layout.estimatedItemSize = .zero
        layout.itemSize = CGSize(width: (collectionView.frame.width - 60) / 3, height: (collectionView.frame.width - 60) / 3 * 1.25)
        layout.minimumLineSpacing = 10
        layout.minimumInteritemSpacing = 10
        layout.sectionInset = .init(top: 10, left: 5, bottom: 10, right: 5)

        // Set the data source
        let dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel<String.UIImage>>(configureCell: { (ds, cv, ip, item) -> UICollectionViewCell in

            let cell = cv.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: ip) as! CollectionViewCell
            DispatchQueue.main.async {
                cell.image.image = item
            }

            return cell
        })

       // Convert data into signals
        Observable"[UIImage]>.just(datas)
            .map { [SectionModel(model: "", items: $0)] }
            .asDriver(onErrorJustReturn: [])
            .drive(collectionView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)

        // Click the event
        collectionView.rx.modelSelected(UIImage.self)
            .subscribe(onNext: { model in
                print(model)
            })
            .disposed(by: disposeBag)
    }
}
Copy the code

RxDataSource usage tips

  1. RxCollectionViewSectionedReloadDataSource create a data source, and specify the type, I am a direct use of RxDataSource SectionModel built-in types
  2. To customize SectionModel, you need to inherit it, with a String argument representing the header information and an item representing the Model type of each cell
  3. Rx is used to transform data into signals, which will be automatically reloaded every time the data is updated
  4. Cell clicking also takes only one method to solve

RxSwift (RxSwift

3. Extend the use of SwiftUI in UICollectionView

  • SwiftUI is a new UI development framework, but there is no layout control similar to UICollectionView. If you need stack and List to implement, it is still quite troublesome, so we can directly use bridge
import SwiftUI


struct SwiftUICollectionView: UIViewControllerRepresentable {
    typealias UIViewControllerType = CollectionViewController

    func makeUIViewController(context: Context) -> CollectionViewController {
        guard let controller = UIStoryboard(name: "Main", bundle: nil)
                .instantiateViewController(withIdentifier: "CollectionViewController") as? CollectionViewController else {

            return CollectionViewController()}// setting Coordinator
// context.coordinator = ..
        return controller
    }

    func updateUIViewController(_ uiViewController: CollectionViewController.context: Context) {
        // You can modify the data source here
    }

    func makeCoordinator(a) -> Coordinator {
        Coordinator()}class Coordinator: NSObject.UICollectionViewDelegate {
        // todo:}}Copy the code
let controller = UIHostingController(rootView: SwiftUICollectionView())
show(controller, sender: nil)
Copy the code

SwiftUI and UIKit conversion

  1. Is structure struct SwiftUI controller need to implement UIViewControllerRepresentable agreementstruct SwiftUICollectionView: UIViewControllerRepresentable
  2. You need to implementmakeUIViewController updateUIViewControllerMethods are linked through a context
  3. If you need to implement the proxy, you can use the class branch class implementationmakeCoordinatorMethod that returns the class of the proxy and sets the class that needs the proxycontroller.collectionView.delegate = context.coordinator
  4. UIHostingControllerIs the method to convert SwiftUI to Controller

Some considerations & Some problems encountered:

  • If in use, only onecellIn the case of trying to play the layout, cancel auto getcellSize, can be solved; Or rewritelayout, special processing when there is only one

Swift grammar

// Swift uses weak proxies directly to prevent cyclic references. ? It stands for optional type, which means it can be nil
weak var delegate: CollectionViewLayoutDelegate?

// The default get method, which omits get, retrun, etc., can ignore return if there is only one statement in Swift, and this statement is the return value
/ /?? If the optional property is nil, it can be replaced by 2
// Check whether the delegate implementation is more economical than oc each time
var itemColumnCount: Int {
    delegate?.itemColumnCount(layout: self) ?? 2
}

// Complete
var itemColumnCount: Int {
    get {
        if let delegate = delegate {
            return delegate.itemColumnCount(layout: self)}return 2}}// return can be ignored
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
  true
}

// The sorted int array is a sorted array. Map (Swift high order syntax) is a format closure that converts ints to UIImage arrays {} representing each data conversion
let dataSource = (0..<39).sorted().map { UIImage(named: "\ [$0)" )}

// 强转 as as! as? 
(collectionViewLayout as! CollectionViewLayout).delegate = self
Copy the code

CollectionViewDemo