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 here
UIStoryBoard
To create theUICollectionViewController
- Only two proxy methods are needed in the default layout
collectionView
The 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 implemented
UICollectionViewDelegateFlowLayout
Lay it out, or just get itUICollectionViewController
thecollectionViewLayout
Do 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 suggests
tableView
Again, 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, like
willDisplayCell
Will 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 definition
layout
- I wrote a protocol to implement each with a back-and-forth call
cell
Layout 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
- 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
- RxCollectionViewSectionedReloadDataSource create a data source, and specify the type, I am a direct use of RxDataSource SectionModel built-in types
- 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
- Rx is used to transform data into signals, which will be automatically reloaded every time the data is updated
- 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
- Is structure struct SwiftUI controller need to implement UIViewControllerRepresentable agreement
struct SwiftUICollectionView: UIViewControllerRepresentable
- You need to implement
makeUIViewController
updateUIViewController
Methods are linked through a context - If you need to implement the proxy, you can use the class branch class implementation
makeCoordinator
Method that returns the class of the proxy and sets the class that needs the proxycontroller.collectionView.delegate = context.coordinator
UIHostingController
Is the method to convert SwiftUI to Controller
Some considerations & Some problems encountered:
- If in use, only one
cell
In the case of trying to play the layout, cancel auto getcell
Size, 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