Collor
A declarative-ui framework for UICollectionView with great and useful features.
Install / Use
/learn @sncf-connect-tech/CollorREADME
About
Collor is a MVVM data-oriented framework for accelerating, simplifying and ensuring UICollectionView building.<br> Collor was created for and improved in the Voyages-sncf.com app.
Features
Here is the list of all the features:
- [x] Easy to use.
- [x] A readable collectionView model.
- [x] Architectured for reusing cell.
- [x] Protocol / Struct oriented.
- [x] Scalable.
- [x] Never use
IndexPath. - [x] Never register a cell.
- [x] Update the collectionView model easily.
- [x] Diffing data or section(s)
- [x] Diffing handles deletes, inserts, moves and updates
- [x] Manage decoration views in our custom layout easily.
- [x] Make easier building custom layout.
- [x] Swift 4 (use 1.0.x for swift 3 compatibility).
- [x] Well tested.
- [x] 🆕 Handle supplementary views (may be improved in next versions)
Getting started
- A medium article which explains the purpose and how to use Collor.
- Another medium article to understand the diffing feature.
- How to create your own API above UICollectionView with Collor
Example
To run the example project, clone the repo, and run pod install from the Example directory first.<br>
There are 4 examples:
- Menu : Simple collectionView with userEvent propagation example
- Random : Diffing entire data + custom layout
- Weather : Diffing sections + custom layout
- Pantone : Adding and remove items using CollectionDatas.
- RealTime : Complex diffing (insert, delete, reload) + custom layout handling with
DecorationViewHandler. - Alphabet : An example with a
supplementaryView+ custom layout handling withSupplementaryViewsHandler.
Usage
The UICollectionView is represented by a collectionData object which contains sectionDescriptors which contain themself cellDescriptors. Each item or cell in Collor is composed by 3 objects:
- The
UICollectionViewCell(XIB or not + swift file) which implementsCollectionCellAdaptable - A cellDescriptor which implements
CollectionCellDescribable - An adapter (view model) which implements
CollectionAdapter
CellDescriptor
It describes the cell and is the link between the cell and the viewModel. Logically, one type of cell needs only one cellDescriptor. It owns the cell identifier, the cell className and handles the size of the cell. The collectionData handles cell registering and dequeuing using these properties.
final class WeatherDayDescriptor: CollectionCellDescribable {
let identifier: String = "WeatherDayCollectionViewCell"
let className: String = "WeatherDayCollectionViewCell"
var selectable:Bool = false
let adapter: WeatherDayAdapter
init(adapter: WeatherDayAdapter) {
self.adapter = adapter
}
func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize {
let sectionInset = sectionDescriptor.sectionInset(collectionView)
let width:CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right
return CGSize(width:width, height:60)
}
public func getAdapter() -> CollectionAdapter {
return adapter
}
}
Adapter
An adapter is a viewModel object. It transforms your model in a human readable data used by the cell.
struct WeatherDayAdapter: CollectionAdapter {
let date: NSAttributedString
static let dateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "EEEE d MMMM"
return df
}()
init(day:WeatherDay) {
let dateString = WeatherDayAdapter.dateFormatter.string(from: day.date)
date = NSAttributedString(string: dateString, attributes: [
NSFontAttributeName: UIFont.boldSystemFont(ofSize: 18),
NSForegroundColorAttributeName: UIColor.black
])
}
}
When a cell is dequeued, the collectionData updates the cell with this object.
final class WeatherDayCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable {
@IBOutlet weak var label: UILabel!
func update(with adapter: CollectionAdapter) {
guard let adapter = adapter as? WeatherDayAdapter else {
fatalError("WeatherDayAdapter required")
}
label.attributedText = adapter.date
}
}
ViewController
Create the dataSource and the delegate:
lazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: self)
lazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: self)
Create the collectionData:
let collectionData = MyCollectionData()
Bind the collectionView with the data, the datasource and the delegate:
bind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource)
Create a section and one cell in the collectionData:
final class MyCollectionData : CollectionData {
override func reloadData() {
super.reloadData()
let section = MySectionDescriptor().reloadSection { (cells) in
let cell = MyColorDescriptor(adapter: MyColorAdapter(model: "someThing"))
cells.append(cell)
})
}
sections.append(section)
}
}
MySectionDescriptor:
final class MySectionDescriptor : CollectionSectionDescribable {
func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
}
}
You get a readable data which represents your UICollectionView, without code duplication, reusing cell and with a good separation of code.
SupplementatyView
To add a supplementaryView, instead of using CollectionSectionDescribable.reloadSection(:), call CollectionSectionDescribable.reload(:).
The builder passed as parameter exposes new method to add a supplementaryView easily.
let section = MySectionDescriptor().reload { builder in
let letterAdapter = LetterAdapter(letter: "A")
let letterDescriptor = LetterCollectionReusableViewDescriptor(adapter: letterAdapter)
builder.add(supplementaryView: letterDescriptor, kind: "letter")
}
sections.append(section)
CollectionSupplementaryViewDescribable behaves like a CollectionCellDescribable. It needs an adapter to fill the view with data.
But, for displaying a supplementaryView correctly, a custom layout is required in order to set attributes for the supplementaryViews created in the CollectionData. As with decorationViews, Collor provides a SupplementaryViewsHandler` object to manage supplementaryViews. It handles addition, caching and updatating.
class Layout : UICollectionViewFlowLayout {
//...
override func prepare() {
super.prepare()
supplementaryViewsHandler.prepare()
for (sectionIndex, sectionDescriptor) in datas.sections.enumerated() {
let firstCellIndexPath = IndexPath(item: 0, section: sectionIndex)
let firstCellAttributes = layoutAttributesForItem(at: firstCellIndexPath)!
sectionDescriptor.supplementaryViews.forEach { (kind, views) in
views.enumerated().forEach { (index, viewDescriptor) in
let indexPath = IndexPath(item: index, section: sectionIndex)
let a = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: kind, with: indexPath)
a.frame = viewDescriptor.frame(collectionView, sectionDescriptor: sectionDescriptor)
a.frame.origin.y += firstCellAttributes.frame.origin.y
supplementaryViewsHandler.add(attributes: a)
}
}
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
let supplementaryAttributes = supplementaryViewsHandler.attributes(in: rect)
if let attributes = attributes {
return attributes + supplementaryAttributes
}
return attributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return supplementaryViewsHandler.attributes(for: elementKind, at: indexPath)
}
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
super.prepare(forCollectionViewUpdates: updateItems)
supplementaryViewsHandler.prepare(forCo
Related Skills
node-connect
342.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
85.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
342.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
