├── .gitignore ├── .slather.yml ├── .travis.yml ├── Collor.podspec ├── Collor ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── CollectionAdapter.swift │ ├── CollectionCellAdaptable.swift │ ├── CollectionCellDescribable.swift │ ├── CollectionDataSource.swift │ ├── CollectionDatas.swift │ ├── CollectionDelegate.swift │ ├── CollectionDidSelectCellDelegate.swift │ ├── CollectionSectionDescriptable.swift │ ├── CollectionSupplementaryViewDescribable.swift │ ├── CollectionUpdater+Diff.swift │ ├── CollectionUpdater.swift │ ├── CollectionUserEventDelegate.swift │ ├── CollorDiff.swift │ ├── DecorationViewsHandler.swift │ ├── Diffable.swift │ ├── Helpers.swift │ ├── Identifiable.swift │ ├── SupplementaryViewsHandler.swift │ └── UICollectionView+CollectionDescriptor.swift ├── Example ├── Collor.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Collor-Example.xcscheme ├── Collor.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Collor │ ├── Alphabet │ │ ├── AlphabetCollectionData.swift │ │ ├── AlphabetViewController.swift │ │ ├── AlphabetViewController.xib │ │ ├── cell │ │ │ ├── AlphabetCollectionViewCell.swift │ │ │ └── AlphabetCollectionViewCell.xib │ │ ├── layout │ │ │ └── AlphabetLayout.swift │ │ ├── model │ │ │ └── AlphabetModel.swift │ │ ├── section │ │ │ └── AlphabetSectionDescriptor.swift │ │ └── view │ │ │ ├── LetterCollectionReusableView.swift │ │ │ └── LetterCollectionReusableView.xib │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Common │ │ ├── Colors.swift │ │ ├── cell │ │ │ ├── label │ │ │ │ ├── LabelCollectionViewCell.swift │ │ │ │ └── LabelCollectionViewCell.xib │ │ │ └── title │ │ │ │ ├── TitleCollectionViewCell.swift │ │ │ │ └── TitleCollectionViewCell.xib │ │ └── layout │ │ │ ├── SimpleDecorationView.swift │ │ │ └── WhiteSectionLayout.swift │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── arrow.imageset │ │ │ ├── Contents.json │ │ │ ├── arrow_left_price@2x.png │ │ │ └── arrow_left_price@3x.png │ ├── Info.plist │ ├── MenuSample │ │ ├── MenuCollectionData.swift │ │ ├── MenuViewController.swift │ │ ├── MenuViewController.xib │ │ ├── cell │ │ │ ├── MenuItemAdapter.swift │ │ │ ├── MenuItemCollectionViewCell.swift │ │ │ └── MenuItemCollectionViewCell.xib │ │ └── section │ │ │ └── MenuSectionDescriptor.swift │ ├── PantoneSample │ │ ├── PantoneCollectionData.swift │ │ ├── PantoneViewController.swift │ │ ├── PantoneViewController.xib │ │ ├── cell │ │ │ ├── PantoneColorAdapter.swift │ │ │ ├── PantoneColorCollectionViewCell.swift │ │ │ └── PantoneColorCollectionViewCell.xib │ │ ├── model │ │ │ └── MetallicPantone.swift │ │ └── section │ │ │ └── PantoneSectionDescriptor.swift │ ├── RandomSample │ │ ├── RandomAdapters.swift │ │ ├── RandomCollectionData.swift │ │ ├── RandomViewController.swift │ │ ├── RandomViewController.xib │ │ ├── model │ │ │ └── Crew.swift │ │ └── section │ │ │ └── RandomSectionDescriptor.swift │ ├── RealTimeSample │ │ ├── RealTimeAdapters.swift │ │ ├── RealTimeCollectionData.swift │ │ ├── RealTimeStyle.swift │ │ ├── RealTimeViewController.swift │ │ ├── RealTimeViewController.xib │ │ ├── cell │ │ │ ├── info │ │ │ │ ├── TweetInfoCollectionViewCell.swift │ │ │ │ └── TweetInfoCollectionViewCell.xib │ │ │ └── tweet │ │ │ │ ├── TweetAdapter.swift │ │ │ │ ├── TweetCollectionViewCell.swift │ │ │ │ └── TweetCollectionViewCell.xib │ │ ├── json │ │ │ ├── tweets_0.json │ │ │ ├── tweets_1.json │ │ │ ├── tweets_2.json │ │ │ ├── tweets_3.json │ │ │ ├── tweets_4.json │ │ │ └── tweets_5.json │ │ ├── layout │ │ │ └── RealTimeLayout.swift │ │ ├── model │ │ │ └── RealTimeModel.swift │ │ ├── section │ │ │ └── TweetSectionDescriptor.swift │ │ └── service │ │ │ └── RealTimeService.swift │ ├── TextFieldSample │ │ ├── TextFieldCollectionData.swift │ │ ├── TextFieldViewController.swift │ │ ├── UIView+Autolayout.swift │ │ ├── cell │ │ │ ├── TextFieldCell.swift │ │ │ └── TextFieldCell.xib │ │ └── section │ │ │ └── TextFieldSectionDescriptor.swift │ └── WeatherSample │ │ ├── WeatherCollectionData.swift │ │ ├── WeatherStyle.swift │ │ ├── WeatherViewController.swift │ │ ├── WeatherViewController.xib │ │ ├── WheaterAdapters.swift │ │ ├── cell │ │ ├── WeatherDayAdapter.swift │ │ ├── WeatherDayCollectionViewCell.swift │ │ └── WeatherDayCollectionViewCell.xib │ │ ├── model │ │ └── WeatherModel.swift │ │ ├── section │ │ └── WeatherSectionDescriptor.swift │ │ └── service │ │ └── WheatherService.swift ├── Gemfile ├── Gemfile.lock ├── Podfile ├── Podfile.lock ├── Pods │ ├── Local Podspecs │ │ └── Collor.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ └── project.pbxproj │ └── Target Support Files │ │ ├── Collor │ │ ├── Collor-dummy.m │ │ ├── Collor-prefix.pch │ │ ├── Collor-umbrella.h │ │ └── Collor.modulemap │ │ ├── Pods-Collor_Example │ │ ├── Pods-Collor_Example-acknowledgements.markdown │ │ ├── Pods-Collor_Example-acknowledgements.plist │ │ ├── Pods-Collor_Example-dummy.m │ │ ├── Pods-Collor_Example-frameworks.sh │ │ ├── Pods-Collor_Example-umbrella.h │ │ ├── Pods-Collor_Example.debug.xcconfig │ │ ├── Pods-Collor_Example.modulemap │ │ └── Pods-Collor_Example.release.xcconfig │ │ └── Pods-Collor_Tests │ │ ├── Pods-Collor_Tests-acknowledgements.markdown │ │ ├── Pods-Collor_Tests-acknowledgements.plist │ │ ├── Pods-Collor_Tests-dummy.m │ │ ├── Pods-Collor_Tests-frameworks.sh │ │ ├── Pods-Collor_Tests-umbrella.h │ │ ├── Pods-Collor_Tests.debug.xcconfig │ │ ├── Pods-Collor_Tests.modulemap │ │ └── Pods-Collor_Tests.release.xcconfig ├── Tests │ ├── CollectionDataSourceTest.swift │ ├── CollectionDatasTest.swift │ ├── CollectionDelegateTest.swift │ ├── CollectionExtensionTest.swift │ ├── CollectionSectionDescribableTest.swift │ ├── CollectionUpdaterDiffDataTest.swift │ ├── CollectionUpdaterDiffSectionTest.swift │ ├── CollectionUpdaterTest.swift │ ├── CollectionViewTest.swift │ ├── CollorDiffTest.swift │ ├── DecorationViewsHandlerTest.swift │ ├── IndexPathTest.swift │ ├── Info.plist │ ├── SupplementaryViewsHandlerTest.swift │ ├── cell │ │ ├── NiblessCollectionViewCell.swift │ │ ├── TestCollectionViewCell.swift │ │ ├── TestCollectionViewCell.xib │ │ └── noAdaptable │ │ │ ├── NoAdaptableCollectionViewCell.swift │ │ │ └── NoAdaptableCollectionViewCell.xib │ ├── controller │ │ ├── TestViewController.swift │ │ └── TestViewController.xib │ ├── decoration │ │ ├── TestDecorationView.swift │ │ └── TestDecorationView.xib │ ├── layout │ │ └── TestLayout.swift │ └── view │ │ ├── TestReusableView.swift │ │ └── TestReusableView.xib └── install.sh ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── _Pods.xcodeproj ├── docs ├── Classes.html ├── Classes │ ├── CollectionData.html │ ├── CollectionDataSource.html │ ├── CollectionDelegate.html │ └── CollectionUpdater.html ├── Extensions.html ├── Extensions │ ├── NSExceptionName.html │ └── UICollectionView.html ├── Functions.html ├── Protocols.html ├── Protocols │ ├── CollectionCellAdaptable.html │ ├── CollectionCellDescribable.html │ ├── CollectionDidSelectCellDelegate.html │ ├── CollectionSectionDescribable.html │ ├── Diffable.html │ └── Identifiable.html ├── Structs.html ├── Structs │ ├── CollorDiff.html │ ├── DecorationViewsHandler.html │ └── UpdateCollectionResult.html ├── badge.svg ├── css │ ├── highlight.css │ └── jazzy.css ├── docsets │ ├── Collor.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── CollectionData.html │ │ │ │ ├── CollectionDataSource.html │ │ │ │ ├── CollectionDelegate.html │ │ │ │ └── CollectionUpdater.html │ │ │ ├── Extensions.html │ │ │ ├── Extensions │ │ │ │ ├── NSExceptionName.html │ │ │ │ └── UICollectionView.html │ │ │ ├── Functions.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── CollectionCellAdaptable.html │ │ │ │ ├── CollectionCellDescribable.html │ │ │ │ ├── CollectionDidSelectCellDelegate.html │ │ │ │ ├── CollectionSectionDescribable.html │ │ │ │ ├── Diffable.html │ │ │ │ └── Identifiable.html │ │ │ ├── Structs.html │ │ │ ├── Structs │ │ │ │ ├── CollorDiff.html │ │ │ │ ├── DecorationViewsHandler.html │ │ │ │ └── UpdateCollectionResult.html │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ └── gh.png │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ └── jquery.min.js │ │ │ └── search.json │ │ │ └── docSet.dsidx │ ├── Collor.tgz │ └── Collor.xml ├── img │ ├── carat.png │ ├── dash.png │ └── gh.png ├── index.html ├── js │ ├── jazzy.js │ └── jquery.min.js ├── search.json └── undocumented.json ├── fastlane └── FastFile ├── jazzy.sh ├── resources ├── alphabet.gif ├── logo.png ├── pantone.gif ├── random.gif ├── realtime.gif ├── screenshot.jpg ├── weather.gif └── xctemplates.png └── xctemplates ├── CollorCell.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___FILEBASENAME___CollectionViewCell.swift └── ___FILEBASENAME___CollectionViewCell.xib ├── CollorController.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___VARIABLE_collectionDataName___.swift ├── ___VARIABLE_controllerName___.swift └── ___VARIABLE_controllerName___.xib ├── CollorSection.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist └── ___FILEBASENAME___SectionDescriptor.swift └── install.sh /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OS X 3 | .DS_Store 4 | 5 | # Xcode 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata/ 16 | *.xccheckout 17 | profile 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | 23 | Example/.idea/ 24 | Example/Pods/ 25 | Example/local/ 26 | 27 | # Bundler 28 | .bundle 29 | 30 | Carthage 31 | # We recommend against adding the Pods directory to your .gitignore. However 32 | # you should judge for yourself, the pros and cons are mentioned at: 33 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 34 | # 35 | # Note: if you ignore the Pods directory, make sure to uncomment 36 | # `pod install` in .travis.yml 37 | # 38 | # Pods/ 39 | Example/.ruby-version 40 | .ruby-version 41 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | ci_service: travis_ci 2 | coverage_service: coveralls 3 | scheme: Collor-Example 4 | xcodeproj: Example/Collor.xcodeproj 5 | workspace: Example/Collor.xcworkspace 6 | binary_basename: Collor_Example.app/Frameworks/Collor.framework/Collor -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode11.5 6 | language: swift 7 | rvm: 8 | - 2.7.1 9 | cache: cocoapods 10 | podfile: Example/Podfile 11 | before_install: 12 | - bundle install 13 | - bundle exec pod install --project-directory=Example --repo-update 14 | script: 15 | - set -o pipefail && xcodebuild test -workspace Example/Collor.xcworkspace -scheme Collor-Example -destination "platform=iOS Simulator,name=iPhone 8" | xcpretty 16 | - pod lib lint --allow-warnings 17 | after_success: 18 | - bundle exec slather 19 | -------------------------------------------------------------------------------- /Collor.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Collor' 3 | s.version = '1.1.41' 4 | s.summary = 'A declarative data-oriented framework for UICollectionView.' 5 | s.homepage = 'https://github.com/voyages-sncf-technologies/Collor' 6 | s.screenshots = 'https://raw.githubusercontent.com/voyages-sncf-technologies/Collor/master/resources/screenshot.jpg' 7 | s.license = { :type => 'BSD', :file => 'LICENSE' } 8 | s.author = { 'Gwenn Guihal' => 'gguihal@oui.sncf' } 9 | s.source = { :git => 'https://github.com/voyages-sncf-technologies/Collor.git', :tag => s.version.to_s } 10 | s.social_media_url = 'https://twitter.com/gwennguihal' 11 | s.ios.deployment_target = '10.0' 12 | s.source_files = 'Collor/Classes/**/*' 13 | s.swift_versions = ['5.1', '5.2'] 14 | end 15 | -------------------------------------------------------------------------------- /Collor/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/Collor/Assets/.gitkeep -------------------------------------------------------------------------------- /Collor/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/Collor/Classes/.gitkeep -------------------------------------------------------------------------------- /Collor/Classes/CollectionAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionAdapter.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 16/03/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol CollectionAdapter {} 12 | -------------------------------------------------------------------------------- /Collor/Classes/CollectionCellAdaptable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionCellAdaptable.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 16/03/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol CollectionCellAdaptable { 12 | func update(with adapter: CollectionAdapter) -> Void 13 | func set(delegate:CollectionUserEventDelegate?) -> Void 14 | } 15 | 16 | // default implementation of CollectionCellAdaptable 17 | public extension CollectionCellAdaptable { 18 | func set(delegate:CollectionUserEventDelegate?) {} 19 | } 20 | 21 | public protocol CollectionSupplementaryViewAdaptable { 22 | func update(with adapter: CollectionAdapter) -> Void 23 | } 24 | -------------------------------------------------------------------------------- /Collor/Classes/CollectionCellDescribable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionCellDescribable.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 16/03/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import ObjectiveC 12 | 13 | private struct AssociatedKeys { 14 | static var IndexPath = "collor_IndexPath" 15 | } 16 | 17 | public protocol CollectionCellDescribable : Identifiable { 18 | var identifier: String { get } 19 | var className: String { get } 20 | var selectable: Bool { get } 21 | func getAdapter() -> CollectionAdapter 22 | func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize 23 | var bundle: Bundle { get } 24 | } 25 | 26 | extension CollectionCellDescribable { 27 | public internal(set) var indexPath: IndexPath? { 28 | get { 29 | return objc_getAssociatedObject(self, &AssociatedKeys.IndexPath) as? IndexPath 30 | } 31 | set { 32 | objc_setAssociatedObject( self, &AssociatedKeys.IndexPath, newValue as IndexPath?, .OBJC_ASSOCIATION_COPY) 33 | } 34 | } 35 | } 36 | 37 | public extension CollectionCellDescribable { 38 | var bundle: Bundle { 39 | let typeClass = type(of: self) 40 | return Bundle(for: typeClass) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Collor/Classes/CollectionDatas.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionData.swift 3 | // VSC 4 | // 5 | // Created by Guihal Gwenn on 20/02/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class CollectionData { 12 | 13 | public private(set) lazy var updater:CollectionUpdater = CollectionUpdater(collectionData: self) 14 | 15 | public init() {} 16 | 17 | public var sections = [CollectionSectionDescribable]() 18 | 19 | open func reloadData() { 20 | sections.removeAll() 21 | } 22 | 23 | //MARK: Internal 24 | 25 | var registeredCells = Set() 26 | var registeredSupplementaryViews = Set() 27 | 28 | public func sectionsCount() -> Int { 29 | return sections.count 30 | } 31 | 32 | //MARK: Update 33 | 34 | /** 35 | Computes indexPath for each CollectionCellDescribable and section index for each CollectionSectionDescribable. 36 | 37 | This method is called every time the collectionView calls 'numberOfSections' or during a data update (insertion or deletion) if needed. 38 | 39 | In most of cases, **this method doesn't have to be called manually**, call it only if you need to access to an indexPath before reloading the collectionView. 40 | */ 41 | public func computeIndices() { 42 | for (sectionIndex, section) in sections.enumerated() { 43 | section.index = sectionIndex 44 | computeIndexPaths(in: section) 45 | } 46 | } 47 | 48 | public func computeIndexPaths(in section:CollectionSectionDescribable) { 49 | for (itemIndex, cell) in section.cells.enumerated() { 50 | cell.indexPath = IndexPath(item: itemIndex, section: section.index!) 51 | } 52 | section.supplementaryViews.forEach { (kind, views) in 53 | views.enumerated().forEach { (index, view) in 54 | view.indexPath = IndexPath(item: index, section: section.index!) 55 | } 56 | } 57 | } 58 | 59 | 60 | public func update(_ updates: (_ updater:CollectionUpdater) -> Void) -> UpdateCollectionResult { 61 | updater.result = UpdateCollectionResult() 62 | updates(updater) 63 | return updater.result! 64 | } 65 | } 66 | 67 | public extension CollectionData { 68 | 69 | func sectionDescribable(at indexPath: IndexPath) -> CollectionSectionDescribable? { 70 | return sections[safe: indexPath.section] 71 | } 72 | func sectionDescribable(for cellDescribable: CollectionCellDescribable) -> CollectionSectionDescribable? { 73 | return sections[safe: cellDescribable.indexPath?.section] 74 | } 75 | func cellDescribable(at indexPath: IndexPath) -> CollectionCellDescribable? { 76 | return sections[safe: indexPath.section]?.cells[indexPath.item] 77 | } 78 | func supplementaryViewDescribable(at indexPath: IndexPath, for kind: String) -> CollectionSupplementaryViewDescribable? { 79 | return sections[safe: indexPath.section]?.supplementaryViews[kind]?[indexPath.item] 80 | } 81 | } 82 | 83 | extension Collection { 84 | 85 | subscript (safe index: Index?) -> Iterator.Element? { 86 | guard let index = index else { 87 | return nil 88 | } 89 | return indices.contains(index) ? self[index] : nil 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /Collor/Classes/CollectionDidSelectCellDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionAdapter.swift 3 | // VSC 4 | // 5 | // Created by Guihal Gwenn on 06/10/16. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | public protocol CollectionDidSelectCellDelegate : NSObjectProtocol { 11 | func didSelect(_ cellDescriptor: CollectionCellDescribable, sectionDescriptor: CollectionSectionDescribable, indexPath: IndexPath) 12 | } 13 | -------------------------------------------------------------------------------- /Collor/Classes/CollectionSupplementaryViewDescribable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionSupplementaryViewDescribable.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | import Foundation 11 | import UIKit 12 | import ObjectiveC 13 | 14 | private struct AssociatedKeys { 15 | static var IndexPath = "collor_SupplementaryViewIndexPath" 16 | } 17 | 18 | public protocol CollectionSupplementaryViewDescribable : Identifiable { 19 | var identifier: String { get } 20 | var className: String { get } 21 | func getAdapter() -> CollectionAdapter 22 | /** 23 | Return frame according the section - origin of section is the origin of the first cell 24 | */ 25 | func frame(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGRect 26 | var bundle: Bundle { get } 27 | } 28 | 29 | extension CollectionSupplementaryViewDescribable { 30 | public internal(set) var indexPath: IndexPath? { 31 | get { 32 | return objc_getAssociatedObject(self, &AssociatedKeys.IndexPath) as? IndexPath 33 | } 34 | set { 35 | objc_setAssociatedObject( self, &AssociatedKeys.IndexPath, newValue as IndexPath?, .OBJC_ASSOCIATION_COPY) 36 | } 37 | } 38 | } 39 | 40 | public extension CollectionSupplementaryViewDescribable { 41 | var bundle: Bundle { 42 | let typeClass = type(of: self) 43 | return Bundle(for: typeClass) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Collor/Classes/CollectionUserEventDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionUserEventDelegate.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 16/03/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol CollectionUserEventDelegate : NSObjectProtocol { 12 | } 13 | -------------------------------------------------------------------------------- /Collor/Classes/CollorDiff.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollorDiff.swift 3 | // Pods 4 | // 5 | // Created by Guihal Gwenn on 01/09/2017. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public struct CollorDiff { 12 | 13 | public typealias DiffItem = (index: I, key: T, value:Diffable?) 14 | public typealias DiffList = [I] 15 | public typealias FromToList = [(from: I, to: I)] 16 | 17 | public let inserted: DiffList 18 | public let deleted: DiffList 19 | public let reloaded: DiffList 20 | public let moved: FromToList 21 | 22 | public init(before: [DiffItem], after: [DiffItem]) { 23 | 24 | typealias MapItem = (index:I,value:Diffable?) 25 | 26 | let beforeIsSmaller = before.count < after.count 27 | var map = [T: MapItem]() 28 | for item in (beforeIsSmaller ? before : after) { 29 | map[item.key] = (item.index,item.value) 30 | } 31 | 32 | var inserted = DiffList() 33 | var deleted = DiffList() 34 | var reloaded = DiffList() 35 | var moved = FromToList() 36 | 37 | let isValueUpdated:(DiffItem) -> Bool = { 38 | if let value1 = $0.value, let value2 = map[$0.key]?.value, !value1.isEqual(to: value2) { 39 | return true 40 | } 41 | return false 42 | } 43 | 44 | (beforeIsSmaller ? after : before).forEach { 45 | let index1 = $0.index 46 | if let index2 = map[$0.key]?.index { 47 | if (index1 != index2) { 48 | if isValueUpdated($0) { 49 | deleted.append(beforeIsSmaller ? index2 : index1) 50 | inserted.append(beforeIsSmaller ? index1 : index2) 51 | } else { 52 | let (from, to) = beforeIsSmaller ? (index2, index1) : (index1, index2) 53 | moved.append((from: from, to: to)) 54 | } 55 | } else { 56 | if isValueUpdated($0) { 57 | reloaded.append(index1) 58 | } 59 | } 60 | map.removeValue(forKey: $0.key) 61 | } else { 62 | if (beforeIsSmaller) { 63 | inserted.append(index1) 64 | } else { 65 | deleted.append(index1) 66 | } 67 | } 68 | } 69 | 70 | map.values.forEach { 71 | if (beforeIsSmaller) { 72 | deleted.append($0.index) 73 | } else { 74 | inserted.append($0.index) 75 | } 76 | } 77 | 78 | self.inserted = inserted 79 | self.deleted = deleted 80 | self.moved = moved 81 | self.reloaded = reloaded 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Collor/Classes/Diffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Diffable.swift 3 | // Pods 4 | // 5 | // Created by Guihal Gwenn on 08/09/2017. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol Diffable { 12 | func isEqual(to other: Diffable?) -> Bool 13 | } 14 | 15 | public extension Diffable where Self: Equatable { 16 | 17 | func isEqual(to other: Diffable?) -> Bool { 18 | guard let other = other as? Self else { return false } 19 | return self == other 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Collor/Classes/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 17/03/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | public func bind(collectionView: UICollectionView, with data: CollectionData, and collectionViewDelegate: CollectionDelegate, and collectionViewDatasource: CollectionDataSource) { 13 | 14 | collectionViewDelegate.collectionData = data 15 | collectionViewDatasource.collectionData = data 16 | 17 | collectionView.delegate = collectionViewDelegate 18 | collectionView.dataSource = collectionViewDatasource 19 | 20 | data.reloadData() 21 | } 22 | -------------------------------------------------------------------------------- /Collor/Classes/Identifiable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Identifiable.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 15/06/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private struct AssociatedKeys { 12 | static var UniqueIdentifier = "collor_UniqueIdentifier" 13 | static var Value = "collor_Value" 14 | } 15 | 16 | public protocol Identifiable: class { 17 | 18 | } 19 | 20 | public extension Identifiable { 21 | @discardableResult func uid(_ uid:String) -> Self { 22 | _uid = uid 23 | return self 24 | } 25 | func uid() -> String? { 26 | return _uid 27 | } 28 | } 29 | 30 | extension Identifiable { 31 | var _uid: String? { 32 | get { 33 | return objc_getAssociatedObject(self, &AssociatedKeys.UniqueIdentifier) as? String 34 | } 35 | set { 36 | objc_setAssociatedObject( self, &AssociatedKeys.UniqueIdentifier, newValue as String?, .OBJC_ASSOCIATION_COPY) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Collor/Classes/UICollectionView+CollectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+CollectionDescriptor.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 14/03/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | extension UICollectionView { 13 | 14 | @available(*, deprecated, message: "Use `perforCollorDiff(_:completion)` instead.") 15 | public func performUpdates(with result: UpdateCollectionResult, completion: ((Bool) -> Void)? = nil) { 16 | self.performBatchUpdates({ 17 | self.insertSections(result.insertedSectionsIndexSet) 18 | self.deleteSections(result.deletedSectionsIndexSet) 19 | self.reloadSections(result.reloadedSectionsIndexSet) 20 | let insertedIndexPaths = result.insertedIndexPaths.filter { 21 | $0.section.notContained(in: result.insertedSectionsIndexSet) 22 | } 23 | self.insertItems(at: insertedIndexPaths) 24 | let deletedIndexPaths = result.deletedIndexPaths.filter { 25 | $0.section.notContained(in: result.deletedSectionsIndexSet) 26 | } 27 | self.deleteItems(at: deletedIndexPaths) 28 | let reloadedIndexPaths = result.reloadedIndexPaths.filter { 29 | $0.section.notContained(in: result.reloadedSectionsIndexSet) 30 | } 31 | self.reloadItems(at: reloadedIndexPaths) 32 | result.movedIndexPaths.forEach { (from,to) in 33 | self.moveItem(at: from, to: to) 34 | } 35 | }, completion: completion) 36 | } 37 | 38 | public func performCollorDiff(_ updates: () -> UpdateCollectionResult?, 39 | completion: ((Bool) -> Void)? = nil) { 40 | self.performBatchUpdates({ 41 | guard let result = updates() else { 42 | return 43 | } 44 | 45 | self.insertSections(result.insertedSectionsIndexSet) 46 | self.deleteSections(result.deletedSectionsIndexSet) 47 | self.reloadSections(result.reloadedSectionsIndexSet) 48 | let insertedIndexPaths = result.insertedIndexPaths.filter { 49 | !result.insertedSectionsIndexSet.contains($0.section) 50 | } 51 | self.insertItems(at: insertedIndexPaths) 52 | let deletedIndexPaths = result.deletedIndexPaths.filter { 53 | !result.deletedSectionsIndexSet.contains($0.section) 54 | } 55 | self.deleteItems(at: deletedIndexPaths) 56 | let reloadedIndexPaths = result.reloadedIndexPaths.filter { 57 | !result.reloadedSectionsIndexSet.contains($0.section) 58 | } 59 | self.reloadItems(at: reloadedIndexPaths) 60 | result.movedIndexPaths.forEach { (from,to) in 61 | self.moveItem(at: from, to: to) 62 | } 63 | }, completion: completion) 64 | } 65 | 66 | } 67 | 68 | fileprivate extension Equatable { 69 | func notContained(in sequence: S) -> Bool where S.Element == Self { 70 | return !sequence.contains(self) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Example/Collor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Collor.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Collor.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/AlphabetCollectionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphabetCollectionData.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | //Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class AlphabetCollectionData : CollectionData { 13 | 14 | let model = AlphabetModel() 15 | 16 | override func reloadData() { 17 | super.reloadData() 18 | 19 | model.values.forEach { letter in 20 | let section = AlphabetSectionDescriptorSectionDescriptor().uid(letter.key).reload { builder in 21 | letter.countries.forEach { country in 22 | let cell = AlphabetDescriptor(adapter: AlphabetAdapter(country: country)).uid(country) 23 | builder.cells.append(cell) 24 | } 25 | 26 | // supplementaryView 27 | let letterAdapter = LetterAdapter(letter: letter.key) 28 | let letterDescriptor = LetterCollectionReusableViewDescriptor(adapter: letterAdapter) 29 | builder.add(supplementaryView: letterDescriptor, kind: "letter") 30 | } 31 | sections.append(section) 32 | } 33 | } 34 | 35 | func add() -> UpdateCollectionResult { 36 | model.add() 37 | return diff() 38 | } 39 | 40 | func shake() -> UpdateCollectionResult { 41 | model.shake() 42 | return diff() 43 | } 44 | 45 | func reset() -> UpdateCollectionResult { 46 | model.reset() 47 | return diff() 48 | } 49 | 50 | func diff() -> UpdateCollectionResult { 51 | return update { (updater) in 52 | updater.diff() 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/AlphabetViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphabetViewController.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | //Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class AlphabetViewController: UIViewController { 13 | 14 | @IBOutlet weak var collectionView: UICollectionView! 15 | 16 | fileprivate(set) lazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: self) 17 | fileprivate(set) lazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: self) 18 | 19 | let collectionData = AlphabetCollectionData() 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | bind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource) 25 | collectionView.collectionViewLayout = AlphabetLayout(datas: collectionData) 26 | 27 | navigationItem.rightBarButtonItems = [ 28 | UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(AlphabetViewController.add)), 29 | UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(AlphabetViewController.reset)), 30 | UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(AlphabetViewController.shake)) 31 | ] 32 | } 33 | 34 | @objc func reset() { 35 | let updateResult = collectionData.reset() 36 | collectionView.performUpdates(with: updateResult) 37 | } 38 | 39 | @objc func add() { 40 | let updateResult = collectionData.add() 41 | collectionView.performUpdates(with: updateResult) 42 | } 43 | 44 | @objc func shake() { 45 | let updateResult = collectionData.shake() 46 | collectionView.performUpdates(with: updateResult) 47 | } 48 | } 49 | 50 | extension AlphabetViewController : CollectionDidSelectCellDelegate { 51 | func didSelect(_ cellDescriptor: CollectionCellDescribable, sectionDescriptor: CollectionSectionDescribable, indexPath: IndexPath) {} 52 | } 53 | 54 | extension AlphabetViewController : CollectionUserEventDelegate { 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/AlphabetViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/cell/AlphabetCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphabetCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | public final class AlphabetCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | @IBOutlet weak var label: UILabel! 15 | 16 | public override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | public func update(with adapter: CollectionAdapter) { 22 | guard let adapter = adapter as? AlphabetAdapterProtocol else { 23 | fatalError("AlphabetAdapterProtocol required") 24 | } 25 | label.attributedText = adapter.label 26 | } 27 | 28 | } 29 | 30 | public protocol AlphabetAdapterProtocol: CollectionAdapter { 31 | var label: NSAttributedString { get } 32 | } 33 | 34 | public struct AlphabetAdapter: AlphabetAdapterProtocol { 35 | public let label: NSAttributedString 36 | public init(country: String) { 37 | label = NSAttributedString(string: country) 38 | } 39 | 40 | } 41 | 42 | public final class AlphabetDescriptor: CollectionCellDescribable { 43 | 44 | public let identifier: String = "AlphabetCollectionViewCell" 45 | public let className: String = "AlphabetCollectionViewCell" 46 | public var selectable: Bool = false 47 | 48 | let adapter: AlphabetAdapterProtocol 49 | 50 | public init(adapter: AlphabetAdapterProtocol) { 51 | self.adapter = adapter 52 | } 53 | 54 | public func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 55 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 56 | let width: CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right - 80 57 | return CGSize(width: width, height: adapter.label.height(width)) 58 | } 59 | 60 | public func getAdapter() -> CollectionAdapter { 61 | return adapter 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/cell/AlphabetCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/model/AlphabetModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphabetModel.swift 3 | // Collor_Example 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AlphabetModel { 12 | 13 | struct Letter { 14 | var key: String 15 | var countries: [String] 16 | } 17 | 18 | static let originalvalues: [Letter] = [ 19 | Letter(key: "A", countries: ["Afghanistan", 20 | "Albania", 21 | "Algeria", 22 | "Andorra", 23 | "Angola", 24 | "Antigua and Barbuda", 25 | "Argentina"]), 26 | Letter(key: "B", countries: ["Bahrain", 27 | "Bangladesh", 28 | "Barbados", 29 | "Belarus", 30 | "Belgium", 31 | "Belize", 32 | "Benin"]), 33 | Letter(key: "C", countries: ["Cambodia", 34 | "Cameroon", 35 | "Canada", 36 | "Cabo Verde", 37 | "Central African Republic", 38 | "Chad", 39 | "Chile", 40 | "China", 41 | "Colombia"]), 42 | Letter(key: "D", countries: ["Denmar", 43 | "Djibout", 44 | "Dominic", 45 | "Dominican Republic"]) 46 | ] 47 | var values: [Letter] = [AlphabetModel.originalvalues[0]] 48 | 49 | func add() { 50 | if values.count == AlphabetModel.originalvalues.count { 51 | return 52 | } 53 | let index = values.indices.last! 54 | values.append( AlphabetModel.originalvalues[index + 1] ) 55 | } 56 | 57 | func reset() { 58 | values = [AlphabetModel.originalvalues[0]] 59 | } 60 | 61 | func shake() { 62 | values = values.shuffled() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/section/AlphabetSectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphabetSectionDescriptor.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | //Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class AlphabetSectionDescriptorSectionDescriptor: CollectionSectionDescribable { 13 | 14 | func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets { 15 | return UIEdgeInsets(top: 30, left: 0, bottom: 0, right: 0) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/view/LetterCollectionReusableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LetterCollectionReusableView.swift 3 | // Collor_Example 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class LetterCollectionReusableViewDescriptor : CollectionSupplementaryViewDescribable { 13 | var identifier: String = "LetterCollectionReusableView" 14 | var className: String = "LetterCollectionReusableView" 15 | 16 | let adapter: LetterAdapter 17 | 18 | init(adapter: LetterAdapter) { 19 | self.adapter = adapter 20 | } 21 | 22 | func getAdapter() -> CollectionAdapter { 23 | return adapter 24 | } 25 | 26 | func frame(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGRect { 27 | return CGRect(x: 0, y: 0, width: 80, height: 80) 28 | } 29 | } 30 | 31 | struct LetterAdapter: CollectionAdapter { 32 | var letter: String 33 | 34 | init(letter: String) { 35 | self.letter = letter 36 | } 37 | } 38 | 39 | class LetterCollectionReusableView: UICollectionReusableView, CollectionSupplementaryViewAdaptable { 40 | 41 | @IBOutlet weak var label: UILabel! 42 | 43 | func update(with adapter: CollectionAdapter) { 44 | guard let adapter = adapter as? LetterAdapter else { 45 | fatalError("LetterAdapter required") 46 | } 47 | self.label.text = adapter.letter 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Example/Collor/Alphabet/view/LetterCollectionReusableView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/Collor/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Collor 4 | // 5 | // Created by myrddinus on 02/22/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | 18 | // disable launch app if test 19 | if let _ = NSClassFromString("XCTest") { 20 | self.window = UIWindow(frame: UIScreen.main.bounds) 21 | self.window?.rootViewController = UINavigationController(rootViewController: UIViewController()) 22 | self.window?.makeKeyAndVisible() 23 | return true 24 | } 25 | 26 | self.window = UIWindow(frame: UIScreen.main.bounds) 27 | let viewController = MenuViewController() 28 | let navigationController = UINavigationController(rootViewController: viewController) 29 | self.window?.rootViewController = navigationController 30 | self.window?.makeKeyAndVisible() 31 | 32 | return true 33 | } 34 | 35 | func applicationWillResignActive(_ application: UIApplication) { 36 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 37 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 38 | } 39 | 40 | func applicationDidEnterBackground(_ application: UIApplication) { 41 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 42 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 43 | } 44 | 45 | func applicationWillEnterForeground(_ application: UIApplication) { 46 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 47 | } 48 | 49 | func applicationDidBecomeActive(_ application: UIApplication) { 50 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 51 | } 52 | 53 | func applicationWillTerminate(_ application: UIApplication) { 54 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 55 | } 56 | 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /Example/Collor/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/Collor/Common/Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colors.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 07/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | 13 | extension UIColor { 14 | static let twitterBlue = UIColor(red: 0, green: 172/255, blue: 237/255, alpha: 1) 15 | } 16 | -------------------------------------------------------------------------------- /Example/Collor/Common/cell/label/LabelCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LabelCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | final class LabelCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | @IBOutlet weak var label: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | func update(with adapter: CollectionAdapter) { 22 | guard let adapter = adapter as? LabelAdapterProtocol else { 23 | fatalError("LabelAdapterProtocol required") 24 | } 25 | label.attributedText = adapter.label 26 | } 27 | 28 | } 29 | 30 | final class LabelDescriptor: CollectionCellDescribable { 31 | 32 | let identifier: String = "LabelCollectionViewCell" 33 | let className: String = "LabelCollectionViewCell" 34 | var selectable:Bool = true 35 | 36 | let adapter: LabelAdapterProtocol 37 | 38 | init(adapter:LabelAdapterProtocol) { 39 | self.adapter = adapter 40 | } 41 | 42 | func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 43 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 44 | let width:CGFloat = adapter.width ?? collectionView.bounds.width - sectionInset.left - sectionInset.right 45 | return CGSize(width:width, height:adapter.height ?? adapter.label.height(width)) 46 | } 47 | 48 | public func getAdapter() -> CollectionAdapter { 49 | return adapter 50 | } 51 | } 52 | 53 | protocol LabelAdapterProtocol : CollectionAdapter { 54 | var label:NSAttributedString { get } 55 | var height:CGFloat? { get } 56 | var width:CGFloat? { get } 57 | } 58 | 59 | extension NSAttributedString { 60 | func height(_ forWidth: CGFloat) -> CGFloat { 61 | let size = self.boundingRect(with: CGSize(width: forWidth, height: CGFloat.greatestFiniteMagnitude), 62 | options: [NSStringDrawingOptions.usesLineFragmentOrigin, NSStringDrawingOptions.usesFontLeading], 63 | context: nil) 64 | return ceil(size.height) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Example/Collor/Common/cell/label/LabelCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/Collor/Common/cell/title/TitleCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TitleCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | final class TitleCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | @IBOutlet weak var titleLabel: UILabel! 15 | @IBOutlet weak var leftLineView: UIView! 16 | @IBOutlet weak var rightLineView: UIView! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | // Initialization code 21 | } 22 | 23 | func update(with adapter: CollectionAdapter) { 24 | guard let adapter = adapter as? TitleAdapterProtocol else { 25 | fatalError("TitleAdapterProtocol required") 26 | } 27 | titleLabel.attributedText = adapter.title 28 | leftLineView.backgroundColor = adapter.lineColor 29 | rightLineView.backgroundColor = adapter.lineColor 30 | } 31 | 32 | } 33 | 34 | final class TitleDescriptor: CollectionCellDescribable { 35 | 36 | let identifier: String = "TitleCollectionViewCell" 37 | let className: String = "TitleCollectionViewCell" 38 | var selectable:Bool = false 39 | 40 | let adapter: TitleAdapterProtocol 41 | 42 | init(adapter:TitleAdapterProtocol) { 43 | self.adapter = adapter 44 | } 45 | 46 | func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 47 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 48 | let width:CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right 49 | return CGSize(width:width, height:adapter.cellHeight) 50 | } 51 | 52 | public func getAdapter() -> CollectionAdapter { 53 | return adapter 54 | } 55 | } 56 | 57 | protocol TitleAdapterProtocol : CollectionAdapter { 58 | var title:NSAttributedString { get } 59 | var lineColor:UIColor { get } 60 | var cellHeight:CGFloat { get } 61 | } 62 | -------------------------------------------------------------------------------- /Example/Collor/Common/layout/SimpleDecorationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleDecorationView.swift 3 | // 4 | // Created by Guihal Gwenn on 04/08/2017. 5 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import CoreGraphics 10 | 11 | protocol SectionDecorable { 12 | var hasBackground:Bool { get } 13 | } 14 | 15 | class SimpleDecorationViewLayoutAttributes: UICollectionViewLayoutAttributes { 16 | 17 | var backgroundColor: UIColor? 18 | var cornerRadius: CGFloat? 19 | 20 | override func copy(with zone: NSZone?) -> Any { 21 | let copy = super.copy(with: zone) as! SimpleDecorationViewLayoutAttributes 22 | copy.backgroundColor = backgroundColor 23 | copy.cornerRadius = cornerRadius 24 | return copy 25 | } 26 | 27 | override func isEqual(_ object: Any?) -> Bool { 28 | guard let object = object as? SimpleDecorationViewLayoutAttributes else { 29 | return false 30 | } 31 | if object.backgroundColor == backgroundColor && object.cornerRadius == cornerRadius { 32 | return super.isEqual(object) 33 | } 34 | return false 35 | } 36 | 37 | } 38 | 39 | class SimpleDecorationView: UICollectionReusableView { 40 | 41 | override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { 42 | 43 | guard let layoutAttributes = layoutAttributes as? SimpleDecorationViewLayoutAttributes else { 44 | fatalError("SimpleDecorationViewLayoutAttributes required") 45 | } 46 | 47 | backgroundColor = layoutAttributes.backgroundColor 48 | layer.cornerRadius = layoutAttributes.cornerRadius ?? 0 49 | layer.zPosition = -10 + CGFloat(layoutAttributes.zIndex) // fix issue in zIndex sorting in collectionView on ios10 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Example/Collor/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Example/Collor/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Collor/Images.xcassets/arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "arrow_left_price@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "arrow_left_price@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } -------------------------------------------------------------------------------- /Example/Collor/Images.xcassets/arrow.imageset/arrow_left_price@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/Example/Collor/Images.xcassets/arrow.imageset/arrow_left_price@2x.png -------------------------------------------------------------------------------- /Example/Collor/Images.xcassets/arrow.imageset/arrow_left_price@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/Example/Collor/Images.xcassets/arrow.imageset/arrow_left_price@3x.png -------------------------------------------------------------------------------- /Example/Collor/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/Collor/MenuSample/MenuCollectionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuCollectionData.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 14/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class MenuCollectionData : CollectionData { 13 | 14 | let examples:[Example] 15 | 16 | 17 | init(examples:[Example]) { 18 | self.examples = examples 19 | super.init() 20 | } 21 | 22 | 23 | override func reloadData() { 24 | super.reloadData() 25 | 26 | let section = MenuSectionDescriptor().reloadSection { cells in 27 | self.examples.forEach { 28 | cells.append( MenuItemDescriptor(adapter: MenuItemAdapter(example: $0 ) )) 29 | } 30 | } 31 | sections.append(section) 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Example/Collor/MenuSample/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuViewController.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 14/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | // model 13 | struct Example { 14 | let title: String 15 | let controllerClass: UIViewController.Type 16 | } 17 | 18 | // user event 19 | enum MenuUserEvent { 20 | case itemTap(Example) 21 | } 22 | 23 | protocol MenuUserEventDelegate: CollectionUserEventDelegate { 24 | func onUserEvent(userEvent: MenuUserEvent) 25 | } 26 | 27 | // controller 28 | class MenuViewController: UIViewController { 29 | 30 | @IBOutlet weak var collectionView: UICollectionView! 31 | 32 | fileprivate(set) lazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: nil) 33 | fileprivate(set) lazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: self) 34 | 35 | let examples:[Example] = [Example(title: "Panton", controllerClass: PantoneViewController.self), 36 | Example(title: "Random", controllerClass: RandomViewController.self), 37 | Example(title: "Weather", controllerClass: WeatherViewController.self), 38 | Example(title: "Real Time", controllerClass: RealTimeViewController.self), 39 | Example(title: "Alphabet", controllerClass: AlphabetViewController.self), 40 | Example(title: "TextField", controllerClass: TextFieldViewController.self)] 41 | 42 | 43 | lazy var collectionData: MenuCollectionData = MenuCollectionData(examples: self.examples) 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | 48 | title = "Menu" 49 | 50 | bind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource) 51 | collectionViewDelegate.forwardingDelegate = self 52 | } 53 | } 54 | 55 | extension MenuViewController: MenuUserEventDelegate { 56 | func onUserEvent(userEvent: MenuUserEvent) { 57 | switch userEvent { 58 | case .itemTap(let example): 59 | let controller = example.controllerClass.init() 60 | navigationController?.pushViewController(controller, animated: true) 61 | break 62 | } 63 | } 64 | } 65 | 66 | extension MenuViewController : ForwardingUICollectionViewDelegate { 67 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 68 | print("scroll") 69 | } 70 | 71 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 72 | print("willAppear") 73 | } 74 | } 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Example/Collor/MenuSample/cell/MenuItemAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuItemAdapter.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 14/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Collor 12 | 13 | struct MenuItemAdapter: CollectionAdapter { 14 | 15 | let example:Example 16 | 17 | let label:String 18 | 19 | init(example:Example) { 20 | self.example = example 21 | label = example.title 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Example/Collor/MenuSample/cell/MenuItemCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuItemCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 14/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | final class MenuItemCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | @IBOutlet weak var label: UILabel! 15 | @IBOutlet weak var arrowImage: UIImageView! 16 | 17 | var adapter:MenuItemAdapter? 18 | var userEventDelegate:MenuUserEventDelegate? 19 | 20 | override func awakeFromNib() { 21 | super.awakeFromNib() 22 | 23 | //example for user Event 24 | let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(MenuItemCollectionViewCell.onTap(gr:))) 25 | addGestureRecognizer(tapRecognizer) 26 | } 27 | 28 | @objc func onTap(gr:UIGestureRecognizer) { 29 | guard let adapter = adapter, let delegate = userEventDelegate else { 30 | return 31 | } 32 | delegate.onUserEvent(userEvent: .itemTap(adapter.example)) 33 | } 34 | 35 | func update(with adapter: CollectionAdapter) { 36 | guard let adapter = adapter as? MenuItemAdapter else { 37 | fatalError("MenuItemAdapter required") 38 | } 39 | self.adapter = adapter 40 | 41 | label.text = adapter.label 42 | } 43 | 44 | func set(delegate: CollectionUserEventDelegate?) { 45 | userEventDelegate = delegate as? MenuUserEventDelegate 46 | } 47 | 48 | } 49 | 50 | final class MenuItemDescriptor: CollectionCellDescribable { 51 | 52 | let identifier: String = "MenuItemCollectionViewCell" 53 | let className: String = "MenuItemCollectionViewCell" 54 | var selectable:Bool = false 55 | 56 | let adapter: MenuItemAdapter 57 | 58 | init(adapter:MenuItemAdapter) { 59 | self.adapter = adapter 60 | } 61 | 62 | func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 63 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 64 | let width:CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right 65 | return CGSize(width:width, height:55) 66 | } 67 | 68 | public func getAdapter() -> CollectionAdapter { 69 | return adapter 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Example/Collor/MenuSample/section/MenuSectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuSectionDescriptor.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 14/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class MenuSectionDescriptor: CollectionSectionDescribable { 13 | 14 | func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets { 15 | return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/Collor/PantoneSample/PantoneCollectionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PantoneCollectionData.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 08/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class PantoneCollectionData : CollectionData { 13 | 14 | var model: MetallicPantone? 15 | 16 | func reload(model:MetallicPantone) { 17 | self.model = model 18 | } 19 | 20 | override func reloadData() { 21 | super.reloadData() 22 | 23 | guard let model = model else { 24 | return 25 | } 26 | 27 | let section = PantoneSectionDescriptor().reloadSection { (cells) in 28 | model.used.forEach({ hexaColor in 29 | let colorCellDescriptor = PantoneColorDescriptor(adapter: PantoneColorAdapter(hexaColor: hexaColor)) 30 | cells.append(colorCellDescriptor) 31 | }) 32 | } 33 | sections.append(section) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Example/Collor/PantoneSample/PantoneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PantoneViewController.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 08/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | final class PantoneViewController: UIViewController { 13 | 14 | @IBOutlet weak var collectionView: UICollectionView! 15 | 16 | fileprivate(set) lazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: self) 17 | fileprivate(set) lazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: nil) 18 | 19 | var metallicPantone = MetallicPantone() 20 | let collectionData = PantoneCollectionData() 21 | 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | title = "Pantone Metallic" 27 | 28 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(PantoneViewController.add)) 29 | enableAddButon() 30 | 31 | collectionData.reload(model: metallicPantone) 32 | 33 | bind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource) 34 | } 35 | 36 | func enableAddButon() { 37 | navigationItem.rightBarButtonItem?.isEnabled = !metallicPantone.notUsed.isEmpty 38 | } 39 | 40 | @objc func add() { 41 | // get a random visible indexPath 42 | let visibleIndexPaths = collectionView.indexPathsForVisibleItems 43 | let randomIndexPath = visibleIndexPaths[ Int(arc4random_uniform(UInt32(visibleIndexPaths.count))) ] 44 | 45 | if let cellDescriptor = collectionData.cellDescribable(at: randomIndexPath) as? PantoneColorDescriptor { 46 | let hexaColor = metallicPantone.add() // update model 47 | 48 | let newCellDescriptor = PantoneColorDescriptor(adapter: PantoneColorAdapter(hexaColor: hexaColor)) // update collectionView Data 49 | let result = collectionData.update { updater in 50 | updater.append(cells: [newCellDescriptor], after: cellDescriptor) 51 | } 52 | collectionView.performUpdates(with: result) 53 | } 54 | enableAddButon() 55 | } 56 | } 57 | 58 | extension PantoneViewController : CollectionDidSelectCellDelegate { 59 | func didSelect(_ cellDescriptor: CollectionCellDescribable, sectionDescriptor: CollectionSectionDescribable, indexPath: IndexPath) { 60 | switch (cellDescriptor.getAdapter()) { 61 | case (let adapter as PantoneColorAdapter): 62 | metallicPantone.remove(hexaColor: adapter.hexaColor) 63 | 64 | let result = collectionData.update { updater in 65 | updater.remove(cells: [cellDescriptor]) 66 | } 67 | collectionView.performUpdates(with: result) 68 | 69 | enableAddButon() 70 | default: 71 | break 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Example/Collor/PantoneSample/PantoneViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/Collor/PantoneSample/cell/PantoneColorAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PantoneColorAdapter.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 08/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | struct PantoneColorAdapter: CollectionAdapter { 13 | 14 | let hexaColor:Int 15 | 16 | let color:UIColor 17 | 18 | init(hexaColor:Int) { 19 | self.hexaColor = hexaColor 20 | color = UIColor(rgb: hexaColor) 21 | } 22 | } 23 | 24 | 25 | // from https://stackoverflow.com/questions/24263007/how-to-use-hex-colour-values-in-swift-ios 26 | extension UIColor { 27 | convenience init(red: Int, green: Int, blue: Int) { 28 | assert(red >= 0 && red <= 255, "Invalid red component") 29 | assert(green >= 0 && green <= 255, "Invalid green component") 30 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 31 | 32 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) 33 | } 34 | 35 | convenience init(rgb: Int) { 36 | self.init( 37 | red: (rgb >> 16) & 0xFF, 38 | green: (rgb >> 8) & 0xFF, 39 | blue: rgb & 0xFF 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Example/Collor/PantoneSample/cell/PantoneColorCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PantoneColorCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 08/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | final class PantoneColorCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | override func awakeFromNib() { 15 | super.awakeFromNib() 16 | // Initialization code 17 | } 18 | 19 | func update(with adapter: CollectionAdapter) { 20 | guard let adapter = adapter as? PantoneColorAdapter else { 21 | fatalError("PantoneColorAdapter required") 22 | } 23 | 24 | backgroundColor = adapter.color 25 | } 26 | 27 | } 28 | 29 | final class PantoneColorDescriptor: CollectionCellDescribable { 30 | 31 | let identifier: String = "PantoneColorCollectionViewCell" 32 | let className: String = "PantoneColorCollectionViewCell" 33 | var selectable:Bool = true 34 | 35 | let adapter: PantoneColorAdapter 36 | 37 | init(adapter:PantoneColorAdapter) { 38 | self.adapter = adapter 39 | } 40 | 41 | func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 42 | let margin = PantoneSectionDescriptor.margin * 4 43 | let side:CGFloat = (collectionView.bounds.width - margin) / 3 44 | return CGSize(width: side, height: side) 45 | } 46 | 47 | public func getAdapter() -> CollectionAdapter { 48 | return adapter 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Example/Collor/PantoneSample/cell/PantoneColorCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/Collor/PantoneSample/section/PantoneSectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PantoneSectionDescriptor.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 08/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | import CoreGraphics 12 | 13 | final class PantoneSectionDescriptor : CollectionSectionDescribable { 14 | 15 | static let margin:CGFloat = 2 16 | 17 | func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets { 18 | return UIEdgeInsets(top: 0, left: PantoneSectionDescriptor.margin, bottom: 0, right: PantoneSectionDescriptor.margin) 19 | } 20 | func minimumLineSpacing(_ collectionView: UICollectionView, layout: UICollectionViewFlowLayout) -> CGFloat { 21 | return PantoneSectionDescriptor.margin 22 | } 23 | func minimumInteritemSpacing(_ collectionView: UICollectionView, layout: UICollectionViewFlowLayout) -> CGFloat { 24 | return PantoneSectionDescriptor.margin 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/Collor/RandomSample/RandomAdapters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomAdapters.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 07/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | import UIKit 12 | 13 | struct TeamTitleAdapter : TitleAdapterProtocol { 14 | let title: NSAttributedString 15 | var lineColor: UIColor = .lightGray 16 | var cellHeight: CGFloat = 40 17 | 18 | init(team: Team) { 19 | self.title = NSAttributedString(string: team.title, attributes: [ 20 | NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14), 21 | NSAttributedString.Key.foregroundColor: UIColor.black 22 | ]) 23 | } 24 | } 25 | 26 | struct TeamMemberAdapter: LabelAdapterProtocol { 27 | let label:NSAttributedString 28 | var height: CGFloat? = 30 29 | var width: CGFloat? = 111 // handly computed :) 30 | 31 | init(member: String) { 32 | 33 | let style = NSMutableParagraphStyle() 34 | style.alignment = .center 35 | 36 | self.label = NSAttributedString(string: member, attributes: [ 37 | NSAttributedString.Key.paragraphStyle: style, 38 | NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 12), 39 | NSAttributedString.Key.foregroundColor: UIColor.black 40 | ]) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Example/Collor/RandomSample/RandomCollectionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomCollectionData.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 07/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class RandomCollectionData : CollectionData { 13 | 14 | var crew:Crew? 15 | 16 | func reload(crew:Crew) { 17 | self.crew = crew 18 | } 19 | 20 | override func reloadData() { 21 | super.reloadData() 22 | 23 | guard let crew = crew else { 24 | return 25 | } 26 | 27 | crew.teams.forEach { team in 28 | 29 | let section = RandomSectionDescriptor().uid(team.title).reloadSection { cells in 30 | let titleItem = TitleDescriptor(adapter: TeamTitleAdapter(team: team)).uid(team.title) 31 | cells.append(titleItem) 32 | 33 | team.members.forEach { member in 34 | let memberItem = LabelDescriptor(adapter: TeamMemberAdapter(member: member)).uid(member) 35 | cells.append(memberItem) 36 | } 37 | } 38 | sections.append( section ) 39 | } 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Example/Collor/RandomSample/RandomViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomViewController.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 07/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class RandomViewController: UIViewController { 13 | 14 | @IBOutlet weak var collectionView: UICollectionView! 15 | 16 | fileprivate(set) lazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: self) 17 | fileprivate(set) lazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: self) 18 | 19 | var crew = Crew() 20 | let collectionData = RandomCollectionData() 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | title = "Random" 26 | 27 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(RandomViewController.randomize)) 28 | 29 | collectionData.reload(crew: crew) 30 | collectionView.collectionViewLayout = WhiteSectionLayout(datas: collectionData) 31 | bind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource) 32 | } 33 | 34 | static var randomizesCount = 0 35 | 36 | @objc func randomize() { 37 | 38 | switch RandomViewController.randomizesCount { 39 | case let count where count % 2 == 0: 40 | crew.randomizeMembers() 41 | case let count where count % 3 == 0: 42 | crew.randomizeTeams() 43 | default: 44 | crew.randomizeAll() 45 | } 46 | RandomViewController.randomizesCount += 1 47 | 48 | collectionData.reload(crew: crew) 49 | let result = collectionData.update { updater in 50 | updater.diff() 51 | } 52 | collectionView.performUpdates(with: result) 53 | } 54 | } 55 | 56 | extension RandomViewController : CollectionDidSelectCellDelegate { 57 | func didSelect(_ cellDescriptor: CollectionCellDescribable, sectionDescriptor: CollectionSectionDescribable, indexPath: IndexPath) {} 58 | } 59 | 60 | extension RandomViewController : CollectionUserEventDelegate { 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Example/Collor/RandomSample/model/Crew.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Crew.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 07/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Team { 12 | let title: String 13 | var members: [String] 14 | 15 | mutating func randomize() { 16 | members.shuffle() 17 | } 18 | } 19 | 20 | struct Crew { 21 | var teams: [Team] = [ 22 | Team(title: "✏️ UX", members: ["Eric","Jules"]), 23 | Team(title: "🖼 UI", members: ["Christophe"]), 24 | Team(title: "⌨️ DEV", members: ["Jad", "Gwenn", "Oussam", "Aymen", "Slim", "Lana", "Guillaume", "Alvaro", "Marie-Odile"]), 25 | Team(title: "💊 TEST", members: ["Emilien", "Helena", "Ines"]), 26 | Team(title: "🕹 SCRUM", members: ["Maxime", "Jessica", "Johan"]) 27 | ] 28 | 29 | mutating func randomizeMembers() { 30 | teams = teams.map { team in 31 | var team = team 32 | team.randomize() 33 | return team 34 | } 35 | } 36 | 37 | mutating func randomizeTeams() { 38 | teams = teams.shuffled() 39 | } 40 | 41 | mutating func randomizeAll() { 42 | teams = teams.map { team in 43 | var team = team 44 | team.randomize() 45 | return team 46 | }.shuffled() 47 | } 48 | } 49 | 50 | 51 | // from https://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift 52 | 53 | extension MutableCollection { 54 | /// Shuffles the contents of this collection. 55 | mutating func shuffle() { 56 | let c = count 57 | guard c > 1 else { return } 58 | 59 | for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) { 60 | let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount))) 61 | guard d != 0 else { continue } 62 | let i = index(firstUnshuffled, offsetBy: d) 63 | self.swapAt(firstUnshuffled, i) 64 | } 65 | } 66 | } 67 | 68 | extension Sequence { 69 | /// Returns an array with the contents of this sequence, shuffled. 70 | func shuffled() -> [Iterator.Element] { 71 | var result = Array(self) 72 | result.shuffle() 73 | return result 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Example/Collor/RandomSample/section/RandomSectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomSectionDescriptor.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 07/08/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | class RandomSectionDescriptor: CollectionSectionDescribable, SectionDecorable { 13 | var hasBackground: Bool = true 14 | 15 | func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets { 16 | return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/RealTimeAdapters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealTimeAdapters.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 05/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | struct RetweetAdapter : TweetInfoAdapterProtocol, Diffable, Equatable { 13 | let key: NSAttributedString 14 | let value: NSAttributedString 15 | init(tweet:Tweet) { 16 | key = RealTimeStyle.TweetInfo.keyAttributedString(text: "Retweets:") 17 | value = RealTimeStyle.TweetInfo.valueAttributedString(text: "\(tweet.retweetCount)") 18 | } 19 | } 20 | 21 | struct FavoriteAdapter : TweetInfoAdapterProtocol, Diffable, Equatable { 22 | let key: NSAttributedString 23 | let value: NSAttributedString 24 | init(tweet:Tweet) { 25 | key = RealTimeStyle.TweetInfo.keyAttributedString(text: "Favorites:") 26 | value = RealTimeStyle.TweetInfo.valueAttributedString(text: "\(tweet.favoriteCount)") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/RealTimeCollectionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealTimeCollectionData.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 04/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class RealTimeCollectionData : CollectionData { 13 | 14 | var model:RealTimeModel? 15 | 16 | func update(model:RealTimeModel) { 17 | self.model = model 18 | } 19 | 20 | override func reloadData() { 21 | super.reloadData() 22 | 23 | guard let model = self.model else { 24 | return 25 | } 26 | 27 | model.tweets.forEach { tweet in 28 | let section = TweetSectionDescriptor().uid(tweet.id).reloadSection { cells in 29 | let tweetCell = TweetDescriptor(adapter: TweetAdapter(tweet: tweet)).uid("text") 30 | cells.append(tweetCell) 31 | if tweet.retweetCount > 0 { 32 | let retweetCell = TweetInfoDescriptor(adapter: RetweetAdapter(tweet: tweet)).uid("retweet") 33 | cells.append(retweetCell) 34 | } 35 | if tweet.favoriteCount > 0 { 36 | let favoriteCell = TweetInfoDescriptor(adapter: FavoriteAdapter(tweet: tweet)).uid("favorite") 37 | cells.append(favoriteCell) 38 | } 39 | } 40 | sections.append(section) 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/RealTimeStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealTimeStyle.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 07/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | struct RealTimeStyle { 13 | 14 | struct TweetInfo { 15 | 16 | static func keyAttributedString(text:String) -> NSAttributedString { 17 | let textAttString = NSAttributedString(string: text, attributes: [ 18 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 10), 19 | NSAttributedString.Key.foregroundColor: UIColor.darkGray 20 | ]) 21 | return textAttString 22 | } 23 | 24 | static func valueAttributedString(text:String) -> NSAttributedString { 25 | let textAttString = NSAttributedString(string: text, attributes: [ 26 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 10), 27 | NSAttributedString.Key.foregroundColor: UIColor.black 28 | ]) 29 | return textAttString 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/RealTimeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealTimeViewController.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 04/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class RealTimeViewController: UIViewController { 13 | 14 | @IBOutlet weak var collectionView: UICollectionView! 15 | 16 | fileprivate(set) lazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: self) 17 | fileprivate(set) lazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: self) 18 | 19 | private let realTimeService = RealTimeService() 20 | let collectionData = RealTimeCollectionData() 21 | 22 | var timer:Timer? 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | title = "Real Time" 28 | 29 | bind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource) 30 | collectionView.collectionViewLayout = RealTimeLayout(datas: collectionData) 31 | 32 | timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(RealTimeViewController.fetch), userInfo: nil, repeats: true) 33 | timer!.fire() 34 | } 35 | 36 | @objc func fetch() { 37 | realTimeService.getRecentTweets { [weak self] response in 38 | switch response { 39 | case .success(let data): 40 | self?.collectionData.update(model: data) 41 | let result = self?.collectionData.update { updater in 42 | updater.diff() 43 | } 44 | if let result = result { 45 | self?.collectionView.performUpdates(with: result) 46 | } 47 | case .error(let error): 48 | print(error) 49 | } 50 | } 51 | } 52 | } 53 | 54 | extension RealTimeViewController : CollectionDidSelectCellDelegate { 55 | func didSelect(_ cellDescriptor: CollectionCellDescribable, sectionDescriptor: CollectionSectionDescribable, indexPath: IndexPath) {} 56 | } 57 | 58 | extension RealTimeViewController : CollectionUserEventDelegate { 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/RealTimeViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/cell/info/TweetInfoCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetInfoCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 05/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | final class TweetInfoCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | @IBOutlet weak var keyLabel: UILabel! 15 | @IBOutlet weak var valueLabel: UILabel! 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | func update(with adapter: CollectionAdapter) { 22 | guard let adapter = adapter as? TweetInfoAdapterProtocol else { 23 | fatalError("TweetInfoAdapterProtocol required") 24 | } 25 | 26 | keyLabel.attributedText = adapter.key 27 | valueLabel.attributedText = adapter.value 28 | } 29 | 30 | } 31 | 32 | final class TweetInfoDescriptor: CollectionCellDescribable { 33 | 34 | let identifier: String = "TweetInfoCollectionViewCell" 35 | let className: String = "TweetInfoCollectionViewCell" 36 | var selectable:Bool = false 37 | 38 | let adapter: TweetInfoAdapterProtocol 39 | 40 | init(adapter:TweetInfoAdapterProtocol) { 41 | self.adapter = adapter 42 | } 43 | 44 | func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 45 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 46 | let width:CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right 47 | return CGSize(width:width, height:44) 48 | } 49 | 50 | public func getAdapter() -> CollectionAdapter { 51 | return adapter 52 | } 53 | } 54 | 55 | protocol TweetInfoAdapterProtocol : CollectionAdapter { 56 | var key:NSAttributedString { get } 57 | var value:NSAttributedString { get } 58 | } 59 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/cell/tweet/TweetAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetAdapter.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 05/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | import UIKit 12 | 13 | struct TweetAdapter: CollectionAdapter, Diffable, Equatable { 14 | 15 | let label:NSAttributedString 16 | let imageURL:URL 17 | let backgroundColor:UIColor 18 | 19 | init(tweet:Tweet) { 20 | label = NSAttributedString(string: tweet.text, attributes: [ 21 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18), 22 | NSAttributedString.Key.foregroundColor: UIColor.black 23 | ]) 24 | imageURL = URL(string: tweet.userProfileImageURL)! 25 | backgroundColor = UIColor(rgb: tweet.color) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/cell/tweet/TweetCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 05/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | import AlamofireImage 12 | 13 | final class TweetCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 14 | 15 | @IBOutlet weak var imageView: UIImageView! 16 | @IBOutlet weak var label: UILabel! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | imageView.af_cancelImageRequest() 21 | } 22 | 23 | func update(with adapter: CollectionAdapter) { 24 | guard let adapter = adapter as? TweetAdapter else { 25 | fatalError("TweetAdapter required") 26 | } 27 | 28 | label.attributedText = adapter.label 29 | imageView.af_setImage(withURL: adapter.imageURL) 30 | } 31 | 32 | } 33 | 34 | final class TweetDescriptor: CollectionCellDescribable { 35 | 36 | let identifier: String = "TweetCollectionViewCell" 37 | let className: String = "TweetCollectionViewCell" 38 | var selectable:Bool = false 39 | 40 | let adapter: TweetAdapter 41 | 42 | init(adapter:TweetAdapter) { 43 | self.adapter = adapter 44 | } 45 | 46 | func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 47 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 48 | let width:CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right 49 | let labelWidth = width - 48 - 4 // cf xib 50 | let cellHeight = max(48, adapter.label.height(labelWidth)) 51 | return CGSize(width:width, height:cellHeight) 52 | } 53 | 54 | public func getAdapter() -> CollectionAdapter { 55 | return adapter 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/json/tweets_0.json: -------------------------------------------------------------------------------- 1 | { "tweets": [ 2 | { 3 | "id_str": "6", 4 | "text": "Nam turpis tellus, commodo quis odio sit amet, aliquam posuere nulla.", 5 | "user": 6 | { 7 | "color": "2dde98", 8 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 9 | }, 10 | "retweet_count": 0, 11 | "favorite_count": 0 12 | }, 13 | { 14 | "id_str": "5", 15 | "text": "Donec eget vehicula eros. Fusce blandit et lectus quis eleifend.", 16 | "user": 17 | { 18 | "color": "8a7967", 19 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 20 | }, 21 | "retweet_count": 1, 22 | "favorite_count": 2 23 | }, 24 | { 25 | "id_str": "4", 26 | "text": "Praesent eu dui vitae nisi convallis porttitor.", 27 | "user": 28 | { 29 | "color": "ffdd00", 30 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 31 | }, 32 | "retweet_count": 0, 33 | "favorite_count": 2 34 | }, 35 | { 36 | "id_str": "3", 37 | "text": "Mauris vestibulum sed est eu semper. Sed vitae ullamcorper ligula.", 38 | "user": 39 | { 40 | "color": "00a4e4", 41 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 42 | }, 43 | "retweet_count": 0, 44 | "favorite_count": 1 45 | }, 46 | { 47 | "id_str": "2", 48 | "text": "In metus lorem, malesuada eget odio at, mollis ullamcorper metus.", 49 | "user": 50 | { 51 | "color": "c1d82f", 52 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 53 | }, 54 | "retweet_count": 1, 55 | "favorite_count": 0 56 | }, 57 | { 58 | "id_str": "1", 59 | "text": "Suspendisse a tortor quis ligula faucibus ornare vitae ac sem.", 60 | "user": 61 | { 62 | "color": "ffdd00", 63 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 64 | }, 65 | "retweet_count": 0, 66 | "favorite_count": 0 67 | }, 68 | { 69 | "id_str": "0", 70 | "text": "Integer nisi nunc, varius id elit et, scelerisque volutpat nisi.", 71 | "user": 72 | { 73 | "color": "fbb034", 74 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 75 | }, 76 | "retweet_count": 0, 77 | "favorite_count": 1 78 | } 79 | ]} 80 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/json/tweets_1.json: -------------------------------------------------------------------------------- 1 | { "tweets": [ 2 | { 3 | "id_str": "6", 4 | "text": "Nam turpis tellus, commodo quis odio sit amet, aliquam posuere nulla.", 5 | "user": 6 | { 7 | "color": "2dde98", 8 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 9 | }, 10 | "retweet_count": 1, 11 | "favorite_count": 1 12 | }, 13 | { 14 | "id_str": "5", 15 | "text": "Donec eget vehicula eros. Fusce blandit et lectus quis eleifend.", 16 | "user": 17 | { 18 | "color": "8a7967", 19 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 20 | }, 21 | "retweet_count": 1, 22 | "favorite_count": 2 23 | }, 24 | { 25 | "id_str": "4", 26 | "text": "Praesent eu dui vitae nisi convallis porttitor.", 27 | "user": 28 | { 29 | "color": "ffdd00", 30 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 31 | }, 32 | "retweet_count": 0, 33 | "favorite_count": 2 34 | }, 35 | { 36 | "id_str": "3", 37 | "text": "Mauris vestibulum sed est eu semper. Sed vitae ullamcorper ligula.", 38 | "user": 39 | { 40 | "color": "00a4e4", 41 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 42 | }, 43 | "retweet_count": 0, 44 | "favorite_count": 1 45 | }, 46 | { 47 | "id_str": "2", 48 | "text": "In metus lorem, malesuada eget odio at, mollis ullamcorper metus.", 49 | "user": 50 | { 51 | "color": "c1d82f", 52 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 53 | }, 54 | "retweet_count": 1, 55 | "favorite_count": 0 56 | }, 57 | { 58 | "id_str": "1", 59 | "text": "Suspendisse a tortor quis ligula faucibus ornare vitae ac sem.", 60 | "user": 61 | { 62 | "color": "ffdd00", 63 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 64 | }, 65 | "retweet_count": 0, 66 | "favorite_count": 0 67 | }, 68 | { 69 | "id_str": "0", 70 | "text": "Integer nisi nunc, varius id elit et, scelerisque volutpat nisi.", 71 | "user": 72 | { 73 | "color": "fbb034", 74 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png", 75 | }, 76 | "retweet_count": 0, 77 | "favorite_count": 1 78 | } 79 | ]} 80 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/json/tweets_2.json: -------------------------------------------------------------------------------- 1 | { "tweets": [ 2 | { 3 | "id_str": "6", 4 | "text": "Nam turpis tellus, commodo quis odio sit amet, aliquam posuere nulla.", 5 | "user": 6 | { 7 | "color": "2dde98", 8 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 9 | }, 10 | "retweet_count": 3, 11 | "favorite_count": 2 12 | }, 13 | { 14 | "id_str": "5", 15 | "text": "Donec eget vehicula eros. Fusce blandit et lectus quis eleifend.", 16 | "user": 17 | { 18 | "color": "8a7967", 19 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 20 | }, 21 | "retweet_count": 1, 22 | "favorite_count": 5 23 | }, 24 | { 25 | "id_str": "4", 26 | "text": "Praesent eu dui vitae nisi convallis porttitor.", 27 | "user": 28 | { 29 | "color": "ffdd00", 30 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 31 | }, 32 | "retweet_count": 1, 33 | "favorite_count": 2 34 | }, 35 | { 36 | "id_str": "3", 37 | "text": "Mauris vestibulum sed est eu semper. Sed vitae ullamcorper ligula.", 38 | "user": 39 | { 40 | "color": "00a4e4", 41 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 42 | }, 43 | "retweet_count": 0, 44 | "favorite_count": 1 45 | }, 46 | { 47 | "id_str": "2", 48 | "text": "In metus lorem, malesuada eget odio at, mollis ullamcorper metus.", 49 | "user": 50 | { 51 | "color": "c1d82f", 52 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 53 | }, 54 | "retweet_count": 1, 55 | "favorite_count": 0 56 | }, 57 | { 58 | "id_str": "1", 59 | "text": "Suspendisse a tortor quis ligula faucibus ornare vitae ac sem.", 60 | "user": 61 | { 62 | "color": "ffdd00", 63 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 64 | }, 65 | "retweet_count": 0, 66 | "favorite_count": 0 67 | }, 68 | { 69 | "id_str": "0", 70 | "text": "Integer nisi nunc, varius id elit et, scelerisque volutpat nisi.", 71 | "user": 72 | { 73 | "color": "fbb034", 74 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 75 | }, 76 | "retweet_count": 0, 77 | "favorite_count": 1 78 | } 79 | ]} 80 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/json/tweets_3.json: -------------------------------------------------------------------------------- 1 | { "tweets": [ 2 | { 3 | "id_str": "7", 4 | "text": "Well animated!", 5 | "user": 6 | { 7 | "color": "ff0000", 8 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 9 | }, 10 | "retweet_count": 0, 11 | "favorite_count": 0 12 | }, 13 | { 14 | "id_str": "6", 15 | "text": "Nam turpis tellus, commodo quis odio sit amet, aliquam posuere nulla.", 16 | "user": 17 | { 18 | "color": "2dde98", 19 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 20 | }, 21 | "retweet_count": 3, 22 | "favorite_count": 2 23 | }, 24 | { 25 | "id_str": "5", 26 | "text": "Donec eget vehicula eros. Fusce blandit et lectus quis eleifend.", 27 | "user": 28 | { 29 | "color": "8a7967", 30 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 31 | }, 32 | "retweet_count": 8, 33 | "favorite_count": 5 34 | }, 35 | { 36 | "id_str": "4", 37 | "text": "Praesent eu dui vitae nisi convallis porttitor.", 38 | "user": 39 | { 40 | "color": "ffdd00", 41 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 42 | }, 43 | "retweet_count": 1, 44 | "favorite_count": 2 45 | }, 46 | { 47 | "id_str": "3", 48 | "text": "Mauris vestibulum sed est eu semper. Sed vitae ullamcorper ligula.", 49 | "user": 50 | { 51 | "color": "00a4e4", 52 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 53 | }, 54 | "retweet_count": 0, 55 | "favorite_count": 1 56 | }, 57 | { 58 | "id_str": "2", 59 | "text": "In metus lorem, malesuada eget odio at, mollis ullamcorper metus.", 60 | "user": 61 | { 62 | "color": "c1d82f", 63 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 64 | }, 65 | "retweet_count": 1, 66 | "favorite_count": 0 67 | }, 68 | { 69 | "id_str": "1", 70 | "text": "Suspendisse a tortor quis ligula faucibus ornare vitae ac sem.", 71 | "user": 72 | { 73 | "color": "ffdd00", 74 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 75 | }, 76 | "retweet_count": 0, 77 | "favorite_count": 0 78 | }, 79 | { 80 | "id_str": "0", 81 | "text": "Integer nisi nunc, varius id elit et, scelerisque volutpat nisi.", 82 | "user": 83 | { 84 | "color": "fbb034", 85 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 86 | }, 87 | "retweet_count": 0, 88 | "favorite_count": 1 89 | } 90 | ]} 91 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/json/tweets_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "tweets": [ 3 | { 4 | "id_str": "7", 5 | "text": "Edited Well animated! Edited Well animated! Edited Well animated! Edited Well animated! Edited Well animated!", 6 | "user": { 7 | "color": "ff0000", 8 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 9 | }, 10 | "retweet_count": 0, 11 | "favorite_count": 0 12 | }, 13 | { 14 | "id_str": "6", 15 | "text": "Nam turpis tellus, commodo quis odio sit amet, aliquam posuere nulla.", 16 | "user": { 17 | "color": "2dde98", 18 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 19 | }, 20 | "retweet_count": 3, 21 | "favorite_count": 2 22 | }, 23 | { 24 | "id_str": "5", 25 | "text": "Donec eget vehicula eros. Fusce blandit et lectus quis eleifend.", 26 | "user": { 27 | "color": "8a7967", 28 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 29 | }, 30 | "retweet_count": 8, 31 | "favorite_count": 5 32 | }, 33 | { 34 | "id_str": "4", 35 | "text": "Praesent eu dui vitae nisi convallis porttitor.", 36 | "user": { 37 | "color": "ffdd00", 38 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 39 | }, 40 | "retweet_count": 1, 41 | "favorite_count": 2 42 | }, 43 | { 44 | "id_str": "3", 45 | "text": "Mauris vestibulum sed est eu semper. Sed vitae ullamcorper ligula.", 46 | "user": { 47 | "color": "00a4e4", 48 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 49 | }, 50 | "retweet_count": 0, 51 | "favorite_count": 1 52 | }, 53 | { 54 | "id_str": "2", 55 | "text": "In metus lorem, malesuada eget odio at, mollis ullamcorper metus.", 56 | "user": { 57 | "color": "c1d82f", 58 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 59 | }, 60 | "retweet_count": 1, 61 | "favorite_count": 0 62 | }, 63 | { 64 | "id_str": "1", 65 | "text": "Suspendisse a tortor quis ligula faucibus ornare vitae ac sem.", 66 | "user": { 67 | "color": "ffdd00", 68 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 69 | }, 70 | "retweet_count": 0, 71 | "favorite_count": 0 72 | }, 73 | { 74 | "id_str": "0", 75 | "text": "Integer nisi nunc, varius id elit et, scelerisque volutpat nisi.", 76 | "user": { 77 | "color": "fbb034", 78 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 79 | }, 80 | "retweet_count": 0, 81 | "favorite_count": 1 82 | } 83 | ] 84 | } -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/json/tweets_5.json: -------------------------------------------------------------------------------- 1 | { "tweets": [ 2 | { 3 | "id_str": "8", 4 | "text": "What a beautiful framework!", 5 | "user": 6 | { 7 | "color": "00a78e", 8 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 9 | }, 10 | "retweet_count": 0, 11 | "favorite_count": 0 12 | }, 13 | { 14 | "id_str": "7", 15 | "text": "Edited Well animated! Edited Well animated! Edited Well animated! Edited Well animated! Edited Well animated!", 16 | "user": 17 | { 18 | "color": "ff0000", 19 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 20 | }, 21 | "retweet_count": 0, 22 | "favorite_count": 0 23 | }, 24 | { 25 | "id_str": "5", 26 | "text": "Donec eget vehicula eros. Fusce blandit et lectus quis eleifend.", 27 | "user": 28 | { 29 | "color": "8a7967", 30 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 31 | }, 32 | "retweet_count": 8, 33 | "favorite_count": 5 34 | }, 35 | { 36 | "id_str": "4", 37 | "text": "Praesent eu dui vitae nisi convallis porttitor.", 38 | "user": 39 | { 40 | "color": "ffdd00", 41 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 42 | }, 43 | "retweet_count": 1, 44 | "favorite_count": 2 45 | }, 46 | { 47 | "id_str": "3", 48 | "text": "Mauris vestibulum sed est eu semper. Sed vitae ullamcorper ligula.", 49 | "user": 50 | { 51 | "color": "00a4e4", 52 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 53 | }, 54 | "retweet_count": 0, 55 | "favorite_count": 1 56 | }, 57 | { 58 | "id_str": "2", 59 | "text": "In metus lorem, malesuada eget odio at, mollis ullamcorper metus.", 60 | "user": 61 | { 62 | "color": "c1d82f", 63 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 64 | }, 65 | "retweet_count": 1, 66 | "favorite_count": 0 67 | }, 68 | { 69 | "id_str": "1", 70 | "text": "Suspendisse a tortor quis ligula faucibus ornare vitae ac sem.", 71 | "user": 72 | { 73 | "color": "ffdd00", 74 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 75 | }, 76 | "retweet_count": 0, 77 | "favorite_count": 0 78 | }, 79 | { 80 | "id_str": "0", 81 | "text": "Integer nisi nunc, varius id elit et, scelerisque volutpat nisi.", 82 | "user": 83 | { 84 | "color": "fbb034", 85 | "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png" 86 | }, 87 | "retweet_count": 0, 88 | "favorite_count": 1 89 | } 90 | ]} 91 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/model/RealTimeModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealTimeModel.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 04/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class RealTimeModel { 12 | 13 | let tweets:[Tweet] 14 | 15 | init?( json:[String:Any] ) { 16 | 17 | guard let tweetsList = json["tweets"] as? [[String:Any]] else { 18 | return nil 19 | } 20 | 21 | tweets = tweetsList.compactMap { tweetData -> Tweet? in 22 | guard let id = tweetData["id_str"] as? String, 23 | let text = tweetData["text"] as? String, 24 | let user = tweetData["user"] as? [String:Any], 25 | let userProfileImageURL = user["profile_image_url"] as? String, 26 | let colorStr = user["color"] as? String, let color = Int(colorStr, radix: 16), 27 | let favoriteCount = tweetData["favorite_count"] as? Int, 28 | let retweetCount = tweetData["retweet_count"] as? Int else { 29 | return nil 30 | } 31 | return Tweet(id: id, text: text, userProfileImageURL: userProfileImageURL, color: color, favoriteCount: favoriteCount, retweetCount: retweetCount) 32 | } 33 | } 34 | } 35 | 36 | struct Tweet { 37 | let id:String 38 | let text:String 39 | let userProfileImageURL:String 40 | let color:Int 41 | let favoriteCount:Int 42 | let retweetCount:Int 43 | } 44 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/section/TweetSectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetSectionDescriptor.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 05/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class TweetSectionDescriptor: CollectionSectionDescribable { 13 | 14 | func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets { 15 | return UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/Collor/RealTimeSample/service/RealTimeService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealTimeService.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 04/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | class RealTimeService { 13 | 14 | enum RealTimeError : Error { 15 | case unknowError 16 | case networkError 17 | } 18 | 19 | enum RealTimeResponse { 20 | case success(RealTimeModel) 21 | case error(RealTimeError) 22 | } 23 | 24 | private static var maxIndex = 5 25 | private static var count = 0 26 | 27 | func getRecentTweets(completion: @escaping (RealTimeResponse)->Void ) { 28 | 29 | let url = "file://" + Bundle.main.path(forResource: "tweets_\(RealTimeService.count)", ofType: "json")! 30 | 31 | Alamofire.request(url).responseJSON { response in 32 | 33 | switch (response.result) { 34 | case .success(let data): 35 | if let dictionary = data as? [String:Any], let model = RealTimeModel(json: dictionary) { 36 | completion( .success(model) ) 37 | } else { 38 | completion( .error( .unknowError ) ) 39 | } 40 | break 41 | case .failure( _): 42 | break 43 | //completion( .error( .networkError ) ) 44 | } 45 | } 46 | 47 | RealTimeService.count += 1 48 | if RealTimeService.count > RealTimeService.maxIndex { 49 | RealTimeService.count = 0 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Example/Collor/TextFieldSample/TextFieldCollectionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCollectionData.swift 3 | // Collor_Example 4 | // 5 | // Created by Deffrasnes Ghislain on 07/05/2020. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class TextFieldCollectionData: CollectionData { 13 | 14 | var numberOfCell = 2 15 | 16 | var sectionToReload: CollectionSectionDescribable? 17 | 18 | override func reloadData() { 19 | super.reloadData() 20 | 21 | let section1 = TextFieldSectionDescriptor().uid("section1_id").reloadSection { cells in 22 | let cellDescriptor = TextFieldDescriptor(adapter: TextFieldAdapter()).uid("cell1_id") 23 | cells.append(cellDescriptor) 24 | } 25 | sections.append(section1) 26 | 27 | let section2 = TextFieldSectionDescriptor().uid("section2_id").reloadSection { [weak self] cells in 28 | guard let self = self else { 29 | return 30 | } 31 | 32 | if self.numberOfCell == 2 { 33 | let cellDescriptor = TextFieldDescriptor(adapter: TextFieldAdapter()).uid("cell2_id") 34 | cells.append(cellDescriptor) 35 | } 36 | 37 | self.numberOfCell = self.numberOfCell == 2 ? 1 : 2 38 | } 39 | sections.append(section2) 40 | self.sectionToReload = section2 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Example/Collor/TextFieldSample/UIView+Autolayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Autolayout.swift 3 | // Collor_Example 4 | // 5 | // Created by Deffrasnes Ghislain on 11/05/2020. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | extension UIView { 13 | 14 | public func autoPinEdgesToSuperview(respectingSafeArea: Bool = false) { 15 | guard let superview = superview else { 16 | return 17 | } 18 | 19 | self.translatesAutoresizingMaskIntoConstraints = false 20 | 21 | // Select good anchors 22 | let topAnchor: NSLayoutYAxisAnchor 23 | let leadingAnchor: NSLayoutXAxisAnchor 24 | let trailingAnchor: NSLayoutXAxisAnchor 25 | let bottomAnchor: NSLayoutYAxisAnchor 26 | 27 | if #available(iOS 13.0, *) { 28 | if respectingSafeArea { 29 | topAnchor = superview.safeAreaLayoutGuide.topAnchor 30 | leadingAnchor = superview.safeAreaLayoutGuide.leadingAnchor 31 | trailingAnchor = superview.safeAreaLayoutGuide.trailingAnchor 32 | bottomAnchor = superview.safeAreaLayoutGuide.bottomAnchor 33 | } else { 34 | topAnchor = superview.topAnchor 35 | leadingAnchor = superview.leadingAnchor 36 | trailingAnchor = superview.trailingAnchor 37 | bottomAnchor = superview.bottomAnchor 38 | } 39 | } else { 40 | topAnchor = superview.topAnchor 41 | leadingAnchor = superview.leadingAnchor 42 | trailingAnchor = superview.trailingAnchor 43 | bottomAnchor = superview.bottomAnchor 44 | } 45 | 46 | self.topAnchor.constraint(equalTo: topAnchor).isActive = true 47 | self.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true 48 | self.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true 49 | self.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Example/Collor/TextFieldSample/cell/TextFieldCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | 13 | @objc 14 | public protocol RealTextFieldCellDelegate { 15 | 16 | @objc 17 | func textFieldDidEndEditing(_ textField: UITextField) 18 | 19 | } 20 | 21 | 22 | public protocol TextFieldCellDelegate: CollectionUserEventDelegate { 23 | 24 | func supplyDelegate() -> RealTextFieldCellDelegate 25 | 26 | } 27 | 28 | 29 | public final class TextFieldCell: UICollectionViewCell, CollectionCellAdaptable { 30 | 31 | @IBOutlet weak var textField: UITextField! 32 | 33 | private var delegate: TextFieldCellDelegate? 34 | 35 | public override func awakeFromNib() { 36 | super.awakeFromNib() 37 | } 38 | 39 | public func update(with adapter: CollectionAdapter) { 40 | // guard let adapter = adapter as? TextFieldAdapterProtocol else { 41 | // fatalError("TextFieldAdapterProtocol required") 42 | // } 43 | } 44 | 45 | public func set(delegate: CollectionUserEventDelegate?) { 46 | guard delegate is TextFieldCellDelegate else { 47 | return 48 | } 49 | 50 | textField.addTarget(delegate, 51 | action: #selector(RealTextFieldCellDelegate.textFieldDidEndEditing), 52 | for: .editingDidEnd) 53 | } 54 | 55 | } 56 | 57 | public protocol TextFieldAdapterProtocol: CollectionAdapter { 58 | 59 | } 60 | 61 | public struct TextFieldAdapter: TextFieldAdapterProtocol { 62 | 63 | public init() { 64 | 65 | } 66 | 67 | } 68 | 69 | public final class TextFieldDescriptor: CollectionCellDescribable { 70 | 71 | public let identifier: String = "TextFieldCell" 72 | public let className: String = "TextFieldCell" 73 | public var selectable: Bool = false 74 | 75 | let adapter: TextFieldAdapterProtocol 76 | 77 | public init(adapter: TextFieldAdapterProtocol) { 78 | self.adapter = adapter 79 | } 80 | 81 | public func size(_ collectionView: UICollectionView, 82 | sectionDescriptor: CollectionSectionDescribable) -> CGSize { 83 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 84 | let width: CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right - 80 85 | return CGSize(width: width, height: 50) 86 | } 87 | 88 | public func getAdapter() -> CollectionAdapter { 89 | return adapter 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Example/Collor/TextFieldSample/cell/TextFieldCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/Collor/TextFieldSample/section/TextFieldSectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldSectionDescriptor.swift 3 | // Collor_Example 4 | // 5 | // Created by Deffrasnes Ghislain on 07/05/2020. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class TextFieldSectionDescriptor: CollectionSectionDescribable { 13 | 14 | func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets { 15 | return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/WeatherCollectionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherCollectionData.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class WeatherCollectionData : CollectionData { 13 | 14 | var weatherModel:WeatherModel? 15 | 16 | func reload(model:WeatherModel) { 17 | weatherModel = model 18 | reloadData() 19 | } 20 | 21 | override func reloadData() { 22 | super.reloadData() 23 | 24 | guard let weatherModel = self.weatherModel else { 25 | return 26 | } 27 | 28 | let titleSection = WeatherSectionDescriptor(hasBackground: false).reloadSection { cells in 29 | let header = TitleDescriptor(adapter: WeatherHeaderAdapter(cityName: weatherModel.cityName )) 30 | cells.append(header) 31 | } 32 | sections.append(titleSection) 33 | 34 | let daySections = weatherModel.weatherDays.map { day -> CollectionSectionDescribable in 35 | let section = WeatherSectionDescriptor().uid(day.date.debugDescription) 36 | section.reloadSection { cells in 37 | 38 | let dayCellDescriptor = WeatherDayDescriptor(adapter: WeatherDayAdapter(day: day) ).uid("day") 39 | cells.append( dayCellDescriptor ) 40 | 41 | if section.isExpanded { 42 | 43 | let temperatureCellDescriptor = LabelDescriptor(adapter: WeatherTemperatureAdapter(day: day) ).uid("temp") 44 | cells.append( temperatureCellDescriptor ) 45 | } 46 | 47 | let pressureCellDescriptor = LabelDescriptor(adapter: WeatherPressureAdapter(day: day) ).uid("pressure") 48 | cells.append( pressureCellDescriptor ) 49 | //} 50 | } 51 | return section 52 | } 53 | sections.append(contentsOf: daySections) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/WeatherStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherStyle.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | struct WeatherStyle { 13 | 14 | struct WeatherProperty { 15 | 16 | static func property(key:String, value:String) -> NSAttributedString { 17 | let keyAttString = NSAttributedString(string: key, attributes: [ 18 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14), 19 | NSAttributedString.Key.foregroundColor: UIColor.black 20 | ]) 21 | let valueAttString = NSAttributedString(string: value, attributes: [ 22 | NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14), 23 | NSAttributedString.Key.foregroundColor: UIColor.black 24 | ]) 25 | let result = NSMutableAttributedString(attributedString: keyAttString) 26 | result.append(valueAttString) 27 | return result 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/WeatherViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherViewController.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | final class WeatherViewController: UIViewController { 13 | 14 | @IBOutlet weak var collectionView: UICollectionView! 15 | 16 | fileprivate(set) lazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: self) 17 | fileprivate(set) lazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: nil) 18 | 19 | private let weatherService = WeatherService() 20 | let collectionData = WeatherCollectionData() 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | title = "Weather" 26 | 27 | bind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource) 28 | collectionView.collectionViewLayout = WhiteSectionLayout(datas: collectionData) 29 | 30 | weatherService.get16DaysWeather { [weak self] response in 31 | switch response { 32 | case .success(let data): 33 | self?.collectionData.reload(model: data) 34 | self?.collectionView.reloadData() 35 | case .error(let error): 36 | print(error) 37 | } 38 | } 39 | } 40 | } 41 | 42 | extension WeatherViewController : CollectionDidSelectCellDelegate { 43 | func didSelect(_ cellDescriptor: CollectionCellDescribable, sectionDescriptor: CollectionSectionDescribable, indexPath: IndexPath) { 44 | switch (sectionDescriptor) { 45 | case (let sectionDescriptor as WeatherSectionDescriptor): 46 | sectionDescriptor.isExpanded = !sectionDescriptor.isExpanded 47 | let result = collectionData.update{ updater in 48 | updater.diff(sections: [sectionDescriptor]) 49 | } 50 | collectionView.performUpdates(with: result) 51 | default: 52 | break 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/WheaterAdapters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WheaterAdapters.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | struct WeatherHeaderAdapter: TitleAdapterProtocol { 13 | 14 | let title: NSAttributedString 15 | var lineColor: UIColor = .lightGray 16 | var cellHeight: CGFloat = 50 17 | 18 | init(cityName:String) { 19 | self.title = NSAttributedString(string: cityName, attributes: [ 20 | NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14), 21 | NSAttributedString.Key.foregroundColor: UIColor.black 22 | ]) 23 | } 24 | } 25 | 26 | struct WeatherTemperatureAdapter: LabelAdapterProtocol { 27 | var label:NSAttributedString 28 | var height: CGFloat? 29 | var width: CGFloat? 30 | 31 | init(day:WeatherDay) { 32 | label = WeatherStyle.WeatherProperty.property(key: "Temperature: ", value: "\(day.temperature)°C") 33 | } 34 | } 35 | 36 | struct WeatherPressureAdapter: LabelAdapterProtocol { 37 | 38 | var label:NSAttributedString 39 | var height: CGFloat? 40 | var width: CGFloat? 41 | 42 | init(day:WeatherDay) { 43 | label = WeatherStyle.WeatherProperty.property(key: "Pressure: ", value: "\(day.pressure)") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/cell/WeatherDayAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherDayAdapter.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | struct WeatherDayAdapter: CollectionAdapter { 13 | 14 | let iconURL:URL 15 | let date:NSAttributedString 16 | let day:WeatherDay 17 | 18 | static let dateFormatter:DateFormatter = { 19 | let df = DateFormatter() 20 | df.dateFormat = "EEEE d MMMM" 21 | return df 22 | }() 23 | 24 | init(day:WeatherDay) { 25 | 26 | self.day = day 27 | 28 | iconURL = URL(string: "https://openweathermap.org/img/w/" + day.weatherIcon + ".png")! 29 | let dateString = WeatherDayAdapter.dateFormatter.string(from: day.date) 30 | date = NSAttributedString(string: dateString, attributes: [ 31 | NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 18), 32 | NSAttributedString.Key.foregroundColor: UIColor.black 33 | ]) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/cell/WeatherDayCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherDayCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | import AlamofireImage 12 | 13 | final class WeatherDayCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 14 | 15 | @IBOutlet weak var imageView: UIImageView! 16 | @IBOutlet weak var label: UILabel! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | 21 | imageView.af_cancelImageRequest() 22 | imageView.image = nil 23 | } 24 | 25 | func update(with adapter: CollectionAdapter) { 26 | guard let adapter = adapter as? WeatherDayAdapter else { 27 | fatalError("WeatherDayAdapter required") 28 | } 29 | imageView.af_setImage(withURL: adapter.iconURL) 30 | label.attributedText = adapter.date 31 | } 32 | } 33 | 34 | 35 | final class WeatherDayDescriptor: CollectionCellDescribable { 36 | 37 | let identifier: String = "WeatherDayCollectionViewCell" 38 | let className: String = "WeatherDayCollectionViewCell" 39 | var selectable:Bool = true 40 | 41 | let adapter: WeatherDayAdapter 42 | 43 | init(adapter:WeatherDayAdapter) { 44 | self.adapter = adapter 45 | } 46 | 47 | func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 48 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 49 | let width:CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right 50 | return CGSize(width:width, height:60) 51 | } 52 | 53 | public func getAdapter() -> CollectionAdapter { 54 | return adapter 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/model/WeatherModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherModel.swift 3 | // BABA 4 | // 5 | // Created by Guihal Gwenn on 16/02/17. 6 | // Copyright © 2017 Guihal Gwenn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct WeatherDay : Equatable { 12 | let date:Date 13 | let temperature:Float 14 | let pressure:Float 15 | let humidity:Float 16 | let weatherIcon:String 17 | 18 | public static func ==(lhs: WeatherDay, rhs: WeatherDay) -> Bool { 19 | return lhs.date == rhs.date 20 | } 21 | } 22 | 23 | final class WeatherModel { 24 | 25 | let weatherDays:[WeatherDay] 26 | let cityName:String 27 | 28 | init?( json:[String:Any] ) { 29 | 30 | // city name 31 | guard let cityData = json["city"] as? [String:Any], let cityName = cityData["name"] as? String 32 | else { 33 | return nil 34 | } 35 | 36 | self.cityName = cityName 37 | 38 | // weather days 39 | guard let daysList = json["list"] as? [[String:Any]] else { 40 | return nil 41 | } 42 | 43 | weatherDays = daysList.compactMap { (dayData) in 44 | guard let timeStamp = dayData["dt"] as? Double, 45 | let tempData = dayData["temp"] as? [String:Any], 46 | let temp = tempData["day"] as? Float, 47 | let pressure = dayData["pressure"] as? Float, 48 | let humidity = dayData["humidity"] as? Float, 49 | let weatherData = dayData["weather"] as? [[String:Any]], 50 | let icon = weatherData.first?["icon"] as? String else { 51 | return nil 52 | } 53 | return WeatherDay(date: Date(timeIntervalSince1970: TimeInterval(timeStamp)), 54 | temperature: temp, pressure: pressure, humidity: humidity, 55 | weatherIcon: icon) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/section/WeatherSectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherSectionDescriptor.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 26/07/2017. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class WeatherSectionDescriptor : CollectionSectionDescribable, SectionDecorable { 13 | 14 | let hasBackground:Bool 15 | var isExpanded:Bool = false 16 | 17 | convenience init() { 18 | self.init(hasBackground: true) 19 | } 20 | 21 | init(hasBackground:Bool) { 22 | self.hasBackground = hasBackground 23 | } 24 | 25 | func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets { 26 | return UIEdgeInsets(top: 10, left: 15, bottom: 15, right: 15) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/Collor/WeatherSample/service/WheatherService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherService.swift 3 | // BABA 4 | // 5 | // Created by Guihal Gwenn on 16/02/17. 6 | // Copyright © 2017 Guihal Gwenn. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import UIKit 11 | 12 | final class WeatherService { 13 | 14 | enum WheatherError : Error { 15 | case unknowError 16 | case networkError 17 | } 18 | 19 | enum WeatherResponse { 20 | case success(WeatherModel) 21 | case error(WheatherError) 22 | } 23 | 24 | func get16DaysWeather(completion: @escaping (WeatherResponse)->Void ) { 25 | 26 | let url = "http://api.openweathermap.org/data/2.5/forecast/daily?id=6455259&appid=92b25fae9369a3cf06456eda297b162a&units=metric" 27 | 28 | Alamofire.request(url).responseJSON { response in 29 | 30 | switch (response.result) { 31 | case .success(let data): 32 | if let dictionary = data as? [String:Any], let weatherModel = WeatherModel(json: dictionary) { 33 | completion( .success(weatherModel) ) 34 | } else { 35 | completion( .error( .unknowError ) ) 36 | } 37 | 38 | case .failure( _): 39 | completion( .error( .networkError ) ) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Example/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | ruby '~> 2.7' 3 | gem "cocoapods", "~> 1.5" 4 | gem "fastlane", "~> 2" -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | use_frameworks! 3 | 4 | target 'Collor_Example' do 5 | pod 'Collor', :path => '../' 6 | pod 'Alamofire' 7 | pod 'AlamofireImage' 8 | 9 | target 'Collor_Tests' do 10 | inherit! :search_paths 11 | 12 | pod 'CwlPreconditionTesting', :git => 'https://github.com/mattgallagher/CwlPreconditionTesting.git' 13 | pod 'CwlCatchException', :git => 'https://github.com/mattgallagher/CwlCatchException.git' 14 | 15 | 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.8.1) 3 | - AlamofireImage (3.5.0): 4 | - Alamofire (~> 4.8) 5 | - Collor (1.1.33) 6 | - CwlCatchException (1.0.2) 7 | - CwlPreconditionTesting (1.1.1): 8 | - CwlCatchException 9 | 10 | DEPENDENCIES: 11 | - Alamofire 12 | - AlamofireImage 13 | - Collor (from `../`) 14 | - CwlCatchException (from `https://github.com/mattgallagher/CwlCatchException.git`) 15 | - CwlPreconditionTesting (from `https://github.com/mattgallagher/CwlPreconditionTesting.git`) 16 | 17 | SPEC REPOS: 18 | https://github.com/CocoaPods/Specs.git: 19 | - Alamofire 20 | - AlamofireImage 21 | 22 | EXTERNAL SOURCES: 23 | Collor: 24 | :path: "../" 25 | CwlCatchException: 26 | :git: https://github.com/mattgallagher/CwlCatchException.git 27 | CwlPreconditionTesting: 28 | :git: https://github.com/mattgallagher/CwlPreconditionTesting.git 29 | 30 | CHECKOUT OPTIONS: 31 | CwlCatchException: 32 | :commit: 52edb6d4329318ff466db1689e85525c3e18689f 33 | :git: https://github.com/mattgallagher/CwlCatchException.git 34 | CwlPreconditionTesting: 35 | :commit: 243dc31e8f3728f9bdd15b353dbcead4fb3d9fdf 36 | :git: https://github.com/mattgallagher/CwlPreconditionTesting.git 37 | 38 | SPEC CHECKSUMS: 39 | Alamofire: 16ce2c353fb72865124ddae8a57c5942388f4f11 40 | AlamofireImage: 1aea346f4dda2f6c67622fa5a89fcbb80d79cc16 41 | Collor: e99563201d7b1be4e9d447b98cc479fb4ba0d8e5 42 | CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c 43 | CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 44 | 45 | PODFILE CHECKSUM: e152076269961dd4e5a4f77a12b102b316a4f7ee 46 | 47 | COCOAPODS: 1.9.1 48 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/Collor.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Collor", 3 | "version": "1.1.33", 4 | "summary": "A MVVM data-oriented framework for UICollectionView.", 5 | "homepage": "https://github.com/voyages-sncf-technologies/Collor", 6 | "screenshots": "https://raw.githubusercontent.com/voyages-sncf-technologies/Collor/master/resources/screenshot.jpg", 7 | "license": { 8 | "type": "BSD", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "Gwenn Guihal": "gguihal@oui.sncf" 13 | }, 14 | "source": { 15 | "git": "https://github.com/voyages-sncf-technologies/Collor.git", 16 | "tag": "1.1.33" 17 | }, 18 | "social_media_url": "https://twitter.com/_myrddin_", 19 | "platforms": { 20 | "ios": "10.0" 21 | }, 22 | "source_files": "Collor/Classes/**/*", 23 | "swift_versions": "4.2", 24 | "swift_version": "4.2" 25 | } 26 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.8.1) 3 | - AlamofireImage (3.5.0): 4 | - Alamofire (~> 4.8) 5 | - Collor (1.1.33) 6 | - CwlCatchException (1.0.2) 7 | - CwlPreconditionTesting (1.1.1): 8 | - CwlCatchException 9 | 10 | DEPENDENCIES: 11 | - Alamofire 12 | - AlamofireImage 13 | - Collor (from `../`) 14 | - CwlCatchException (from `https://github.com/mattgallagher/CwlCatchException.git`) 15 | - CwlPreconditionTesting (from `https://github.com/mattgallagher/CwlPreconditionTesting.git`) 16 | 17 | SPEC REPOS: 18 | https://github.com/CocoaPods/Specs.git: 19 | - Alamofire 20 | - AlamofireImage 21 | 22 | EXTERNAL SOURCES: 23 | Collor: 24 | :path: "../" 25 | CwlCatchException: 26 | :git: https://github.com/mattgallagher/CwlCatchException.git 27 | CwlPreconditionTesting: 28 | :git: https://github.com/mattgallagher/CwlPreconditionTesting.git 29 | 30 | CHECKOUT OPTIONS: 31 | CwlCatchException: 32 | :commit: 52edb6d4329318ff466db1689e85525c3e18689f 33 | :git: https://github.com/mattgallagher/CwlCatchException.git 34 | CwlPreconditionTesting: 35 | :commit: 243dc31e8f3728f9bdd15b353dbcead4fb3d9fdf 36 | :git: https://github.com/mattgallagher/CwlPreconditionTesting.git 37 | 38 | SPEC CHECKSUMS: 39 | Alamofire: 16ce2c353fb72865124ddae8a57c5942388f4f11 40 | AlamofireImage: 1aea346f4dda2f6c67622fa5a89fcbb80d79cc16 41 | Collor: e99563201d7b1be4e9d447b98cc479fb4ba0d8e5 42 | CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c 43 | CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961 44 | 45 | PODFILE CHECKSUM: e152076269961dd4e5a4f77a12b102b316a4f7ee 46 | 47 | COCOAPODS: 1.9.1 48 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Collor/Collor-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Collor : NSObject 3 | @end 4 | @implementation PodsDummy_Collor 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Collor/Collor-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Collor/Collor-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double CollorVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char CollorVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Collor/Collor.modulemap: -------------------------------------------------------------------------------- 1 | framework module Collor { 2 | umbrella header "Collor-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Example/Pods-Collor_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Collor_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Collor_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Example/Pods-Collor_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_Collor_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_Collor_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Example/Pods-Collor_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/AlamofireImage" "${PODS_CONFIGURATION_BUILD_DIR}/Collor" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/AlamofireImage/AlamofireImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Collor/Collor.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "AlamofireImage" -framework "Collor" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Example/Pods-Collor_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Collor_Example { 2 | umbrella header "Pods-Collor_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Example/Pods-Collor_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/AlamofireImage" "${PODS_CONFIGURATION_BUILD_DIR}/Collor" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/AlamofireImage/AlamofireImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Collor/Collor.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "AlamofireImage" -framework "Collor" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Tests/Pods-Collor_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## CwlCatchException 5 | 6 | ISC License 7 | 8 | Copyright © 2017 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved. 9 | 10 | Permission to use, copy, modify, and/or distribute this software for any 11 | purpose with or without fee is hereby granted, provided that the above 12 | copyright notice and this permission notice appear in all copies. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 15 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 17 | SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 18 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 19 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 20 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 21 | 22 | 23 | ## CwlPreconditionTesting 24 | 25 | ISC License 26 | 27 | Copyright © 2017 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved. 28 | 29 | Permission to use, copy, modify, and/or distribute this software for any 30 | purpose with or without fee is hereby granted, provided that the above 31 | copyright notice and this permission notice appear in all copies. 32 | 33 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 34 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 35 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 36 | SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 37 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 38 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 39 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 40 | 41 | Generated by CocoaPods - https://cocoapods.org 42 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Tests/Pods-Collor_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | ISC License 18 | 19 | Copyright © 2017 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved. 20 | 21 | Permission to use, copy, modify, and/or distribute this software for any 22 | purpose with or without fee is hereby granted, provided that the above 23 | copyright notice and this permission notice appear in all copies. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 26 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 27 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 28 | SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 29 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 30 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 31 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 32 | 33 | License 34 | ISC 35 | Title 36 | CwlCatchException 37 | Type 38 | PSGroupSpecifier 39 | 40 | 41 | FooterText 42 | ISC License 43 | 44 | Copyright © 2017 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved. 45 | 46 | Permission to use, copy, modify, and/or distribute this software for any 47 | purpose with or without fee is hereby granted, provided that the above 48 | copyright notice and this permission notice appear in all copies. 49 | 50 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 51 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 52 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 53 | SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 54 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 55 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 56 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 57 | 58 | License 59 | ISC 60 | Title 61 | CwlPreconditionTesting 62 | Type 63 | PSGroupSpecifier 64 | 65 | 66 | FooterText 67 | Generated by CocoaPods - https://cocoapods.org 68 | Title 69 | 70 | Type 71 | PSGroupSpecifier 72 | 73 | 74 | StringsTable 75 | Acknowledgements 76 | Title 77 | Acknowledgements 78 | 79 | 80 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Tests/Pods-Collor_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Collor_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Collor_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Tests/Pods-Collor_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_Collor_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_Collor_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Tests/Pods-Collor_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/AlamofireImage" "${PODS_CONFIGURATION_BUILD_DIR}/Collor" "${PODS_CONFIGURATION_BUILD_DIR}/CwlCatchException" "${PODS_CONFIGURATION_BUILD_DIR}/CwlPreconditionTesting" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/AlamofireImage/AlamofireImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Collor/Collor.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/CwlCatchException/CwlCatchException.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/CwlPreconditionTesting/CwlPreconditionTesting.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "AlamofireImage" -framework "Collor" -framework "CwlCatchException" -framework "CwlPreconditionTesting" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Tests/Pods-Collor_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Collor_Tests { 2 | umbrella header "Pods-Collor_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Collor_Tests/Pods-Collor_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/AlamofireImage" "${PODS_CONFIGURATION_BUILD_DIR}/Collor" "${PODS_CONFIGURATION_BUILD_DIR}/CwlCatchException" "${PODS_CONFIGURATION_BUILD_DIR}/CwlPreconditionTesting" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/AlamofireImage/AlamofireImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Collor/Collor.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/CwlCatchException/CwlCatchException.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/CwlPreconditionTesting/CwlPreconditionTesting.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "AlamofireImage" -framework "Collor" -framework "CwlCatchException" -framework "CwlPreconditionTesting" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Tests/CollectionExtensionTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionExtensionTest.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 15/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Collor 11 | 12 | class CollectionExtensionTest: XCTestCase { 13 | 14 | func testIndexNil() { 15 | let array = ["A","B"] 16 | XCTAssertNil(array[safe:nil]) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Example/Tests/CollectionSectionDescribableTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionSectionDescribableTest.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 15/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Collor 11 | 12 | class CollectionSectionDescribableTest: XCTestCase { 13 | func testUID_sectionUIDNil() { 14 | 15 | let cell = TestCellDescriptor(adapter: TestAdapter()).uid("test") 16 | 17 | let section = TestSectionDescriptor().reloadSection { cells in 18 | cells.append(cell) 19 | } 20 | 21 | XCTAssertNil(section.uid(for: cell)) 22 | } 23 | 24 | func testUID_cellUIDNil() { 25 | 26 | let cell = TestCellDescriptor(adapter: TestAdapter()) 27 | 28 | let section = TestSectionDescriptor().uid("test").reloadSection { cells in 29 | cells.append(cell) 30 | } 31 | 32 | XCTAssertNil(section.uid(for: cell)) 33 | } 34 | 35 | func testUID() { 36 | 37 | let cell = TestCellDescriptor(adapter: TestAdapter()).uid("cell") 38 | 39 | let section = TestSectionDescriptor().uid("section").reloadSection { cells in 40 | cells.append(cell) 41 | } 42 | 43 | XCTAssertEqual(section.uid(for: cell), "section/cell") 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Example/Tests/IndexPathTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexPathTest.swift 3 | // Collor_Tests 4 | // 5 | // Created by Guihal Gwenn on 13/03/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Collor 11 | 12 | class IndexPathTest: XCTestCase { 13 | 14 | func test_isSectionOnly_true() { 15 | // given 16 | let indexPath = IndexPath(item: Int.max, section: 0) 17 | // when 18 | let isSectionOnly = indexPath.isSectionOnly 19 | // then 20 | XCTAssertTrue(isSectionOnly) 21 | } 22 | 23 | func test_isSectionOnly_false() { 24 | // given 25 | let indexPath = IndexPath(item: 2, section: 0) 26 | // when 27 | let isSectionOnly = indexPath.isSectionOnly 28 | // then 29 | XCTAssertFalse(isSectionOnly) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/cell/NiblessCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NiblessCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 4/10/18. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class NiblessCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | override func awakeFromNib() { 15 | super.awakeFromNib() 16 | // Initialization code 17 | } 18 | 19 | func update(with adapter: CollectionAdapter) { 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/Tests/cell/TestCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 10/05/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class TestCollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | override func awakeFromNib() { 15 | super.awakeFromNib() 16 | // Initialization code 17 | } 18 | 19 | func update(with adapter: CollectionAdapter) { 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/Tests/cell/TestCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Example/Tests/cell/noAdaptable/NoAdaptableCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoAdaptableCollectionViewCell.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 10/05/17. 6 | // Copyright (c) 2017-present, Voyages-sncf.com. All rights reserved.. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NoAdaptableCollectionViewCell: UICollectionViewCell { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Example/Tests/cell/noAdaptable/NoAdaptableCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Example/Tests/controller/TestViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/Tests/decoration/TestDecorationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestDecorationView.swift 3 | // Collor 4 | // 5 | // Created by Guihal Gwenn on 18/09/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TestDecorationView: UICollectionReusableView { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | } 18 | 19 | class TestCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Example/Tests/decoration/TestDecorationView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/Tests/layout/TestLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestLayout.swift 3 | // Collor_Tests 4 | // 5 | // Created by Guihal Gwenn on 13/03/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class TestLayout : UICollectionViewFlowLayout { 13 | 14 | unowned fileprivate let datas: CollectionData 15 | fileprivate lazy var supplementaryViewsHandler = SupplementaryViewsHandler(collectionViewLayout: self) 16 | 17 | init(datas: CollectionData) { 18 | self.datas = datas 19 | super.init() 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | 26 | override func prepare() { 27 | super.prepare() 28 | 29 | supplementaryViewsHandler.prepare() 30 | 31 | for (sectionIndex, sectionDescriptor) in datas.sections.enumerated() { 32 | 33 | sectionDescriptor.supplementaryViews.forEach { (kind, views) in 34 | var dict = [IndexPath : UICollectionViewLayoutAttributes]() 35 | views.enumerated().forEach { (index, viewDescriptor) in 36 | let indexPath = IndexPath(item: index, section: sectionIndex) 37 | let a = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: kind, with: indexPath) 38 | dict[indexPath] = a 39 | 40 | supplementaryViewsHandler.add(attributes: a) 41 | } 42 | } 43 | } 44 | 45 | } 46 | 47 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 48 | let attributes = super.layoutAttributesForElements(in: rect) 49 | let supplementaryAttributes = supplementaryViewsHandler.attributes(in: rect) 50 | if let attributes = attributes { 51 | return attributes + supplementaryAttributes 52 | } 53 | return attributes 54 | } 55 | 56 | override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 57 | return supplementaryViewsHandler.attributes(for: elementKind, at: indexPath) 58 | } 59 | 60 | override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) { 61 | super.prepare(forCollectionViewUpdates: updateItems) 62 | supplementaryViewsHandler.prepare(forCollectionViewUpdates: updateItems) 63 | } 64 | 65 | override func indexPathsToInsertForSupplementaryView(ofKind elementKind: String) -> [IndexPath] { 66 | return supplementaryViewsHandler.inserted(for: elementKind) 67 | } 68 | 69 | override func indexPathsToDeleteForSupplementaryView(ofKind elementKind: String) -> [IndexPath] { 70 | return supplementaryViewsHandler.deleted(for: elementKind) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Example/Tests/view/TestReusableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestReusableView.swift 3 | // Collor_Example 4 | // 5 | // Created by Guihal Gwenn on 29/01/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class TestReusableViewDescriptor : CollectionSupplementaryViewDescribable { 13 | var identifier: String = "TestReusableView" 14 | var className: String = "TestReusableView" 15 | 16 | let adapter: TestSuppViewAdapter 17 | 18 | init(adapter: TestSuppViewAdapter) { 19 | self.adapter = adapter 20 | } 21 | 22 | func getAdapter() -> CollectionAdapter { 23 | return adapter 24 | } 25 | 26 | func frame(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGRect { 27 | return CGRect(x: 0, y: 0, width: 80, height: 80) 28 | } 29 | } 30 | 31 | struct TestSuppViewAdapter: CollectionAdapter { 32 | } 33 | 34 | class TestReusableView: UICollectionReusableView, CollectionSupplementaryViewAdaptable { 35 | func update(with adapter: CollectionAdapter) { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Example/Tests/view/TestReusableView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | bundle install --path ./local/gems --binstubs ./local/bin 4 | 5 | bundle exec pod install || if [[ $? != 0 ]]; then bundle exec pod install --repo-update; fi 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | ruby '~> 2.7' 3 | gem "cocoapods", "~> 1.9" 4 | gem "fastlane", "~> 2" 5 | gem "slather", "2.4.7" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Voyages-sncf.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | documentationdocumentation0%0% -------------------------------------------------------------------------------- /docs/docsets/Collor.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.collor 7 | CFBundleName 8 | Collor 9 | DocSetPlatformFamily 10 | collor 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/Collor.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/docs/docsets/Collor.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/Collor.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/docs/docsets/Collor.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/Collor.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/docs/docsets/Collor.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/Collor.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /docs/docsets/Collor.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/docs/docsets/Collor.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/Collor.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/docs/docsets/Collor.tgz -------------------------------------------------------------------------------- /docs/docsets/Collor.xml: -------------------------------------------------------------------------------- 1 | 1.1.0https://github.com/voyages-sncf-technologies/docsets/Collor.tgz 2 | -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/docs/img/gh.png -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /fastlane/FastFile: -------------------------------------------------------------------------------- 1 | lane :deploy do 2 | ensure_git_branch 3 | version = version_bump_podspec(path: "Collor.podspec") 4 | git_commit(path: ["./Collor.podspec", "./Example/Podfile.lock"], message: "Bumped to version #{version}") 5 | push_to_git_remote(remote_branch: 'master', force: false, tags: true) 6 | changelog = changelog_from_git_commits 7 | github_release = set_github_release( 8 | repository_name: "voyages-sncf-technologies/Collor", 9 | api_token: ENV["GITHUB_TOKEN"], 10 | name: version, 11 | tag_name: version, 12 | description: changelog, 13 | commitish: "master" 14 | ) 15 | sh("git fetch --tags") 16 | pod_push(allow_warnings: true, verbose: true) 17 | end -------------------------------------------------------------------------------- /jazzy.sh: -------------------------------------------------------------------------------- 1 | jazzy \ 2 | --clean \ 3 | --author "Voyages-sncf.com" \ 4 | --author_url https://voyages-sncf.com \ 5 | --github_url https://github.com/voyages-sncf-technologies/Collor \ 6 | --xcodebuild-arguments -workspace,Example/Collor.xcworkspace,-scheme,Collor \ 7 | --module Collor \ 8 | --root-url https://github.com/voyages-sncf-technologies/Collor \ 9 | --output docs -------------------------------------------------------------------------------- /resources/alphabet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/resources/alphabet.gif -------------------------------------------------------------------------------- /resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/resources/logo.png -------------------------------------------------------------------------------- /resources/pantone.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/resources/pantone.gif -------------------------------------------------------------------------------- /resources/random.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/resources/random.gif -------------------------------------------------------------------------------- /resources/realtime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/resources/realtime.gif -------------------------------------------------------------------------------- /resources/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/resources/screenshot.jpg -------------------------------------------------------------------------------- /resources/weather.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/resources/weather.gif -------------------------------------------------------------------------------- /resources/xctemplates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/resources/xctemplates.png -------------------------------------------------------------------------------- /xctemplates/CollorCell.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/xctemplates/CollorCell.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /xctemplates/CollorCell.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/xctemplates/CollorCell.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /xctemplates/CollorCell.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEFoundation.TextSubstitutionFileTemplateKind 7 | Description 8 | A CollectionViewCell for Collor with its Descripor and Adapter 9 | Summary 10 | A CollectionViewCell for Collor with its Descripor and Adapter 11 | SortOrder 12 | 1 13 | DefaultCompletionName 14 | MyClass 15 | Platforms 16 | 17 | com.apple.platform.iphoneos 18 | 19 | Options 20 | 21 | 22 | Identifier 23 | productName 24 | Required 25 | 26 | Name 27 | Cell Name: 28 | Description 29 | The name of the cell to create 30 | Type 31 | text 32 | NotPersisted 33 | 34 | 35 | 36 | Identifier 37 | aStaticString 38 | Name 39 | 40 | Default 41 | [CellName]CollectionViewCell.swift 42 | Type 43 | static 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /xctemplates/CollorCell.xctemplate/___FILEBASENAME___CollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | public final class ___VARIABLE_productName:identifier___CollectionViewCell: UICollectionViewCell, CollectionCellAdaptable { 13 | 14 | public override func awakeFromNib() { 15 | super.awakeFromNib() 16 | // Initialization code 17 | } 18 | 19 | public func update(with adapter: CollectionAdapter) { 20 | guard let adapter = adapter as? ___VARIABLE_productName:identifier___AdapterProtocol else { 21 | fatalError("___VARIABLE_productName:identifier___AdapterProtocol required") 22 | } 23 | } 24 | 25 | } 26 | 27 | public protocol ___VARIABLE_productName:identifier___AdapterProtocol: CollectionAdapter { 28 | } 29 | 30 | public struct ___VARIABLE_productName:identifier___Adapter: ___VARIABLE_productName:identifier___AdapterProtocol { 31 | 32 | public init() { 33 | 34 | } 35 | 36 | } 37 | 38 | public final class ___VARIABLE_productName:identifier___Descriptor: CollectionCellDescribable { 39 | 40 | public let identifier: String = "___VARIABLE_productName:identifier___CollectionViewCell" 41 | public let className: String = "___VARIABLE_productName:identifier___CollectionViewCell" 42 | public var selectable: Bool = false 43 | 44 | let adapter: ___VARIABLE_productName:identifier___AdapterProtocol 45 | 46 | public init(adapter: ___VARIABLE_productName:identifier___AdapterProtocol) { 47 | self.adapter = adapter 48 | } 49 | 50 | public func size(_ collectionView: UICollectionView, sectionDescriptor: CollectionSectionDescribable) -> CGSize { 51 | let sectionInset = sectionDescriptor.sectionInset(collectionView) 52 | let width: CGFloat = collectionView.bounds.width - sectionInset.left - sectionInset.right 53 | return CGSize(width: width, height: 100) 54 | } 55 | 56 | public func getAdapter() -> CollectionAdapter { 57 | return adapter 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /xctemplates/CollorCell.xctemplate/___FILEBASENAME___CollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /xctemplates/CollorController.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/xctemplates/CollorController.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /xctemplates/CollorController.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/xctemplates/CollorController.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /xctemplates/CollorController.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEFoundation.TextSubstitutionFileTemplateKind 7 | Description 8 | A UIViewController for Collor 9 | Summary 10 | A UIViewController for Collor 11 | SortOrder 12 | 1 13 | DefaultCompletionName 14 | MyClass 15 | Platforms 16 | 17 | com.apple.platform.iphoneos 18 | 19 | Options 20 | 21 | 22 | Identifier 23 | controllerName 24 | Required 25 | 26 | Name 27 | UIViewController Name: 28 | Description 29 | The name of the controller to create 30 | Type 31 | text 32 | Default 33 | ViewController 34 | NotPersisted 35 | 36 | 37 | 38 | Identifier 39 | collectionDataName 40 | Required 41 | 42 | Name 43 | CollectionData Name: 44 | Description 45 | The name of the collectionData to create 46 | Type 47 | text 48 | Default 49 | CollectionData 50 | NotPersisted 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /xctemplates/CollorController.xctemplate/___VARIABLE_collectionDataName___.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | //___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class ___VARIABLE_collectionDataName___ : CollectionData { 13 | 14 | override func reloadData() { 15 | super.reloadData() 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /xctemplates/CollorController.xctemplate/___VARIABLE_controllerName___.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | //___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | import Collor 11 | 12 | class ___VARIABLE_controllerName___: UIViewController { 13 | 14 | @IBOutlet weak var collectionView: UICollectionView! 15 | 16 | fileprivate(set) lazy var collectionViewDelegate: CollectionDelegate = CollectionDelegate(delegate: self) 17 | fileprivate(set) lazy var collectionViewDatasource: CollectionDataSource = CollectionDataSource(delegate: self) 18 | 19 | let collectionData = ___VARIABLE_collectionDataName___() 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | bind(collectionView: collectionView, with: collectionData, and: collectionViewDelegate, and: collectionViewDatasource) 25 | } 26 | } 27 | 28 | extension ___VARIABLE_controllerName___ : CollectionDidSelectCellDelegate { 29 | func didSelect(_ cellDescriptor: CollectionCellDescribable, sectionDescriptor: CollectionSectionDescribable, indexPath: IndexPath) {} 30 | } 31 | 32 | extension ___VARIABLE_controllerName___ : CollectionUserEventDelegate { 33 | 34 | } 35 | -------------------------------------------------------------------------------- /xctemplates/CollorController.xctemplate/___VARIABLE_controllerName___.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /xctemplates/CollorSection.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/xctemplates/CollorSection.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /xctemplates/CollorSection.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sncf-connect-tech/Collor/cdaf9ad08669b9ba3ebfa0219e4db13663675cfe/xctemplates/CollorSection.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /xctemplates/CollorSection.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEFoundation.TextSubstitutionFileTemplateKind 7 | Description 8 | A Section Descriptor for Collor 9 | Summary 10 | A Section Descriptor for Collor 11 | SortOrder 12 | 1 13 | DefaultCompletionName 14 | MyClass 15 | Platforms 16 | 17 | com.apple.platform.iphoneos 18 | 19 | Options 20 | 21 | 22 | Identifier 23 | productName 24 | Required 25 | 26 | Name 27 | Section Name: 28 | Description 29 | The name of the section to create 30 | Type 31 | text 32 | NotPersisted 33 | 34 | 35 | 36 | Identifier 37 | aStaticString 38 | Name 39 | 40 | Default 41 | [SectionName]SectionDescriptor.swift 42 | Type 43 | static 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /xctemplates/CollorSection.xctemplate/___FILEBASENAME___SectionDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | //___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | import Collor 11 | 12 | final class ___FILEBASENAMEASIDENTIFIER___SectionDescriptor: CollectionSectionDescribable { 13 | 14 | func sectionInset(_ collectionView: UICollectionView) -> UIEdgeInsets { 15 | return .zero 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /xctemplates/install.sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | echo "--- Deleting old templates ---" 4 | 5 | rm -rf ~/Library/Developer/Xcode/Templates/Collor/CollorCell.xctemplate 6 | rm -rf ~/Library/Developer/Xcode/Templates/Collor/CollorController.xctemplate 7 | rm -rf ~/Library/Developer/Xcode/Templates/Collor/CollorSection.xctemplate 8 | 9 | 10 | echo "--- Installing new templates ---" 11 | 12 | mkdir -p ~/Library/Developer/Xcode/Templates/Collor 13 | cp -r ./CollorCell.xctemplate ~/Library/Developer/Xcode/Templates/Collor 14 | cp -r ./CollorController.xctemplate ~/Library/Developer/Xcode/Templates/Collor 15 | cp -r ./CollorSection.xctemplate ~/Library/Developer/Xcode/Templates/Collor 16 | 17 | echo "--- Success ---" --------------------------------------------------------------------------------