Collor is a MVVM data-oriented framework for accelerating, simplifying and ensuring UICollectionView building.
Collor was created for and improved in the Voyages-sncf.com app.
Here is the list of all the features:
- Easy to use.
- A readable collectionView model.
- Architectured for reusing cell.
- Protocol / Struct oriented.
- Scalable.
- Never use
IndexPath
. - Never register a cell.
- Update the collectionView model easily.
- Diffing data or section(s)
- 🆕 Diffing handles deletes, inserts, moves and updates
- 🆕 Manage decoration views in our custom layout easily.
- Make easier building custom layout.
- Swift 4 (use 1.0.x for swift 3 compatibility).
- Well tested.
- A medium article which explains the purpose and how to use Collor.
- Another medium article to understand the diffing feature.
To run the example project, clone the repo, and run pod install
from the Example directory first.
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
.
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 + swift file) which implementsCollectionCellAdaptable
- A cellDescriptor which implements
CollectionCellDescribable
- An adapter (view model) which implements
CollectionAdapter
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
}
}
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
}
}
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.
Collor provides some features to easily update your collectionData.
Just append or remove cells or sections using CollectionData.update(_:)
method. This means an end to fiddling around with IndexPath
:
let newCellDescriptor = NewCellDescriptor(...)
let result = collectionData.update { updater in
updater.append(cells: [newCellDescriptor], after: anotherCellDescriptor)
}
collectionView.performUpdates(with: result)
Here is the list of all update methods available:
- append(cells:after:)
- append(cells:before:)
- append(cells:in:)
- remove(cells:)
- reload(cells:)
- append(sections:after:)
- append(sections:before:)
- append(sections:)
- remove(sections:)
- reload(sections:)
Collor is using a home made algorithm for getting the "diff" between two updates of your collectionData.
- Diffing some sections:
sectionDescriptor.isExpanded = !sectionDescriptor.isExpanded
let result = collectionData.update{ updater in
updater.diff(sections: [sectionDescriptor])
}
collectionView.performUpdates(with: result)
- Diffing entire data
model.someUpdates()
let result = collectionData.update { updater in
collectionData.reload(model: model)
updater.diff()
}
collectionView.performUpdates(with: result)
- Effortless management of decoration views
With DecorationViewHandler
, you no longer need to implement code to manage your decoration views:
// register decoration view or class:
decorationViewHandler.register(viewClass: SimpleDecorationView.self, for: sectionBackgroundKind)
// caching
decorationViewHandler.add(attributes: backgroundAttributes)
// compute elements in rect
decorationViewHandler.attributes(in:rect)
// retrieving
decorationViewHandler.attributes(for: elementKind, at: atIndexPath)
// update handling
decorationViewHandler.prepare(forCollectionViewUpdates: updateItems)
return decorationViewHandler.inserted(for: elementKind)
return decorationViewHandler.deleted(for: elementKind)
For more information, have a look at this medium article.
Collor is published with 3 xctemplates for helping you creating ViewController, SectionDescriptor and CellDescriptor.
To install them, just go in xctemplates directory and run this command in a terminal:
sh install.sh
- iOS 8.0+
- Swift 4.0+ (get the 1.0.3 release for swift3)
- Xcode 9.0+
Collor is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod "Collor"
Collor doesn't yet support Carthage. Work in progress...
Work in progress... 1% documented
Collor is owned and maintained by Voyages-sncf.com.
Collor was originally created by Gwenn Guihal.
Collor is available under the BSD license. See the LICENSE file for more info.