{"id":13990709,"url":"https://github.com/sncf-connect-tech/Collor","last_synced_at":"2025-07-22T13:31:12.742Z","repository":{"id":19237521,"uuid":"83295128","full_name":"sncf-connect-tech/Collor","owner":"sncf-connect-tech","description":"A declarative-ui framework for UICollectionView with great and useful features.","archived":false,"fork":false,"pushed_at":"2023-04-12T05:27:31.000Z","size":19024,"stargazers_count":187,"open_issues_count":9,"forks_count":20,"subscribers_count":17,"default_branch":"master","last_synced_at":"2024-04-13T16:08:03.374Z","etag":null,"topics":["cocoapods","declarative","declarative-ui","decoration-views","descriptor","diffing","diffing-data","ios","mvvm","supplementary-views","swift","uicollectionview","viewmodel"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sncf-connect-tech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-27T09:58:07.000Z","updated_at":"2024-06-05T16:55:35.854Z","dependencies_parsed_at":"2024-06-05T16:55:31.483Z","dependency_job_id":"0b555259-7ab8-4509-83e0-81e84837f589","html_url":"https://github.com/sncf-connect-tech/Collor","commit_stats":null,"previous_names":["voyages-sncf-technologies/collor"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sncf-connect-tech%2FCollor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sncf-connect-tech%2FCollor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sncf-connect-tech%2FCollor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sncf-connect-tech%2FCollor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sncf-connect-tech","download_url":"https://codeload.github.com/sncf-connect-tech/Collor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227098958,"owners_count":17730683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cocoapods","declarative","declarative-ui","decoration-views","descriptor","diffing","diffing-data","ios","mvvm","supplementary-views","swift","uicollectionview","viewmodel"],"created_at":"2024-08-09T13:03:08.294Z","updated_at":"2024-11-29T10:31:32.490Z","avatar_url":"https://github.com/sncf-connect-tech.png","language":"Swift","readme":"\u003cbr/\u003e\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/voyages-sncf-technologies/Collor/master/resources/logo.png\" alt=\"Collor logo\"\u003e\u003c/p\u003e\n\n[![CI Status](https://travis-ci.org/voyages-sncf-technologies/Collor.svg?branch=master)](https://travis-ci.org/voyages-sncf-technologies/Collor/)\n[![Coverage Status](https://coveralls.io/repos/github/voyages-sncf-technologies/Collor/badge.svg?branch=master)](https://coveralls.io/github/voyages-sncf-technologies/Collor?branch=master)\n[![Version](https://img.shields.io/cocoapods/v/Collor.svg?style=flat)](http://cocoapods.org/pods/Collor)\n[![License](https://img.shields.io/cocoapods/l/Collor.svg?style=flat)](http://cocoapods.org/pods/Collor)\n[![Platform](https://img.shields.io/cocoapods/p/Collor.svg?style=flat)](http://cocoapods.org/pods/Collor)\n\n## About\n\nCollor is a MVVM data-oriented framework for accelerating, simplifying and ensuring UICollectionView building.\u003cbr\u003e\nCollor was created for and improved in the Voyages-sncf.com app.\n\n## Features\n\nHere is the list of all the features:\n- [x] Easy to use.\n- [x] A readable collectionView model.\n- [x] Architectured for reusing cell.\n- [x] Protocol / Struct oriented.\n- [x] Scalable.\n- [x] Never use ```IndexPath```.\n- [x] Never register a cell.\n- [x] Update the collectionView model easily.\n- [x] Diffing data or section(s)\n- [x] Diffing handles *deletes*, *inserts*, *moves* and *updates*\n- [x] Manage decoration views in our custom layout easily.\n- [x] Make easier building custom layout.\n- [x] Swift 4 (use 1.0.x for swift 3 compatibility).\n- [x] Well tested.\n- [x] 🆕 **Handle supplementary views** (may be improved in next versions)\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/voyages-sncf-technologies/Collor/master/resources/random.gif\" alt=\"Collor Random Sample\"\u003e \u003cimg src=\"https://raw.githubusercontent.com/voyages-sncf-technologies/Collor/master/resources/weather.gif\" alt=\"Collor Weather Sample\"\u003e\u003c/p\u003e\n\n\n## Getting started\n- A [medium article](https://medium.com/p/b55e73d81a59/) which explains the purpose and how to use Collor.\n- Another [medium article](https://medium.com/p/8f37064de388/) to understand the diffing feature.\n- [How to create your own API above UICollectionView with Collor](https://medium.com/@_myrddin_/create-your-own-uicollectionview-api-d13aea5ae642)\n\n## Example\n\nTo run the example project, clone the repo, and run `pod install` from the Example directory first.\u003cbr\u003e\nThere are 4 examples:\n- Menu : Simple collectionView with userEvent propagation example\n- Random : Diffing entire data + custom layout\n- Weather : Diffing sections + custom layout\n- Pantone : Adding and remove items using CollectionDatas.\n- RealTime : Complex diffing (insert, delete, reload) + custom layout handling with `DecorationViewHandler`.\n- Alphabet : An example with a `supplementaryView` + custom layout handling with `SupplementaryViewsHandler`.\n\n## Usage\n\nThe UICollectionView is represented by a collectionData object which contains sectionDescriptors which contain themself cellDescriptors.\nEach item or cell in Collor is composed by 3 objects:\n- The ```UICollectionViewCell``` (XIB or not + swift file) which implements ```CollectionCellAdaptable```\n- A cellDescriptor which implements ```CollectionCellDescribable```\n- An adapter (view model) which implements ```CollectionAdapter```\n\n##### CellDescriptor\nIt 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.\nThe collectionData handles cell registering and dequeuing using these properties.\n```swift\nfinal class WeatherDayDescriptor: CollectionCellDescribable {\n\n    let identifier: String = \"WeatherDayCollectionViewCell\"\n    let className: String = \"WeatherDayCollectionViewCell\"\n    var selectable:Bool = false\n\n    let adapter: WeatherDayAdapter\n\n    init(adapter: WeatherDayAdapter) {\n        self.adapter = adapter\n    }\n\n    func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -\u003e CGSize {\n        let sectionInset = sectionDescriptor.sectionInset(collectionView)\n        let width:CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right\n        return CGSize(width:width, height:60)\n    }\n\n    public func getAdapter() -\u003e CollectionAdapter {\n        return adapter\n    }\n}\n```\n\n##### Adapter\nAn adapter is a viewModel object. It transforms your model in a human readable data used by the cell.\n```swift\nstruct WeatherDayAdapter: CollectionAdapter {\n\n    let date: NSAttributedString\n\n    static let dateFormatter: DateFormatter = {\n        let df = DateFormatter()\n        df.dateFormat = \"EEEE d MMMM\"\n        return df\n    }()\n\n    init(day:WeatherDay) {\n\n        let dateString = WeatherDayAdapter.dateFormatter.string(from: day.date)\n        date = NSAttributedString(string: dateString, attributes: [\n            NSFontAttributeName: UIFont.boldSystemFont(ofSize: 18),\n            NSForegroundColorAttributeName: UIColor.black\n        ])\n    }\n}\n```\n\nWhen a cell is dequeued, the collectionData updates the cell with this object.\n```swift\nfinal class WeatherDayCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable {\n  @IBOutlet weak var label: UILabel!\n\n  func update(with adapter: CollectionAdapter) {\n        guard let adapter = adapter as? WeatherDayAdapter else {\n            fatalError(\"WeatherDayAdapter required\")\n        }\n        label.attributedText = adapter.date\n    }\n}\n```\n##### ViewController\n\nCreate the dataSource and the delegate:\n\n```swift\nlazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: self)\nlazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: self)\n```\n\nCreate the collectionData:\n```swift\nlet collectionData = MyCollectionData()\n```\n\nBind the collectionView with the data, the datasource and the delegate:\n```swift\nbind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource)\n```\n\nCreate a section and one cell in the collectionData:\n```swift\nfinal class MyCollectionData : CollectionData {\n\n    override func reloadData() {\n        super.reloadData()\n\n        let section = MySectionDescriptor().reloadSection { (cells) in\n                let cell = MyColorDescriptor(adapter: MyColorAdapter(model: \"someThing\"))\n                cells.append(cell)\n            })\n        }\n        sections.append(section)\n    }\n}\n```\n\nMySectionDescriptor:\n```swift\nfinal class MySectionDescriptor : CollectionSectionDescribable {\n\n  func sectionInset(_ collectionView: UICollectionView) -\u003e UIEdgeInsets {\n      return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)\n  }\n}\n```\n\nYou get a readable data which represents your UICollectionView, without code duplication, reusing cell and with a good separation of code.\n\n##### SupplementatyView\n\nTo add a supplementaryView, instead of using `CollectionSectionDescribable.reloadSection(:)`, call `CollectionSectionDescribable.reload(:)`.\nThe `builder` passed as parameter exposes new method to add a `supplementaryView` easily.\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/voyages-sncf-technologies/Collor/master/resources/alphabet.gif\" alt=\"Collor Alphabet Sample\"\u003e\u003c/p\u003e\n\n```swift\nlet section = MySectionDescriptor().reload { builder in\n    let letterAdapter = LetterAdapter(letter: \"A\")\n    let letterDescriptor = LetterCollectionReusableViewDescriptor(adapter: letterAdapter)\n    builder.add(supplementaryView: letterDescriptor, kind: \"letter\")\n}\nsections.append(section)\n```\n\n`CollectionSupplementaryViewDescribable` behaves like a `CollectionCellDescribable`. It needs an adapter to fill the view with data.\nBut, 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.\n\n```swift\nclass Layout : UICollectionViewFlowLayout {\n    //...\n    override func prepare() {\n        super.prepare()\n        \n        supplementaryViewsHandler.prepare()\n        \n        for (sectionIndex, sectionDescriptor) in datas.sections.enumerated() {\n            \n            let firstCellIndexPath = IndexPath(item: 0, section: sectionIndex)\n            let firstCellAttributes = layoutAttributesForItem(at: firstCellIndexPath)!\n            \n            sectionDescriptor.supplementaryViews.forEach { (kind, views) in\n                \n                views.enumerated().forEach { (index, viewDescriptor) in\n                    let indexPath = IndexPath(item: index, section: sectionIndex)\n                    let a = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: kind, with: indexPath)\n                    a.frame = viewDescriptor.frame(collectionView, sectionDescriptor: sectionDescriptor)\n                    a.frame.origin.y += firstCellAttributes.frame.origin.y\n                    \n                    supplementaryViewsHandler.add(attributes: a)\n                }\n            }\n        }\n    }\n    \n    override func layoutAttributesForElements(in rect: CGRect) -\u003e [UICollectionViewLayoutAttributes]? {\n        let attributes = super.layoutAttributesForElements(in: rect)\n        let supplementaryAttributes = supplementaryViewsHandler.attributes(in: rect)\n        if let attributes = attributes {\n            return attributes + supplementaryAttributes\n        }\n        return attributes\n    }\n    \n    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -\u003e UICollectionViewLayoutAttributes? {\n        return supplementaryViewsHandler.attributes(for: elementKind, at: indexPath)\n    }\n    \n    override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {\n        super.prepare(forCollectionViewUpdates: updateItems)\n        supplementaryViewsHandler.prepare(forCollectionViewUpdates: updateItems)\n    }\n    \n    override func indexPathsToInsertForSupplementaryView(ofKind elementKind: String) -\u003e [IndexPath] {\n        return supplementaryViewsHandler.inserted(for: elementKind)\n    }\n    \n    override func indexPathsToDeleteForSupplementaryView(ofKind elementKind: String) -\u003e [IndexPath] {\n        return supplementaryViewsHandler.deleted(for: elementKind)\n    }\n}\n```\n\n\n## Diffing and updating\n\nCollor provides some features to easily update your collectionData.\n##### Updating\nJust append or remove cells or sections using ```CollectionData.update(_:)``` method. This means an end to fiddling around with ```IndexPath```:\n```swift\nlet newCellDescriptor = NewCellDescriptor(...)\nlet result = collectionData.update { updater in\n    updater.append(cells: [newCellDescriptor], after: anotherCellDescriptor)\n}\ncollectionView.performUpdates(with: result)\n```\nHere is the list of all update methods available:\n- append(cells:after:)\n- append(cells:before:)\n- append(cells:in:)\n- remove(cells:)\n- reload(cells:)\n- append(sections:after:)\n- append(sections:before:)\n- append(sections:)\n- remove(sections:)\n- reload(sections:)\n\n\n##### Diffing\nCollor is using a home made algorithm for getting the \"diff\" between two updates of your collectionData.\n- Diffing some sections:\n\n```swift\nsectionDescriptor.isExpanded = !sectionDescriptor.isExpanded\nlet result = collectionData.update{ updater in\n    updater.diff(sections: [sectionDescriptor])\n}\ncollectionView.performUpdates(with: result)\n```\n- Diffing entire data\n\n```swift\nmodel.someUpdates()\nlet result = collectionData.update { updater in\n    collectionData.reload(model: model)\n    updater.diff()\n}\ncollectionView.performUpdates(with: result)\n```\n\n- Effortless management of decoration views\n\nWith `DecorationViewHandler`, you no longer need to implement code to manage your decoration views:\n\n```swift\n// register decoration view or class:\ndecorationViewHandler.register(viewClass: SimpleDecorationView.self, for: sectionBackgroundKind)\n// caching\ndecorationViewHandler.add(attributes: backgroundAttributes)\n// compute elements in rect\ndecorationViewHandler.attributes(in:rect)\n// retrieving\ndecorationViewHandler.attributes(for: elementKind, at: atIndexPath)\n// update handling\ndecorationViewHandler.prepare(forCollectionViewUpdates: updateItems)\nreturn decorationViewHandler.inserted(for: elementKind)\nreturn decorationViewHandler.deleted(for: elementKind)\n```\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/voyages-sncf-technologies/Collor/master/resources/realtime.gif\" alt=\"Collor Realtime Sample\"\u003e\u003c/p\u003e\n\n\nFor more information, have a look at this [medium article](https://medium.com/p/8f37064de388/).\n\n## XCTemplates\n\nCollor is published with 3 xctemplates for helping you creating ViewController, SectionDescriptor and CellDescriptor.\n\nTo install them, just go in xctemplates directory and run this command in a terminal:\n```shell\nsh install.sh\n```\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/voyages-sncf-technologies/Collor/master/resources//xctemplates.png\" alt=\"XCTemplates\"\u003e\u003c/p\u003e\n\n## Requirements\n- iOS 10.0+\n- Swift 4.2+ (get the 1.0.3 release for swift3.x, 1.1.23 for swift4.0)\n- Xcode 10.1+\n\n## Installation\n### CocoaPods\nCollor is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod \"Collor\"\n```\n### Carthage\nCollor doesn't yet support Carthage. Work in progress...\n\n## Documentation\n\n[Documentation](https://voyages-sncf-technologies.github.io/Collor/index.html)\n\nWork in progress... 1% documented\n\n## Credits\n\nCollor is owned and maintained by [oui.sncf](http://www.oui.sncf/).\n\nCollor was originally created by [Gwenn Guihal](https://github.com/gwennguihal).\n\n## License\n\nCollor is available under the BSD license. See the LICENSE file for more info.\n","funding_links":[],"categories":["Swift"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsncf-connect-tech%2FCollor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsncf-connect-tech%2FCollor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsncf-connect-tech%2FCollor/lists"}