├── Gemfile ├── UICollectionViewCompositionalLayoutDemo ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Scenes │ ├── Layouts │ │ ├── LayoutsSection.swift │ │ ├── LayoutsProtocols.swift │ │ ├── ViewComponents │ │ │ ├── AnyLayoutCellViewModel.swift │ │ │ ├── LayoutCellViewModel.swift │ │ │ └── LayoutTableViewCell.swift │ │ ├── LayoutsFactory.swift │ │ ├── LayoutsViewModel.swift │ │ └── LayoutsViewController.swift │ └── LayoutDetail │ │ ├── ViewComponents │ │ ├── SectionFooterView.swift │ │ ├── SectionHeaderView.swift │ │ ├── SectionReusableView.swift │ │ └── NumberedCollectionViewCell.swift │ │ ├── LayoutDetailView.swift │ │ └── LayoutDetailViewController.swift ├── Helpers │ ├── Protocols │ │ └── Dequeuable.swift │ └── Extensions │ │ ├── UITableView+Dequeuing.swift │ │ ├── UICollectionView+Dequeuing.swift │ │ └── UIView+Layout.swift ├── CompositionalLayouts │ ├── CompositionalLayoutProtocol.swift │ ├── CompositionalLayoutA.swift │ ├── CompositionalLayoutB.swift │ ├── CompositionalLayoutD.swift │ ├── CompositionalLayoutC.swift │ ├── CompositionalLayoutH.swift │ ├── CompositionalLayoutI.swift │ ├── CompositionalLayoutE.swift │ ├── CompositionalLayoutG.swift │ └── CompositionalLayoutF.swift ├── AppDelegate.swift ├── SceneDelegate.swift ├── MainView.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist └── MainViewController.swift ├── fastlane ├── Snapfile ├── screenshots │ ├── en-US │ │ ├── iPhone 11 Pro-Style A.png │ │ ├── iPhone 11 Pro-Style B.png │ │ ├── iPhone 11 Pro-Style C.png │ │ ├── iPhone 11 Pro-Style D.png │ │ ├── iPhone 11 Pro-Style E.png │ │ ├── iPhone 11 Pro-Style F.png │ │ ├── iPhone 11 Pro-Style G.png │ │ ├── iPhone 11 Pro-Style H.png │ │ └── iPhone 11 Pro-Style I.png │ └── screenshots.html ├── Appfile ├── Fastfile └── README.md ├── UICollectionViewCompositionalLayoutDemo.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── UICollectionViewCompositionalLayoutDemoUITests.xcscheme │ │ └── UICollectionViewCompositionalLayoutDemo.xcscheme └── project.pbxproj ├── UICollectionViewCompositionalLayoutDemoUITests ├── Info.plist ├── UICollectionViewCompositionalLayoutDemoUITests.swift └── SnapshotHelper.swift ├── README.md ├── .gitignore └── Gemfile.lock /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /fastlane/Snapfile: -------------------------------------------------------------------------------- 1 | devices([ 2 | "iPhone 11 Pro" 3 | ]) 4 | 5 | languages([ 6 | "en-US" 7 | ]) 8 | 9 | clear_previous_screenshots(true) 10 | 11 | override_status_bar(true) 12 | -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style A.png -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style B.png -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style C.png -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style D.png -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style E.png -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style F.png -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style G.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style G.png -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style H.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style H.png -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/iPhone 11 Pro-Style I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/HEAD/fastlane/screenshots/en-US/iPhone 11 Pro-Style I.png -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app 2 | # apple_id("[[APPLE_ID]]") # Your Apple email address 3 | 4 | 5 | # For more information about the Appfile, see: 6 | # https://docs.fastlane.tools/advanced/#appfile 7 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/Layouts/LayoutsSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutsSection.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 9/5/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | enum LayoutsSection { 10 | case main 11 | } 12 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/Layouts/LayoutsProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutsProtocols.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 2/09/21. 6 | // Copyright © 2021 Alonso. All rights reserved. 7 | // 8 | 9 | protocol LayoutsViewModelProtocol { 10 | 11 | func layout(at index: Int) -> CompositionalLayoutProtocol 12 | func makeLayoutCellViewModels() -> [AnyLayoutCellViewModel] 13 | 14 | } 15 | 16 | protocol LayoutsViewFactoryProtocol { 17 | 18 | var layouts: [CompositionalLayoutProtocol] { get } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/Layouts/ViewComponents/AnyLayoutCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnyLayoutCellViewModel.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 9/5/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | struct AnyLayoutCellViewModel: Hashable, LayoutCellViewModelProtocol { 10 | 11 | let title: String? 12 | let subtitle: String? 13 | 14 | init(_ viewModel: LayoutCellViewModelProtocol) { 15 | self.title = viewModel.title 16 | self.subtitle = viewModel.subtitle 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Helpers/Protocols/Dequeuable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dequeuable.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/29/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol Dequeuable { 12 | 13 | static var dequeuIdentifier: String { get } 14 | 15 | } 16 | 17 | extension Dequeuable where Self: UIView { 18 | 19 | static var dequeuIdentifier: String { 20 | return String(describing: self) 21 | } 22 | 23 | } 24 | 25 | extension UITableViewCell: Dequeuable { } 26 | 27 | extension UICollectionViewCell: Dequeuable { } 28 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/Layouts/ViewComponents/LayoutCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutCellViewModel.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/29/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | protocol LayoutCellViewModelProtocol { 10 | 11 | var title: String? { get } 12 | var subtitle: String? { get } 13 | 14 | } 15 | 16 | struct LayoutCellViewModel: LayoutCellViewModelProtocol { 17 | 18 | let title: String? 19 | let subtitle: String? 20 | 21 | init(_ layout: CompositionalLayoutProtocol) { 22 | self.title = layout.title 23 | self.subtitle = layout.subtitle 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/Layouts/LayoutsFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutsFactory.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 1/09/21. 6 | // Copyright © 2021 Alonso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct LayoutsFactory: LayoutsViewFactoryProtocol { 12 | 13 | var layouts: [CompositionalLayoutProtocol] { 14 | return [CompositionalLayoutA(), 15 | CompositionalLayoutB(), 16 | CompositionalLayoutC(), 17 | CompositionalLayoutD(), 18 | CompositionalLayoutE(), 19 | CompositionalLayoutF(), 20 | CompositionalLayoutG(), 21 | CompositionalLayoutH(), 22 | CompositionalLayoutI()] 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutProtocol.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol CompositionalLayoutProtocol { 12 | 13 | var title: String { get } 14 | var subtitle: String? { get } 15 | 16 | var numberOfSections: Int { get } 17 | var numberOfRowsPerSection: Int { get } 18 | 19 | func create() -> UICollectionViewLayout 20 | 21 | } 22 | 23 | extension CompositionalLayoutProtocol { 24 | 25 | var numberOfSections: Int { 26 | return 1 27 | } 28 | 29 | var numberOfRowsPerSection: Int { 30 | return 40 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:ios) 17 | 18 | platform :ios do 19 | 20 | lane :tests do 21 | run_tests( 22 | devices: ["iPhone 8"], 23 | scheme: "UICollectionViewCompositionalLayoutDemo" 24 | ) 25 | end 26 | 27 | lane :screenshots do 28 | capture_screenshots( 29 | skip_open_summary: true, 30 | dark_mode: true 31 | ) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/LayoutDetail/ViewComponents/SectionFooterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionFooterView.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/29/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SectionFooterView: SectionReusableView { 12 | 13 | // MARK: - Initializers 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | setupUI() 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | super.init(coder: coder) 22 | setupUI() 23 | } 24 | 25 | // MARK: - Private 26 | 27 | private func setupUI() { 28 | titleLabel.text = "Footer title" 29 | subtitleLabel.text = "Footer subtitle" 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/LayoutDetail/ViewComponents/SectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionHeaderView.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/29/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SectionHeaderView: SectionReusableView { 12 | 13 | // MARK: - Initializers 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | setupUI() 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | super.init(coder: coder) 22 | setupUI() 23 | } 24 | 25 | // MARK: - Private 26 | 27 | private func setupUI() { 28 | titleLabel.text = "Header title" 29 | subtitleLabel.text = "Header subtitle" 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios tests 20 | ``` 21 | fastlane ios tests 22 | ``` 23 | 24 | ### ios screenshots 25 | ``` 26 | fastlane ios screenshots 27 | ``` 28 | 29 | 30 | ---- 31 | 32 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 33 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 34 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 35 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Helpers/Extensions/UITableView+Dequeuing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Dequeuing.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/29/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITableView { 12 | 13 | // MARK: - Cell Register 14 | 15 | func register(cellType: T.Type, bundle: Bundle? = nil) { 16 | let identifier = cellType.dequeuIdentifier 17 | register(cellType, forCellReuseIdentifier: identifier) 18 | } 19 | 20 | // MARK: - Dequeuing 21 | 22 | func dequeueReusableCell(with type: T.Type, for indexPath: IndexPath) -> T { 23 | return self.dequeueReusableCell(withIdentifier: type.dequeuIdentifier, for: indexPath) as! T 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | return true 16 | } 17 | 18 | // MARK: UISceneSession Lifecycle 19 | 20 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 21 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | window = UIWindow(windowScene: windowScene) 18 | 19 | let viewModel = LayoutsViewModel(factory: LayoutsFactory()) 20 | let layoutsViewController = LayoutsViewController(viewModel: viewModel) 21 | let navigationController = UINavigationController(rootViewController: layoutsViewController) 22 | 23 | window?.rootViewController = navigationController 24 | window?.makeKeyAndVisible() 25 | } 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/Layouts/ViewComponents/LayoutTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutTableViewCell.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/16/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class LayoutTableViewCell: UITableViewCell { 12 | 13 | var viewModel: LayoutCellViewModelProtocol? { 14 | didSet { 15 | setupBindings() 16 | } 17 | } 18 | 19 | // MARK: - Initializers 20 | 21 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 22 | super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) 23 | setupUI() 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | super.init(coder: coder) 28 | setupUI() 29 | } 30 | 31 | // MARK: - Private 32 | 33 | private func setupUI() { 34 | accessoryType = .disclosureIndicator 35 | detailTextLabel?.numberOfLines = 0 36 | } 37 | 38 | // MARK: - Reactive Behavior 39 | 40 | private func setupBindings() { 41 | textLabel?.text = viewModel?.title 42 | detailTextLabel?.text = viewModel?.subtitle 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/Layouts/LayoutsViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutsViewModel.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/16/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | final class LayoutsViewModel: LayoutsViewModelProtocol { 10 | 11 | private lazy var layouts: [CompositionalLayoutProtocol] = { 12 | return [CompositionalLayoutA(), 13 | CompositionalLayoutB(), 14 | CompositionalLayoutC(), 15 | CompositionalLayoutD(), 16 | CompositionalLayoutE(), 17 | CompositionalLayoutF(), 18 | CompositionalLayoutG(), 19 | CompositionalLayoutH(), 20 | CompositionalLayoutI()] 21 | }() 22 | 23 | private let factory: LayoutsViewFactoryProtocol 24 | 25 | // MARK: - Initializers 26 | 27 | init(factory: LayoutsViewFactoryProtocol) { 28 | self.factory = factory 29 | } 30 | 31 | // MARK: - LayoutsViewModelProtocol 32 | 33 | func layout(at index: Int) -> CompositionalLayoutProtocol { 34 | return factory.layouts[index] 35 | } 36 | 37 | func makeLayoutCellViewModels() -> [AnyLayoutCellViewModel] { 38 | let cellViewModels = factory.layouts.map { LayoutCellViewModel($0) } 39 | return cellViewModels.map { AnyLayoutCellViewModel($0) } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutA.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutA.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutA: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style A" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Simple layout with an absolute height dimension." 19 | } 20 | 21 | func create() -> UICollectionViewLayout { 22 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 23 | heightDimension: .fractionalHeight(1.0)) 24 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 25 | item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 26 | 27 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 28 | heightDimension: .absolute(100)) 29 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, 30 | subitems: [item]) 31 | 32 | let section = NSCollectionLayoutSection(group: group) 33 | 34 | let layout = UICollectionViewCompositionalLayout(section: section) 35 | return layout 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UICollectionViewCompositionalLayoutDemo 2 | 3 | [![Platform](https://img.shields.io/badge/platform-iOS-yellow.svg)]() 4 | [![Swift 5](https://img.shields.io/badge/Swift-5-orange.svg?style=flat)](https://developer.apple.com/swift/) 5 | 6 | Sample project to demonstrate how to create different layouts using UICollectionViewCompositionalLayout which was released on iOS 13. 7 | 8 | ## Contents 9 | 10 | Each layout style can be found [here](https://github.com/DeluxeAlonso/UICollectionViewCompositionalLayoutDemo/tree/development/UICollectionViewCompositionalLayoutDemo/CompositionalLayouts). 11 | 12 | 13 | 14 | ## Contributing 15 | 16 | Feel free to open an issue or submit a pull request if you have any improvement or feedback. 17 | 18 | ## Author 19 | 20 | Alonso Alvarez, alonso.alvarez.dev@gmail.com 21 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Helpers/Extensions/UICollectionView+Dequeuing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Dequeuing.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/29/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionView { 12 | 13 | // MARK: - Cell Register & Dequeuing 14 | 15 | func register(cellType: T.Type) { 16 | let identifier = cellType.dequeuIdentifier 17 | register(cellType, forCellWithReuseIdentifier: identifier) 18 | } 19 | 20 | func dequeueReusableCell(with type: T.Type, for indexPath: IndexPath) -> T { 21 | return self.dequeueReusableCell(withReuseIdentifier: type.dequeuIdentifier, for: indexPath) as! T 22 | } 23 | 24 | // MARK: - Reusable View Register & Dequeuing 25 | 26 | func register(viewType: T.Type, kind: String) { 27 | register(viewType, forSupplementaryViewOfKind: kind, withReuseIdentifier: kind) 28 | } 29 | 30 | func dequeueReusableView(with type: T.Type, 31 | kind: String, 32 | for indexPath: IndexPath) -> T { 33 | return dequeueReusableSupplementaryView(ofKind: kind, 34 | withReuseIdentifier: kind, 35 | for: indexPath) as! T 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/LayoutDetail/ViewComponents/SectionReusableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionReusableView.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/24/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SectionReusableView: UICollectionReusableView { 12 | 13 | lazy var titleLabel: UILabel = { 14 | let label = UILabel() 15 | label.textColor = .label 16 | label.font = UIFontMetrics.default.scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .bold)) 17 | 18 | return label 19 | }() 20 | 21 | lazy var subtitleLabel: UILabel = { 22 | let label = UILabel() 23 | label.textColor = .secondaryLabel 24 | 25 | return label 26 | }() 27 | 28 | // MARK: - Initializers 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | setupUI() 33 | } 34 | 35 | required init?(coder: NSCoder) { 36 | super.init(coder: coder) 37 | setupUI() 38 | } 39 | 40 | // MARK: - Private 41 | 42 | private func setupUI() { 43 | let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) 44 | stackView.translatesAutoresizingMaskIntoConstraints = false 45 | stackView.axis = .vertical 46 | stackView.spacing = 5.0 47 | 48 | addSubview(stackView) 49 | 50 | stackView.fillSuperview(padding: .init(top: 12, left: 8, bottom: 12, right: 8)) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/MainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainView.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LayoutDetailView: UIView { 12 | 13 | lazy var collectionView: UICollectionView = { 14 | let layout = UICollectionViewLayout() 15 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 16 | collectionView.translatesAutoresizingMaskIntoConstraints = false 17 | collectionView.backgroundColor = .white 18 | 19 | return collectionView 20 | }() 21 | 22 | // MARK: - Initializers 23 | 24 | override init(frame: CGRect) { 25 | super.init(frame: frame) 26 | setupUI() 27 | } 28 | 29 | required init?(coder: NSCoder) { 30 | super.init(coder: coder) 31 | setupUI() 32 | } 33 | 34 | // MARK: - Private 35 | 36 | private func setupUI() { 37 | backgroundColor = .white 38 | 39 | setupCollectionView() 40 | } 41 | 42 | private func setupCollectionView() { 43 | addSubview(collectionView) 44 | NSLayoutConstraint.activate([ 45 | collectionView.topAnchor.constraint(equalTo: topAnchor), 46 | collectionView.bottomAnchor.constraint(equalTo: bottomAnchor), 47 | collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), 48 | collectionView.trailingAnchor.constraint(equalTo: trailingAnchor) 49 | ]) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/LayoutDetail/ViewComponents/NumberedCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberedCollectionViewCell.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/16/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class NumberedCollectionViewCell: UICollectionViewCell { 12 | 13 | private lazy var numberLabel: UILabel = { 14 | let label = UILabel() 15 | label.translatesAutoresizingMaskIntoConstraints = false 16 | label.textColor = .black 17 | label.textAlignment = .center 18 | 19 | let scaledFont = UIFont.systemFont(ofSize: 25, weight: .medium) 20 | label.font = UIFontMetrics.default.scaledFont(for: scaledFont) 21 | 22 | return label 23 | }() 24 | 25 | var number: Int = 0 { 26 | didSet { 27 | numberLabel.text = "\(number)" 28 | } 29 | } 30 | 31 | // MARK: - Initializers 32 | 33 | override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | setupUI() 36 | } 37 | 38 | required init?(coder: NSCoder) { 39 | super.init(coder: coder) 40 | setupUI() 41 | } 42 | 43 | // MARK: - Private 44 | 45 | private func setupUI() { 46 | backgroundColor = .lightGray 47 | 48 | layer.cornerRadius = 10 49 | 50 | setupLabels() 51 | } 52 | 53 | private func setupLabels() { 54 | addSubview(numberLabel) 55 | numberLabel.fillSuperview() 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutB.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutB: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style B" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Simple layout with square cells." 19 | } 20 | 21 | func create() -> UICollectionViewLayout { 22 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), 23 | heightDimension: .fractionalHeight(1.0)) 24 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 25 | item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 26 | 27 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 28 | heightDimension: .fractionalWidth(0.5)) 29 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 30 | group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 4, bottom: 0, trailing: 4) 31 | 32 | let section = NSCollectionLayoutSection(group: group) 33 | section.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0) 34 | 35 | let layout = UICollectionViewCompositionalLayout(section: section) 36 | return layout 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemoUITests/UICollectionViewCompositionalLayoutDemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewCompositionalLayoutDemoUITests.swift 3 | // UICollectionViewCompositionalLayoutDemoUITests 4 | // 5 | // Created by Alonso on 8/29/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class UICollectionViewCompositionalLayoutDemoUITests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | try super.setUpWithError() 15 | continueAfterFailure = false 16 | } 17 | 18 | override func tearDownWithError() throws { 19 | try super.tearDownWithError() 20 | } 21 | 22 | func testLayouts() { 23 | let app = XCUIApplication() 24 | setupSnapshot(app) 25 | app.activate() 26 | 27 | let cells = app.tables.cells 28 | 29 | for i in 0...cells.count - 1 { 30 | cells.element(boundBy: i).tap() 31 | 32 | let layoutCells = app.collectionViews.cells 33 | 34 | XCTAssert(layoutCells.count > 0) 35 | 36 | let collectionView = app.collectionViews.firstMatch 37 | let startPoint = collectionView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) 38 | let endPoint = collectionView.coordinate(withNormalizedOffset: CGVector(dx: 0.0, dy: 0.5)) 39 | startPoint.press(forDuration:1.0, thenDragTo:endPoint); 40 | 41 | snapshot(app.navigationBars.firstMatch.identifier) 42 | 43 | app.navigationBars.buttons.element(boundBy: 0).tap() 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/LayoutDetail/LayoutDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutDetailView.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class LayoutDetailView: UIView { 12 | 13 | lazy var collectionView: UICollectionView = { 14 | let layout = UICollectionViewLayout() 15 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 16 | collectionView.translatesAutoresizingMaskIntoConstraints = false 17 | collectionView.backgroundColor = .white 18 | 19 | return collectionView 20 | }() 21 | 22 | // MARK: - Initializers 23 | 24 | override init(frame: CGRect) { 25 | super.init(frame: frame) 26 | setupUI() 27 | } 28 | 29 | required init?(coder: NSCoder) { 30 | super.init(coder: coder) 31 | setupUI() 32 | } 33 | 34 | // MARK: - Private 35 | 36 | private func setupUI() { 37 | backgroundColor = .systemBackground 38 | 39 | setupCollectionView() 40 | } 41 | 42 | private func setupCollectionView() { 43 | collectionView.backgroundColor = .clear 44 | addSubview(collectionView) 45 | NSLayoutConstraint.activate([ 46 | collectionView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), 47 | collectionView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), 48 | collectionView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), 49 | collectionView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor) 50 | ]) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutD.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/16/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutD: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style D" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Sectioned layout that is scrollable in both axis." 19 | } 20 | 21 | var numberOfSections: Int { 22 | return 10 23 | } 24 | 25 | func create() -> UICollectionViewLayout { 26 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) 27 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 28 | item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 29 | 30 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.5)) 31 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 32 | 33 | let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44)) 34 | let headerElement = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top) 35 | 36 | let section = NSCollectionLayoutSection(group: group) 37 | section.boundarySupplementaryItems = [headerElement] 38 | section.orthogonalScrollingBehavior = .continuous 39 | 40 | let layout = UICollectionViewCompositionalLayout(section: section) 41 | return layout 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutC.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/16/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutC: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style C" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Simple layout with square cells and a single section header." 19 | } 20 | 21 | func create() -> UICollectionViewLayout { 22 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), 23 | heightDimension: .fractionalHeight(1.0)) 24 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 25 | item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4) 26 | 27 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 28 | heightDimension: .fractionalWidth(0.5)) 29 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 30 | group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 4, bottom: 0, trailing: 4) 31 | 32 | let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44)) 33 | let headerElement = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top) 34 | 35 | let section = NSCollectionLayoutSection(group: group) 36 | section.boundarySupplementaryItems = [headerElement] 37 | 38 | let layout = UICollectionViewCompositionalLayout(section: section) 39 | return layout 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutH.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutH.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 10/3/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutH: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style H" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Layout that contains a single leading item and a trailing nested group." 19 | } 20 | 21 | func create() -> UICollectionViewLayout { 22 | let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in 23 | let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1.0))) 24 | leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0) 25 | 26 | let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5))) 27 | trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0) 28 | 29 | let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1.0)), subitem: trailingItem, count: 3) 30 | 31 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.4)), subitems: [leadingItem, trailingGroup]) 32 | 33 | let section = NSCollectionLayoutSection(group: group) 34 | section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0) 35 | 36 | return section 37 | } 38 | 39 | return layout 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutI.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 20/08/21. 6 | // Copyright © 2021 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutI: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style I" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Layout that contains a single leading item and a trailing nested group with a continuous orthogonal scrolling behavior." 19 | } 20 | 21 | var numberOfSections: Int { 22 | return 10 23 | } 24 | 25 | var numberOfRowsPerSection: Int { 26 | return 40 27 | } 28 | 29 | func create() -> UICollectionViewLayout { 30 | let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in 31 | let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1.0))) 32 | leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0) 33 | 34 | let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5))) 35 | trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0) 36 | 37 | let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1.0)), subitem: trailingItem, count: 3) 38 | 39 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.4)), subitems: [leadingItem, trailingGroup]) 40 | 41 | let section = NSCollectionLayoutSection(group: group) 42 | section.orthogonalScrollingBehavior = .continuous 43 | section.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 5.0, bottom: 5.0, trailing: 5.0) 44 | 45 | return section 46 | } 47 | 48 | return layout 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/test_output 83 | 84 | # Code Injection 85 | # 86 | # After new code Injection tools there's a generated folder /iOSInjectionProject 87 | # https://github.com/johnno1962/injectionforxcode 88 | 89 | iOSInjectionProject/ 90 | .DS_Store 91 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutE.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutE.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/17/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutE: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style E" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Layout that makes use of more than one group's subitems." 19 | } 20 | 21 | private var topItem: NSCollectionLayoutItem { 22 | let topItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 23 | heightDimension: .fractionalWidth(0.5)) 24 | let topItem = NSCollectionLayoutItem(layoutSize: topItemSize) 25 | topItem.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 26 | 27 | return topItem 28 | } 29 | 30 | private var bottomItem: NSCollectionLayoutItem { 31 | let bottomItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)) 32 | let bottomItem = NSCollectionLayoutItem(layoutSize: bottomItemSize) 33 | bottomItem.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 34 | 35 | return bottomItem 36 | } 37 | 38 | func create() -> UICollectionViewLayout { 39 | 40 | // Group for bottom item, it repeats the bottom item twice 41 | let bottomGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), 42 | heightDimension: .fractionalWidth(0.5)) 43 | let bottomGroup = NSCollectionLayoutGroup.horizontal(layoutSize: bottomGroupSize, 44 | subitem: bottomItem, count: 2) 45 | 46 | // Combine the top item and bottom group 47 | let nestedGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 48 | heightDimension: .fractionalWidth(1)) 49 | let nestedGroup = NSCollectionLayoutGroup.vertical(layoutSize: nestedGroupSize, 50 | subitems: [topItem, bottomGroup]) 51 | 52 | let section = NSCollectionLayoutSection(group: nestedGroup) 53 | 54 | let layout = UICollectionViewCompositionalLayout(section: section) 55 | 56 | return layout 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LayoutDetailViewController: UIViewController { 12 | 13 | private var mainView: MainView! 14 | 15 | // MARK: - Lifecycle 16 | 17 | override func loadView() { 18 | mainView = MainView() 19 | view = mainView 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | configureUI() 25 | } 26 | 27 | // MARK: - Private 28 | 29 | private func configureUI() { 30 | title = "Alonso" 31 | configureCollectionView() 32 | } 33 | 34 | private func configureCollectionView() { 35 | let collectionView = mainView.collectionView 36 | 37 | collectionView.dataSource = self 38 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell") 39 | 40 | collectionView.collectionViewLayout = createCompositionalLayout() 41 | } 42 | 43 | func createCompositionalLayout() -> UICollectionViewCompositionalLayout { 44 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), 45 | heightDimension: .fractionalHeight(1.0)) 46 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 47 | item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 48 | 49 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 50 | heightDimension: .fractionalWidth(0.5)) 51 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 52 | 53 | let section = NSCollectionLayoutSection(group: group) 54 | 55 | let layout = UICollectionViewCompositionalLayout(section: section) 56 | return layout 57 | } 58 | 59 | } 60 | 61 | // MARK: - UICollectionViewDataSource 62 | 63 | extension LayoutDetailViewController: UICollectionViewDataSource { 64 | 65 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 66 | return 40 67 | } 68 | 69 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 70 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) 71 | cell.backgroundColor = .lightGray 72 | return cell 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutG.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutG.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/24/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutG: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style G" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Layout that contains two sections and a header and footer for each of the two sections." 19 | } 20 | 21 | var numberOfSections: Int { 22 | return 2 23 | } 24 | 25 | var numberOfRowsPerSection: Int { 26 | return 3 27 | } 28 | 29 | func create() -> UICollectionViewLayout { 30 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 31 | heightDimension: .fractionalHeight(1.0)) 32 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 33 | item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 34 | 35 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), 36 | heightDimension: .absolute(70)) 37 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, 38 | subitems: [item]) 39 | group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), 40 | top: nil, 41 | trailing: nil, 42 | bottom: nil) 43 | 44 | let footerHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 45 | heightDimension: .estimated(50.0)) 46 | let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: footerHeaderSize, 47 | elementKind: UICollectionView.elementKindSectionHeader, 48 | alignment: .top) 49 | let footer = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: footerHeaderSize, 50 | elementKind: UICollectionView.elementKindSectionFooter, 51 | alignment: .bottom) 52 | 53 | let section = NSCollectionLayoutSection(group: group) 54 | section.boundarySupplementaryItems = [header, footer] 55 | 56 | let layout = UICollectionViewCompositionalLayout(section: section) 57 | 58 | return layout 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/Layouts/LayoutsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutsViewController.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/16/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class LayoutsViewController: UITableViewController { 12 | 13 | private let viewModel: LayoutsViewModelProtocol 14 | 15 | private var dataSource: UITableViewDiffableDataSource! 16 | 17 | // MARK: - Initializers 18 | 19 | init(viewModel: LayoutsViewModelProtocol) { 20 | self.viewModel = viewModel 21 | super.init(nibName: nil, bundle: nil) 22 | } 23 | 24 | required init?(coder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | // MARK: - Lifecycle 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | configureUI() 33 | } 34 | 35 | // MARK: - Private 36 | 37 | private func configureUI() { 38 | title = "Compositional Layouts" 39 | configureTableView() 40 | } 41 | 42 | private func configureTableView() { 43 | tableView.estimatedRowHeight = 100 44 | tableView.rowHeight = UITableView.automaticDimension 45 | tableView.tableFooterView = UIView() 46 | 47 | tableView.register(cellType: LayoutTableViewCell.self) 48 | 49 | configureDataSource() 50 | updateUI() 51 | } 52 | 53 | private func configureDataSource() { 54 | dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, model -> UITableViewCell? in 55 | let cell = tableView.dequeueReusableCell(with: LayoutTableViewCell.self, for: indexPath) 56 | cell.viewModel = model 57 | 58 | return cell 59 | }) 60 | } 61 | 62 | private func updateUI(animated: Bool = false) { 63 | var currentSnapshot = NSDiffableDataSourceSnapshot() 64 | currentSnapshot.appendSections([.main]) 65 | currentSnapshot.appendItems(viewModel.makeLayoutCellViewModels(), toSection: .main) 66 | dataSource.apply(currentSnapshot, animatingDifferences: animated) 67 | } 68 | 69 | // MARK: - UITableViewControllerDelegate 70 | 71 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 72 | tableView.deselectRow(at: indexPath, animated: true) 73 | 74 | let selectedLayout = viewModel.layout(at: indexPath.row) 75 | let detailViewController = LayoutDetailViewController(compositionalLayout: selectedLayout) 76 | 77 | show(detailViewController, sender: nil) 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/CompositionalLayouts/CompositionalLayoutF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutF.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/18/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CompositionalLayoutF: CompositionalLayoutProtocol { 12 | 13 | var title: String { 14 | return "Style F" 15 | } 16 | 17 | var subtitle: String? { 18 | return "Layout that contains two sections. Each one has a different behavior." 19 | } 20 | 21 | var numberOfSections: Int { 22 | return 2 23 | } 24 | 25 | private func listSection() -> NSCollectionLayoutSection { 26 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 27 | heightDimension: .fractionalHeight(1.0)) 28 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 29 | item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 30 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 31 | heightDimension: .absolute(100)) 32 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, 33 | subitems: [item]) 34 | 35 | let section = NSCollectionLayoutSection(group: group) 36 | 37 | return section 38 | } 39 | 40 | private func gridSection() -> NSCollectionLayoutSection { 41 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), 42 | heightDimension: .fractionalHeight(1.0)) 43 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 44 | item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) 45 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 46 | heightDimension: .fractionalHeight(0.3)) 47 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, 48 | subitem: item, count: 3) 49 | let section = NSCollectionLayoutSection(group: group) 50 | section.orthogonalScrollingBehavior = .continuous 51 | 52 | return section 53 | } 54 | 55 | func create() -> UICollectionViewLayout { 56 | return UICollectionViewCompositionalLayout { sectionNumber, env -> NSCollectionLayoutSection? in 57 | switch sectionNumber { 58 | case 0: 59 | return self.gridSection() 60 | case 1: 61 | return self.listSection() 62 | default: 63 | return nil 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Scenes/LayoutDetail/LayoutDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutDetailViewController.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/15/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class LayoutDetailViewController: UIViewController { 12 | 13 | private let compositionalLayout: CompositionalLayoutProtocol 14 | private var layoutDetailView: LayoutDetailView! 15 | 16 | // MARK: - Initializers 17 | 18 | init(compositionalLayout: CompositionalLayoutProtocol) { 19 | self.compositionalLayout = compositionalLayout 20 | super.init(nibName: nil, bundle: nil) 21 | } 22 | 23 | required init?(coder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | // MARK: - Lifecycle 28 | 29 | override func loadView() { 30 | layoutDetailView = LayoutDetailView() 31 | view = layoutDetailView 32 | } 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | configureUI(with: compositionalLayout) 37 | } 38 | 39 | // MARK: - Private 40 | 41 | private func configureUI(with compositionalLayout: CompositionalLayoutProtocol) { 42 | title = compositionalLayout.title 43 | configureCollectionView(with: compositionalLayout) 44 | } 45 | 46 | private func configureCollectionView(with layout: CompositionalLayoutProtocol) { 47 | let collectionView = layoutDetailView.collectionView 48 | 49 | collectionView.dataSource = self 50 | 51 | collectionView.register(cellType: NumberedCollectionViewCell.self) 52 | collectionView.register(viewType: SectionHeaderView.self, kind: UICollectionView.elementKindSectionHeader) 53 | collectionView.register(viewType: SectionFooterView.self, kind: UICollectionView.elementKindSectionFooter) 54 | 55 | collectionView.collectionViewLayout = layout.create() 56 | } 57 | 58 | } 59 | 60 | // MARK: - UICollectionViewDataSource 61 | 62 | extension LayoutDetailViewController: UICollectionViewDataSource { 63 | 64 | func numberOfSections(in collectionView: UICollectionView) -> Int { 65 | return compositionalLayout.numberOfSections 66 | } 67 | 68 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 69 | return compositionalLayout.numberOfRowsPerSection 70 | } 71 | 72 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 73 | let cell = collectionView.dequeueReusableCell(with: NumberedCollectionViewCell.self, for: indexPath) 74 | cell.number = indexPath.row 75 | 76 | return cell 77 | } 78 | 79 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 80 | return collectionView.dequeueReusableView(with: UICollectionReusableView.self, kind: kind, for: indexPath) 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo.xcodeproj/xcshareddata/xcschemes/UICollectionViewCompositionalLayoutDemoUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo.xcodeproj/xcshareddata/xcschemes/UICollectionViewCompositionalLayoutDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo/Helpers/Extensions/UIView+Layout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Layout.swift 3 | // UICollectionViewCompositionalLayoutDemo 4 | // 5 | // Created by Alonso on 8/19/20. 6 | // Copyright © 2020 Alonso. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | 13 | @discardableResult 14 | func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, 15 | bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, 16 | padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> AnchoredConstraints { 17 | translatesAutoresizingMaskIntoConstraints = false 18 | var anchoredConstraints = AnchoredConstraints() 19 | 20 | if let top = top { 21 | anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top) 22 | } 23 | 24 | if let leading = leading { 25 | anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left) 26 | } 27 | 28 | if let bottom = bottom { 29 | anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom) 30 | } 31 | 32 | if let trailing = trailing { 33 | anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right) 34 | } 35 | 36 | if size.width != 0 { 37 | anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width) 38 | } 39 | 40 | if size.height != 0 { 41 | anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height) 42 | } 43 | 44 | [anchoredConstraints.top, 45 | anchoredConstraints.leading, 46 | anchoredConstraints.bottom, 47 | anchoredConstraints.trailing, 48 | anchoredConstraints.width, 49 | anchoredConstraints.height].forEach { $0?.isActive = true } 50 | 51 | return anchoredConstraints 52 | } 53 | 54 | func fillSuperview(padding: UIEdgeInsets = .zero) { 55 | translatesAutoresizingMaskIntoConstraints = false 56 | if let superviewTopAnchor = superview?.topAnchor { 57 | topAnchor.constraint(equalTo: superviewTopAnchor, constant: padding.top).isActive = true 58 | } 59 | 60 | if let superviewBottomAnchor = superview?.bottomAnchor { 61 | bottomAnchor.constraint(equalTo: superviewBottomAnchor, constant: -padding.bottom).isActive = true 62 | } 63 | 64 | if let superviewLeadingAnchor = superview?.leadingAnchor { 65 | leadingAnchor.constraint(equalTo: superviewLeadingAnchor, constant: padding.left).isActive = true 66 | } 67 | 68 | if let superviewTrailingAnchor = superview?.trailingAnchor { 69 | trailingAnchor.constraint(equalTo: superviewTrailingAnchor, constant: -padding.right).isActive = true 70 | } 71 | } 72 | 73 | func centerInSuperview(size: CGSize = .zero) { 74 | translatesAutoresizingMaskIntoConstraints = false 75 | if let superviewCenterXAnchor = superview?.centerXAnchor { 76 | centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true 77 | } 78 | 79 | if let superviewCenterYAnchor = superview?.centerYAnchor { 80 | centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true 81 | } 82 | 83 | if size.width != 0 { 84 | widthAnchor.constraint(equalToConstant: size.width).isActive = true 85 | } 86 | 87 | if size.height != 0 { 88 | heightAnchor.constraint(equalToConstant: size.height).isActive = true 89 | } 90 | } 91 | 92 | func centerXInSuperview() { 93 | translatesAutoresizingMaskIntoConstraints = false 94 | if let superViewCenterXAnchor = superview?.centerXAnchor { 95 | centerXAnchor.constraint(equalTo: superViewCenterXAnchor).isActive = true 96 | } 97 | } 98 | 99 | func centerYInSuperview() { 100 | translatesAutoresizingMaskIntoConstraints = false 101 | if let centerY = superview?.centerYAnchor { 102 | centerYAnchor.constraint(equalTo: centerY).isActive = true 103 | } 104 | } 105 | 106 | func constraintWidth(constant: CGFloat) { 107 | translatesAutoresizingMaskIntoConstraints = false 108 | widthAnchor.constraint(equalToConstant: constant).isActive = true 109 | } 110 | 111 | func constraintHeight(constant: CGFloat) { 112 | translatesAutoresizingMaskIntoConstraints = false 113 | heightAnchor.constraint(equalToConstant: constant).isActive = true 114 | } 115 | 116 | func constraintWidthAspectRatio(constant: CGFloat) { 117 | translatesAutoresizingMaskIntoConstraints = false 118 | widthAnchor.constraint(equalTo: heightAnchor, multiplier: constant).isActive = true 119 | } 120 | 121 | func constraintHeightAspectRatio(constant: CGFloat) { 122 | translatesAutoresizingMaskIntoConstraints = false 123 | heightAnchor.constraint(equalTo: widthAnchor, multiplier: constant).isActive = true 124 | } 125 | 126 | } 127 | 128 | struct AnchoredConstraints { 129 | var top, leading, bottom, trailing, width, height: NSLayoutConstraint? 130 | } 131 | 132 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.3) 5 | addressable (2.8.0) 6 | public_suffix (>= 2.0.2, < 5.0) 7 | artifactory (3.0.15) 8 | atomos (0.1.3) 9 | aws-eventstream (1.1.1) 10 | aws-partitions (1.493.0) 11 | aws-sdk-core (3.119.1) 12 | aws-eventstream (~> 1, >= 1.0.2) 13 | aws-partitions (~> 1, >= 1.239.0) 14 | aws-sigv4 (~> 1.1) 15 | jmespath (~> 1.0) 16 | aws-sdk-kms (1.47.0) 17 | aws-sdk-core (~> 3, >= 3.119.0) 18 | aws-sigv4 (~> 1.1) 19 | aws-sdk-s3 (1.100.0) 20 | aws-sdk-core (~> 3, >= 3.119.0) 21 | aws-sdk-kms (~> 1) 22 | aws-sigv4 (~> 1.1) 23 | aws-sigv4 (1.2.4) 24 | aws-eventstream (~> 1, >= 1.0.2) 25 | babosa (1.0.4) 26 | claide (1.0.3) 27 | colored (1.2) 28 | colored2 (3.1.2) 29 | commander (4.6.0) 30 | highline (~> 2.0.0) 31 | declarative (0.0.20) 32 | digest-crc (0.6.4) 33 | rake (>= 12.0.0, < 14.0.0) 34 | domain_name (0.5.20190701) 35 | unf (>= 0.0.5, < 1.0.0) 36 | dotenv (2.7.6) 37 | emoji_regex (3.2.2) 38 | excon (0.85.0) 39 | faraday (1.7.1) 40 | faraday-em_http (~> 1.0) 41 | faraday-em_synchrony (~> 1.0) 42 | faraday-excon (~> 1.1) 43 | faraday-httpclient (~> 1.0.1) 44 | faraday-net_http (~> 1.0) 45 | faraday-net_http_persistent (~> 1.1) 46 | faraday-patron (~> 1.0) 47 | faraday-rack (~> 1.0) 48 | multipart-post (>= 1.2, < 3) 49 | ruby2_keywords (>= 0.0.4) 50 | faraday-cookie_jar (0.0.7) 51 | faraday (>= 0.8.0) 52 | http-cookie (~> 1.0.0) 53 | faraday-em_http (1.0.0) 54 | faraday-em_synchrony (1.0.0) 55 | faraday-excon (1.1.0) 56 | faraday-httpclient (1.0.1) 57 | faraday-net_http (1.0.1) 58 | faraday-net_http_persistent (1.2.0) 59 | faraday-patron (1.0.0) 60 | faraday-rack (1.0.0) 61 | faraday_middleware (1.1.0) 62 | faraday (~> 1.0) 63 | fastimage (2.2.5) 64 | fastlane (2.193.0) 65 | CFPropertyList (>= 2.3, < 4.0.0) 66 | addressable (>= 2.8, < 3.0.0) 67 | artifactory (~> 3.0) 68 | aws-sdk-s3 (~> 1.0) 69 | babosa (>= 1.0.3, < 2.0.0) 70 | bundler (>= 1.12.0, < 3.0.0) 71 | colored 72 | commander (~> 4.6) 73 | dotenv (>= 2.1.1, < 3.0.0) 74 | emoji_regex (>= 0.1, < 4.0) 75 | excon (>= 0.71.0, < 1.0.0) 76 | faraday (~> 1.0) 77 | faraday-cookie_jar (~> 0.0.6) 78 | faraday_middleware (~> 1.0) 79 | fastimage (>= 2.1.0, < 3.0.0) 80 | gh_inspector (>= 1.1.2, < 2.0.0) 81 | google-apis-androidpublisher_v3 (~> 0.3) 82 | google-apis-playcustomapp_v1 (~> 0.1) 83 | google-cloud-storage (~> 1.31) 84 | highline (~> 2.0) 85 | json (< 3.0.0) 86 | jwt (>= 2.1.0, < 3) 87 | mini_magick (>= 4.9.4, < 5.0.0) 88 | multipart-post (~> 2.0.0) 89 | naturally (~> 2.2) 90 | optparse (~> 0.1.1) 91 | plist (>= 3.1.0, < 4.0.0) 92 | rubyzip (>= 2.0.0, < 3.0.0) 93 | security (= 0.1.3) 94 | simctl (~> 1.6.3) 95 | terminal-notifier (>= 2.0.0, < 3.0.0) 96 | terminal-table (>= 1.4.5, < 2.0.0) 97 | tty-screen (>= 0.6.3, < 1.0.0) 98 | tty-spinner (>= 0.8.0, < 1.0.0) 99 | word_wrap (~> 1.0.0) 100 | xcodeproj (>= 1.13.0, < 2.0.0) 101 | xcpretty (~> 0.3.0) 102 | xcpretty-travis-formatter (>= 0.0.3) 103 | gh_inspector (1.1.3) 104 | google-apis-androidpublisher_v3 (0.10.0) 105 | google-apis-core (>= 0.4, < 2.a) 106 | google-apis-core (0.4.1) 107 | addressable (~> 2.5, >= 2.5.1) 108 | googleauth (>= 0.16.2, < 2.a) 109 | httpclient (>= 2.8.1, < 3.a) 110 | mini_mime (~> 1.0) 111 | representable (~> 3.0) 112 | retriable (>= 2.0, < 4.a) 113 | rexml 114 | webrick 115 | google-apis-iamcredentials_v1 (0.7.0) 116 | google-apis-core (>= 0.4, < 2.a) 117 | google-apis-playcustomapp_v1 (0.5.0) 118 | google-apis-core (>= 0.4, < 2.a) 119 | google-apis-storage_v1 (0.6.0) 120 | google-apis-core (>= 0.4, < 2.a) 121 | google-cloud-core (1.6.0) 122 | google-cloud-env (~> 1.0) 123 | google-cloud-errors (~> 1.0) 124 | google-cloud-env (1.5.0) 125 | faraday (>= 0.17.3, < 2.0) 126 | google-cloud-errors (1.1.0) 127 | google-cloud-storage (1.34.1) 128 | addressable (~> 2.5) 129 | digest-crc (~> 0.4) 130 | google-apis-iamcredentials_v1 (~> 0.1) 131 | google-apis-storage_v1 (~> 0.1) 132 | google-cloud-core (~> 1.6) 133 | googleauth (>= 0.16.2, < 2.a) 134 | mini_mime (~> 1.0) 135 | googleauth (0.17.0) 136 | faraday (>= 0.17.3, < 2.0) 137 | jwt (>= 1.4, < 3.0) 138 | memoist (~> 0.16) 139 | multi_json (~> 1.11) 140 | os (>= 0.9, < 2.0) 141 | signet (~> 0.14) 142 | highline (2.0.3) 143 | http-cookie (1.0.4) 144 | domain_name (~> 0.5) 145 | httpclient (2.8.3) 146 | jmespath (1.4.0) 147 | json (2.5.1) 148 | jwt (2.2.3) 149 | memoist (0.16.2) 150 | mini_magick (4.11.0) 151 | mini_mime (1.1.1) 152 | multi_json (1.15.0) 153 | multipart-post (2.0.0) 154 | nanaimo (0.3.0) 155 | naturally (2.2.1) 156 | optparse (0.1.1) 157 | os (1.1.1) 158 | plist (3.6.0) 159 | public_suffix (4.0.6) 160 | rake (13.0.6) 161 | representable (3.1.1) 162 | declarative (< 0.1.0) 163 | trailblazer-option (>= 0.1.1, < 0.2.0) 164 | uber (< 0.2.0) 165 | retriable (3.1.2) 166 | rexml (3.2.5) 167 | rouge (2.0.7) 168 | ruby2_keywords (0.0.5) 169 | rubyzip (2.3.2) 170 | security (0.1.3) 171 | signet (0.15.0) 172 | addressable (~> 2.3) 173 | faraday (>= 0.17.3, < 2.0) 174 | jwt (>= 1.5, < 3.0) 175 | multi_json (~> 1.10) 176 | simctl (1.6.8) 177 | CFPropertyList 178 | naturally 179 | terminal-notifier (2.0.0) 180 | terminal-table (1.8.0) 181 | unicode-display_width (~> 1.1, >= 1.1.1) 182 | trailblazer-option (0.1.1) 183 | tty-cursor (0.7.1) 184 | tty-screen (0.8.1) 185 | tty-spinner (0.9.3) 186 | tty-cursor (~> 0.7) 187 | uber (0.1.0) 188 | unf (0.1.4) 189 | unf_ext 190 | unf_ext (0.0.7.7) 191 | unicode-display_width (1.7.0) 192 | webrick (1.7.0) 193 | word_wrap (1.0.0) 194 | xcodeproj (1.21.0) 195 | CFPropertyList (>= 2.3.3, < 4.0) 196 | atomos (~> 0.1.3) 197 | claide (>= 1.0.2, < 2.0) 198 | colored2 (~> 3.1) 199 | nanaimo (~> 0.3.0) 200 | rexml (~> 3.2.4) 201 | xcpretty (0.3.0) 202 | rouge (~> 2.0.7) 203 | xcpretty-travis-formatter (1.0.1) 204 | xcpretty (~> 0.2, >= 0.0.7) 205 | 206 | PLATFORMS 207 | ruby 208 | 209 | DEPENDENCIES 210 | fastlane 211 | 212 | BUNDLED WITH 213 | 2.2.13 214 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemoUITests/SnapshotHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnapshotHelper.swift 3 | // Example 4 | // 5 | // Created by Felix Krause on 10/8/15. 6 | // 7 | 8 | // ----------------------------------------------------- 9 | // IMPORTANT: When modifying this file, make sure to 10 | // increment the version number at the very 11 | // bottom of the file to notify users about 12 | // the new SnapshotHelper.swift 13 | // ----------------------------------------------------- 14 | 15 | import Foundation 16 | import XCTest 17 | 18 | var deviceLanguage = "" 19 | var locale = "" 20 | 21 | func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { 22 | Snapshot.setupSnapshot(app, waitForAnimations: waitForAnimations) 23 | } 24 | 25 | func snapshot(_ name: String, waitForLoadingIndicator: Bool) { 26 | if waitForLoadingIndicator { 27 | Snapshot.snapshot(name) 28 | } else { 29 | Snapshot.snapshot(name, timeWaitingForIdle: 0) 30 | } 31 | } 32 | 33 | /// - Parameters: 34 | /// - name: The name of the snapshot 35 | /// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait. 36 | func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { 37 | Snapshot.snapshot(name, timeWaitingForIdle: timeout) 38 | } 39 | 40 | enum SnapshotError: Error, CustomDebugStringConvertible { 41 | case cannotFindSimulatorHomeDirectory 42 | case cannotRunOnPhysicalDevice 43 | 44 | var debugDescription: String { 45 | switch self { 46 | case .cannotFindSimulatorHomeDirectory: 47 | return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." 48 | case .cannotRunOnPhysicalDevice: 49 | return "Can't use Snapshot on a physical device." 50 | } 51 | } 52 | } 53 | 54 | @objcMembers 55 | open class Snapshot: NSObject { 56 | static var app: XCUIApplication? 57 | static var waitForAnimations = true 58 | static var cacheDirectory: URL? 59 | static var screenshotsDirectory: URL? { 60 | return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true) 61 | } 62 | 63 | open class func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { 64 | 65 | Snapshot.app = app 66 | Snapshot.waitForAnimations = waitForAnimations 67 | 68 | do { 69 | let cacheDir = try getCacheDirectory() 70 | Snapshot.cacheDirectory = cacheDir 71 | setLanguage(app) 72 | setLocale(app) 73 | setLaunchArguments(app) 74 | } catch let error { 75 | NSLog(error.localizedDescription) 76 | } 77 | } 78 | 79 | class func setLanguage(_ app: XCUIApplication) { 80 | guard let cacheDirectory = self.cacheDirectory else { 81 | NSLog("CacheDirectory is not set - probably running on a physical device?") 82 | return 83 | } 84 | 85 | let path = cacheDirectory.appendingPathComponent("language.txt") 86 | 87 | do { 88 | let trimCharacterSet = CharacterSet.whitespacesAndNewlines 89 | deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) 90 | app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"] 91 | } catch { 92 | NSLog("Couldn't detect/set language...") 93 | } 94 | } 95 | 96 | class func setLocale(_ app: XCUIApplication) { 97 | guard let cacheDirectory = self.cacheDirectory else { 98 | NSLog("CacheDirectory is not set - probably running on a physical device?") 99 | return 100 | } 101 | 102 | let path = cacheDirectory.appendingPathComponent("locale.txt") 103 | 104 | do { 105 | let trimCharacterSet = CharacterSet.whitespacesAndNewlines 106 | locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) 107 | } catch { 108 | NSLog("Couldn't detect/set locale...") 109 | } 110 | 111 | if locale.isEmpty && !deviceLanguage.isEmpty { 112 | locale = Locale(identifier: deviceLanguage).identifier 113 | } 114 | 115 | if !locale.isEmpty { 116 | app.launchArguments += ["-AppleLocale", "\"\(locale)\""] 117 | } 118 | } 119 | 120 | class func setLaunchArguments(_ app: XCUIApplication) { 121 | guard let cacheDirectory = self.cacheDirectory else { 122 | NSLog("CacheDirectory is not set - probably running on a physical device?") 123 | return 124 | } 125 | 126 | let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt") 127 | app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"] 128 | 129 | do { 130 | let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8) 131 | let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: []) 132 | let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count)) 133 | let results = matches.map { result -> String in 134 | (launchArguments as NSString).substring(with: result.range) 135 | } 136 | app.launchArguments += results 137 | } catch { 138 | NSLog("Couldn't detect/set launch_arguments...") 139 | } 140 | } 141 | 142 | open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { 143 | if timeout > 0 { 144 | waitForLoadingIndicatorToDisappear(within: timeout) 145 | } 146 | 147 | NSLog("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work 148 | 149 | if Snapshot.waitForAnimations { 150 | sleep(1) // Waiting for the animation to be finished (kind of) 151 | } 152 | 153 | #if os(OSX) 154 | guard let app = self.app else { 155 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 156 | return 157 | } 158 | 159 | app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: []) 160 | #else 161 | 162 | guard self.app != nil else { 163 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 164 | return 165 | } 166 | 167 | let screenshot = XCUIScreen.main.screenshot() 168 | #if os(iOS) 169 | let image = XCUIDevice.shared.orientation.isLandscape ? fixLandscapeOrientation(image: screenshot.image) : screenshot.image 170 | #else 171 | let image = screenshot.image 172 | #endif 173 | 174 | guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } 175 | 176 | do { 177 | // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices 178 | let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ") 179 | let range = NSRange(location: 0, length: simulator.count) 180 | simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "") 181 | 182 | let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") 183 | #if swift(<5.0) 184 | UIImagePNGRepresentation(image)?.write(to: path, options: .atomic) 185 | #else 186 | try image.pngData()?.write(to: path, options: .atomic) 187 | #endif 188 | } catch let error { 189 | NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png") 190 | NSLog(error.localizedDescription) 191 | } 192 | #endif 193 | } 194 | 195 | class func fixLandscapeOrientation(image: UIImage) -> UIImage { 196 | #if os(watchOS) 197 | return image 198 | #else 199 | if #available(iOS 10.0, *) { 200 | let format = UIGraphicsImageRendererFormat() 201 | format.scale = image.scale 202 | let renderer = UIGraphicsImageRenderer(size: image.size, format: format) 203 | return renderer.image { context in 204 | image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) 205 | } 206 | } else { 207 | return image 208 | } 209 | #endif 210 | } 211 | 212 | class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) { 213 | #if os(tvOS) 214 | return 215 | #endif 216 | 217 | guard let app = self.app else { 218 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 219 | return 220 | } 221 | 222 | let networkLoadingIndicator = app.otherElements.deviceStatusBars.networkLoadingIndicators.element 223 | let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator) 224 | _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout) 225 | } 226 | 227 | class func getCacheDirectory() throws -> URL { 228 | let cachePath = "Library/Caches/tools.fastlane" 229 | // on OSX config is stored in /Users//Library 230 | // and on iOS/tvOS/WatchOS it's in simulator's home dir 231 | #if os(OSX) 232 | let homeDir = URL(fileURLWithPath: NSHomeDirectory()) 233 | return homeDir.appendingPathComponent(cachePath) 234 | #elseif arch(i386) || arch(x86_64) || arch(arm64) 235 | guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { 236 | throw SnapshotError.cannotFindSimulatorHomeDirectory 237 | } 238 | let homeDir = URL(fileURLWithPath: simulatorHostHome) 239 | return homeDir.appendingPathComponent(cachePath) 240 | #else 241 | throw SnapshotError.cannotRunOnPhysicalDevice 242 | #endif 243 | } 244 | } 245 | 246 | private extension XCUIElementAttributes { 247 | var isNetworkLoadingIndicator: Bool { 248 | if hasAllowListedIdentifier { return false } 249 | 250 | let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20) 251 | let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3) 252 | 253 | return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize 254 | } 255 | 256 | var hasAllowListedIdentifier: Bool { 257 | let allowListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] 258 | 259 | return allowListedIdentifiers.contains(identifier) 260 | } 261 | 262 | func isStatusBar(_ deviceWidth: CGFloat) -> Bool { 263 | if elementType == .statusBar { return true } 264 | guard frame.origin == .zero else { return false } 265 | 266 | let oldStatusBarSize = CGSize(width: deviceWidth, height: 20) 267 | let newStatusBarSize = CGSize(width: deviceWidth, height: 44) 268 | 269 | return [oldStatusBarSize, newStatusBarSize].contains(frame.size) 270 | } 271 | } 272 | 273 | private extension XCUIElementQuery { 274 | var networkLoadingIndicators: XCUIElementQuery { 275 | let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in 276 | guard let element = evaluatedObject as? XCUIElementAttributes else { return false } 277 | 278 | return element.isNetworkLoadingIndicator 279 | } 280 | 281 | return self.containing(isNetworkLoadingIndicator) 282 | } 283 | 284 | var deviceStatusBars: XCUIElementQuery { 285 | guard let app = Snapshot.app else { 286 | fatalError("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 287 | } 288 | 289 | let deviceWidth = app.windows.firstMatch.frame.width 290 | 291 | let isStatusBar = NSPredicate { (evaluatedObject, _) in 292 | guard let element = evaluatedObject as? XCUIElementAttributes else { return false } 293 | 294 | return element.isStatusBar(deviceWidth) 295 | } 296 | 297 | return self.containing(isStatusBar) 298 | } 299 | } 300 | 301 | private extension CGFloat { 302 | func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool { 303 | return numberA...numberB ~= self 304 | } 305 | } 306 | 307 | // Please don't remove the lines below 308 | // They are used to detect outdated configuration files 309 | // SnapshotHelperVersion [1.27] 310 | -------------------------------------------------------------------------------- /fastlane/screenshots/screenshots.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fastlane/snapshot 5 | 6 | 92 | 93 | 94 |
95 | 96 | 97 |
98 |

By Language:

99 |

en-US

100 |
101 | 102 | 103 | 106 | 107 | 108 | 113 | 118 | 123 | 128 | 133 | 138 | 143 | 148 | 153 | 154 |
104 | iPhone 11 Pro 105 |
109 | 110 | en-US iPhone 11 Pro 111 | 112 | 114 | 115 | en-US iPhone 11 Pro 116 | 117 | 119 | 120 | en-US iPhone 11 Pro 121 | 122 | 124 | 125 | en-US iPhone 11 Pro 126 | 127 | 129 | 130 | en-US iPhone 11 Pro 131 | 132 | 134 | 135 | en-US iPhone 11 Pro 136 | 137 | 139 | 140 | en-US iPhone 11 Pro 141 | 142 | 144 | 145 | en-US iPhone 11 Pro 146 | 147 | 149 | 150 | en-US iPhone 11 Pro 151 | 152 |
155 |
156 |

By Screen:

157 |

Style A

158 |
159 | 160 | 161 | 164 | 165 | 166 | 172 | 173 |
162 | iPhone 11 Pro 163 |
167 | 168 | en-US iPhone 11 Pro 169 | 170 |
en-US
171 |
174 |

Style B

175 |
176 | 177 | 178 | 181 | 182 | 183 | 189 | 190 |
179 | iPhone 11 Pro 180 |
184 | 185 | en-US iPhone 11 Pro 186 | 187 |
en-US
188 |
191 |

Style C

192 |
193 | 194 | 195 | 198 | 199 | 200 | 206 | 207 |
196 | iPhone 11 Pro 197 |
201 | 202 | en-US iPhone 11 Pro 203 | 204 |
en-US
205 |
208 |

Style D

209 |
210 | 211 | 212 | 215 | 216 | 217 | 223 | 224 |
213 | iPhone 11 Pro 214 |
218 | 219 | en-US iPhone 11 Pro 220 | 221 |
en-US
222 |
225 |

Style E

226 |
227 | 228 | 229 | 232 | 233 | 234 | 240 | 241 |
230 | iPhone 11 Pro 231 |
235 | 236 | en-US iPhone 11 Pro 237 | 238 |
en-US
239 |
242 |

Style F

243 |
244 | 245 | 246 | 249 | 250 | 251 | 257 | 258 |
247 | iPhone 11 Pro 248 |
252 | 253 | en-US iPhone 11 Pro 254 | 255 |
en-US
256 |
259 |

Style G

260 |
261 | 262 | 263 | 266 | 267 | 268 | 274 | 275 |
264 | iPhone 11 Pro 265 |
269 | 270 | en-US iPhone 11 Pro 271 | 272 |
en-US
273 |
276 |

Style H

277 |
278 | 279 | 280 | 283 | 284 | 285 | 291 | 292 |
281 | iPhone 11 Pro 282 |
286 | 287 | en-US iPhone 11 Pro 288 | 289 |
en-US
290 |
293 |

Style I

294 |
295 | 296 | 297 | 300 | 301 | 302 | 308 | 309 |
298 | iPhone 11 Pro 299 |
303 | 304 | en-US iPhone 11 Pro 305 | 306 |
en-US
307 |
310 |
311 |
312 | 313 |
314 |
315 | 460 | 461 | 462 | -------------------------------------------------------------------------------- /UICollectionViewCompositionalLayoutDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E209BD2524F4B9F40038A5EB /* CompositionalLayoutG.swift in Sources */ = {isa = PBXBuildFile; fileRef = E209BD2424F4B9F40038A5EB /* CompositionalLayoutG.swift */; }; 11 | E209BD2924F4CEC90038A5EB /* SectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E209BD2824F4CEC80038A5EB /* SectionReusableView.swift */; }; 12 | E22DE50B2500892C00881F53 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22DE50A2500892C00881F53 /* SnapshotHelper.swift */; }; 13 | E22FBB2725297B5200E52A2B /* CompositionalLayoutH.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22FBB2625297B5200E52A2B /* CompositionalLayoutH.swift */; }; 14 | E230042124EB73D600A34346 /* CompositionalLayoutE.swift in Sources */ = {isa = PBXBuildFile; fileRef = E230042024EB73D600A34346 /* CompositionalLayoutE.swift */; }; 15 | E23399B024EE16C900EBCFEE /* UIView+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E23399AF24EE16C900EBCFEE /* UIView+Layout.swift */; }; 16 | E23D840B24FB5FB3003F72D9 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E23D840A24FB5FB3003F72D9 /* SectionHeaderView.swift */; }; 17 | E23D840D24FB5FBF003F72D9 /* SectionFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E23D840C24FB5FBF003F72D9 /* SectionFooterView.swift */; }; 18 | E248DBFB24ECCE1B0067D5DB /* CompositionalLayoutF.swift in Sources */ = {isa = PBXBuildFile; fileRef = E248DBFA24ECCE1B0067D5DB /* CompositionalLayoutF.swift */; }; 19 | E25F6F8F24E88B7F00FCFF75 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25F6F8E24E88B7F00FCFF75 /* AppDelegate.swift */; }; 20 | E25F6F9124E88B7F00FCFF75 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25F6F9024E88B7F00FCFF75 /* SceneDelegate.swift */; }; 21 | E25F6F9824E88B8400FCFF75 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E25F6F9724E88B8400FCFF75 /* Assets.xcassets */; }; 22 | E25F6F9B24E88B8400FCFF75 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E25F6F9924E88B8400FCFF75 /* LaunchScreen.storyboard */; }; 23 | E25F6FB324E88E3D00FCFF75 /* LayoutDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25F6FB224E88E3D00FCFF75 /* LayoutDetailViewController.swift */; }; 24 | E25F6FB524E8924B00FCFF75 /* LayoutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25F6FB424E8924B00FCFF75 /* LayoutDetailView.swift */; }; 25 | E25F6FBA24E8E59000FCFF75 /* CompositionalLayoutProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25F6FB924E8E59000FCFF75 /* CompositionalLayoutProtocol.swift */; }; 26 | E25F6FBC24E8E5CE00FCFF75 /* CompositionalLayoutA.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25F6FBB24E8E5CE00FCFF75 /* CompositionalLayoutA.swift */; }; 27 | E25F6FBE24E8E5DC00FCFF75 /* CompositionalLayoutB.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25F6FBD24E8E5DC00FCFF75 /* CompositionalLayoutB.swift */; }; 28 | E26A1D6824FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26A1D6724FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests.swift */; }; 29 | E26A1D7024FB3F8000611FA3 /* UICollectionView+Dequeuing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26A1D6F24FB3F8000611FA3 /* UICollectionView+Dequeuing.swift */; }; 30 | E26A1D7224FB587600611FA3 /* Dequeuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26A1D7124FB587600611FA3 /* Dequeuable.swift */; }; 31 | E26A1D7424FB58F700611FA3 /* UITableView+Dequeuing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26A1D7324FB58F700611FA3 /* UITableView+Dequeuing.swift */; }; 32 | E26A1D7624FB5B5B00611FA3 /* LayoutCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26A1D7524FB5B5B00611FA3 /* LayoutCellViewModel.swift */; }; 33 | E2726FC826E0912E00F5624E /* LayoutsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2726FC726E0912E00F5624E /* LayoutsFactory.swift */; }; 34 | E277824A26E1DFE500BCBFAE /* LayoutsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = E277824926E1DFE500BCBFAE /* LayoutsProtocols.swift */; }; 35 | E28AF4F624EA222C000689D1 /* LayoutsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28AF4F524EA222C000689D1 /* LayoutsViewModel.swift */; }; 36 | E28AF4FC24EA2E98000689D1 /* LayoutTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28AF4FB24EA2E97000689D1 /* LayoutTableViewCell.swift */; }; 37 | E2A4AAB124E9763100AF73E2 /* CompositionalLayoutC.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A4AAB024E9763100AF73E2 /* CompositionalLayoutC.swift */; }; 38 | E2A4AABA24E9A69E00AF73E2 /* CompositionalLayoutD.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A4AAB924E9A69E00AF73E2 /* CompositionalLayoutD.swift */; }; 39 | E2A4AABC24E9ACDD00AF73E2 /* NumberedCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A4AABB24E9ACDD00AF73E2 /* NumberedCollectionViewCell.swift */; }; 40 | E2B2471A25048B8200C7289C /* AnyLayoutCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B2471925048B8200C7289C /* AnyLayoutCellViewModel.swift */; }; 41 | E2B2471C25049AC500C7289C /* LayoutsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B2471B25049AC500C7289C /* LayoutsSection.swift */; }; 42 | E2B92F2526CF80E600AC882C /* CompositionalLayoutI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B92F2426CF80E600AC882C /* CompositionalLayoutI.swift */; }; 43 | E2DD7CF924EA2004001C5678 /* LayoutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DD7CF824EA2004001C5678 /* LayoutsViewController.swift */; }; 44 | /* End PBXBuildFile section */ 45 | 46 | /* Begin PBXContainerItemProxy section */ 47 | E26A1D6A24FB18D200611FA3 /* PBXContainerItemProxy */ = { 48 | isa = PBXContainerItemProxy; 49 | containerPortal = E25F6F8324E88B7F00FCFF75 /* Project object */; 50 | proxyType = 1; 51 | remoteGlobalIDString = E25F6F8A24E88B7F00FCFF75; 52 | remoteInfo = UICollectionViewCompositionalLayoutDemo; 53 | }; 54 | /* End PBXContainerItemProxy section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | E209BD2424F4B9F40038A5EB /* CompositionalLayoutG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutG.swift; sourceTree = ""; }; 58 | E209BD2824F4CEC80038A5EB /* SectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionReusableView.swift; sourceTree = ""; }; 59 | E22DE50A2500892C00881F53 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotHelper.swift; sourceTree = ""; }; 60 | E22FBB2625297B5200E52A2B /* CompositionalLayoutH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutH.swift; sourceTree = ""; }; 61 | E230042024EB73D600A34346 /* CompositionalLayoutE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutE.swift; sourceTree = ""; }; 62 | E23399AF24EE16C900EBCFEE /* UIView+Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Layout.swift"; sourceTree = ""; }; 63 | E23D840A24FB5FB3003F72D9 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = ""; }; 64 | E23D840C24FB5FBF003F72D9 /* SectionFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionFooterView.swift; sourceTree = ""; }; 65 | E248DBFA24ECCE1B0067D5DB /* CompositionalLayoutF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutF.swift; sourceTree = ""; }; 66 | E25F6F8B24E88B7F00FCFF75 /* UICollectionViewCompositionalLayoutDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UICollectionViewCompositionalLayoutDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | E25F6F8E24E88B7F00FCFF75 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 68 | E25F6F9024E88B7F00FCFF75 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 69 | E25F6F9724E88B8400FCFF75 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 70 | E25F6F9A24E88B8400FCFF75 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 71 | E25F6F9C24E88B8400FCFF75 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | E25F6FB224E88E3D00FCFF75 /* LayoutDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDetailViewController.swift; sourceTree = ""; }; 73 | E25F6FB424E8924B00FCFF75 /* LayoutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDetailView.swift; sourceTree = ""; }; 74 | E25F6FB924E8E59000FCFF75 /* CompositionalLayoutProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutProtocol.swift; sourceTree = ""; }; 75 | E25F6FBB24E8E5CE00FCFF75 /* CompositionalLayoutA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutA.swift; sourceTree = ""; }; 76 | E25F6FBD24E8E5DC00FCFF75 /* CompositionalLayoutB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutB.swift; sourceTree = ""; }; 77 | E26A1D6524FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UICollectionViewCompositionalLayoutDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 78 | E26A1D6724FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionViewCompositionalLayoutDemoUITests.swift; sourceTree = ""; }; 79 | E26A1D6924FB18D200611FA3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 80 | E26A1D6F24FB3F8000611FA3 /* UICollectionView+Dequeuing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Dequeuing.swift"; sourceTree = ""; }; 81 | E26A1D7124FB587600611FA3 /* Dequeuable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dequeuable.swift; sourceTree = ""; }; 82 | E26A1D7324FB58F700611FA3 /* UITableView+Dequeuing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Dequeuing.swift"; sourceTree = ""; }; 83 | E26A1D7524FB5B5B00611FA3 /* LayoutCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutCellViewModel.swift; sourceTree = ""; }; 84 | E2726FC726E0912E00F5624E /* LayoutsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutsFactory.swift; sourceTree = ""; }; 85 | E277824926E1DFE500BCBFAE /* LayoutsProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutsProtocols.swift; sourceTree = ""; }; 86 | E28AF4F524EA222C000689D1 /* LayoutsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutsViewModel.swift; sourceTree = ""; }; 87 | E28AF4FB24EA2E97000689D1 /* LayoutTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutTableViewCell.swift; sourceTree = ""; }; 88 | E2A4AAB024E9763100AF73E2 /* CompositionalLayoutC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutC.swift; sourceTree = ""; }; 89 | E2A4AAB924E9A69E00AF73E2 /* CompositionalLayoutD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutD.swift; sourceTree = ""; }; 90 | E2A4AABB24E9ACDD00AF73E2 /* NumberedCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberedCollectionViewCell.swift; sourceTree = ""; }; 91 | E2B2471925048B8200C7289C /* AnyLayoutCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyLayoutCellViewModel.swift; sourceTree = ""; }; 92 | E2B2471B25049AC500C7289C /* LayoutsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutsSection.swift; sourceTree = ""; }; 93 | E2B92F2426CF80E600AC882C /* CompositionalLayoutI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayoutI.swift; sourceTree = ""; }; 94 | E2DD7CF824EA2004001C5678 /* LayoutsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutsViewController.swift; sourceTree = ""; }; 95 | /* End PBXFileReference section */ 96 | 97 | /* Begin PBXFrameworksBuildPhase section */ 98 | E25F6F8824E88B7F00FCFF75 /* Frameworks */ = { 99 | isa = PBXFrameworksBuildPhase; 100 | buildActionMask = 2147483647; 101 | files = ( 102 | ); 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | E26A1D6224FB18D200611FA3 /* Frameworks */ = { 106 | isa = PBXFrameworksBuildPhase; 107 | buildActionMask = 2147483647; 108 | files = ( 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | /* End PBXFrameworksBuildPhase section */ 113 | 114 | /* Begin PBXGroup section */ 115 | E23399AB24EE14F900EBCFEE /* Protocols */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | E26A1D7124FB587600611FA3 /* Dequeuable.swift */, 119 | ); 120 | path = Protocols; 121 | sourceTree = ""; 122 | }; 123 | E23399AE24EE16BE00EBCFEE /* Extensions */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | E23399AF24EE16C900EBCFEE /* UIView+Layout.swift */, 127 | E26A1D6F24FB3F8000611FA3 /* UICollectionView+Dequeuing.swift */, 128 | E26A1D7324FB58F700611FA3 /* UITableView+Dequeuing.swift */, 129 | ); 130 | path = Extensions; 131 | sourceTree = ""; 132 | }; 133 | E23D840924FB5F74003F72D9 /* ViewComponents */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | E2A4AABB24E9ACDD00AF73E2 /* NumberedCollectionViewCell.swift */, 137 | E209BD2824F4CEC80038A5EB /* SectionReusableView.swift */, 138 | E23D840A24FB5FB3003F72D9 /* SectionHeaderView.swift */, 139 | E23D840C24FB5FBF003F72D9 /* SectionFooterView.swift */, 140 | ); 141 | path = ViewComponents; 142 | sourceTree = ""; 143 | }; 144 | E25F6F8224E88B7F00FCFF75 = { 145 | isa = PBXGroup; 146 | children = ( 147 | E25F6F8D24E88B7F00FCFF75 /* UICollectionViewCompositionalLayoutDemo */, 148 | E26A1D6624FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests */, 149 | E25F6F8C24E88B7F00FCFF75 /* Products */, 150 | ); 151 | sourceTree = ""; 152 | }; 153 | E25F6F8C24E88B7F00FCFF75 /* Products */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | E25F6F8B24E88B7F00FCFF75 /* UICollectionViewCompositionalLayoutDemo.app */, 157 | E26A1D6524FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests.xctest */, 158 | ); 159 | name = Products; 160 | sourceTree = ""; 161 | }; 162 | E25F6F8D24E88B7F00FCFF75 /* UICollectionViewCompositionalLayoutDemo */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | E2A4AAB524E97A7B00AF73E2 /* Helpers */, 166 | E25F6FB824E8E57800FCFF75 /* CompositionalLayouts */, 167 | E25F6FB624E8E4F100FCFF75 /* Scenes */, 168 | E25F6F8E24E88B7F00FCFF75 /* AppDelegate.swift */, 169 | E25F6F9024E88B7F00FCFF75 /* SceneDelegate.swift */, 170 | E25F6F9724E88B8400FCFF75 /* Assets.xcassets */, 171 | E25F6F9924E88B8400FCFF75 /* LaunchScreen.storyboard */, 172 | E25F6F9C24E88B8400FCFF75 /* Info.plist */, 173 | ); 174 | path = UICollectionViewCompositionalLayoutDemo; 175 | sourceTree = ""; 176 | }; 177 | E25F6FB624E8E4F100FCFF75 /* Scenes */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | E2DD7CF724EA1FD5001C5678 /* Layouts */, 181 | E25F6FB724E8E4F900FCFF75 /* LayoutDetail */, 182 | ); 183 | path = Scenes; 184 | sourceTree = ""; 185 | }; 186 | E25F6FB724E8E4F900FCFF75 /* LayoutDetail */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | E23D840924FB5F74003F72D9 /* ViewComponents */, 190 | E25F6FB424E8924B00FCFF75 /* LayoutDetailView.swift */, 191 | E25F6FB224E88E3D00FCFF75 /* LayoutDetailViewController.swift */, 192 | ); 193 | path = LayoutDetail; 194 | sourceTree = ""; 195 | }; 196 | E25F6FB824E8E57800FCFF75 /* CompositionalLayouts */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | E25F6FB924E8E59000FCFF75 /* CompositionalLayoutProtocol.swift */, 200 | E25F6FBB24E8E5CE00FCFF75 /* CompositionalLayoutA.swift */, 201 | E25F6FBD24E8E5DC00FCFF75 /* CompositionalLayoutB.swift */, 202 | E2A4AAB024E9763100AF73E2 /* CompositionalLayoutC.swift */, 203 | E2A4AAB924E9A69E00AF73E2 /* CompositionalLayoutD.swift */, 204 | E230042024EB73D600A34346 /* CompositionalLayoutE.swift */, 205 | E248DBFA24ECCE1B0067D5DB /* CompositionalLayoutF.swift */, 206 | E209BD2424F4B9F40038A5EB /* CompositionalLayoutG.swift */, 207 | E22FBB2625297B5200E52A2B /* CompositionalLayoutH.swift */, 208 | E2B92F2426CF80E600AC882C /* CompositionalLayoutI.swift */, 209 | ); 210 | path = CompositionalLayouts; 211 | sourceTree = ""; 212 | }; 213 | E26A1D6624FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | E22DE50A2500892C00881F53 /* SnapshotHelper.swift */, 217 | E26A1D6724FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests.swift */, 218 | E26A1D6924FB18D200611FA3 /* Info.plist */, 219 | ); 220 | path = UICollectionViewCompositionalLayoutDemoUITests; 221 | sourceTree = ""; 222 | }; 223 | E26A1D7724FB5C1C00611FA3 /* ViewComponents */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | E28AF4FB24EA2E97000689D1 /* LayoutTableViewCell.swift */, 227 | E26A1D7524FB5B5B00611FA3 /* LayoutCellViewModel.swift */, 228 | E2B2471925048B8200C7289C /* AnyLayoutCellViewModel.swift */, 229 | ); 230 | path = ViewComponents; 231 | sourceTree = ""; 232 | }; 233 | E2A4AAB524E97A7B00AF73E2 /* Helpers */ = { 234 | isa = PBXGroup; 235 | children = ( 236 | E23399AE24EE16BE00EBCFEE /* Extensions */, 237 | E23399AB24EE14F900EBCFEE /* Protocols */, 238 | ); 239 | path = Helpers; 240 | sourceTree = ""; 241 | }; 242 | E2DD7CF724EA1FD5001C5678 /* Layouts */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | E26A1D7724FB5C1C00611FA3 /* ViewComponents */, 246 | E2DD7CF824EA2004001C5678 /* LayoutsViewController.swift */, 247 | E28AF4F524EA222C000689D1 /* LayoutsViewModel.swift */, 248 | E2B2471B25049AC500C7289C /* LayoutsSection.swift */, 249 | E2726FC726E0912E00F5624E /* LayoutsFactory.swift */, 250 | E277824926E1DFE500BCBFAE /* LayoutsProtocols.swift */, 251 | ); 252 | path = Layouts; 253 | sourceTree = ""; 254 | }; 255 | /* End PBXGroup section */ 256 | 257 | /* Begin PBXNativeTarget section */ 258 | E25F6F8A24E88B7F00FCFF75 /* UICollectionViewCompositionalLayoutDemo */ = { 259 | isa = PBXNativeTarget; 260 | buildConfigurationList = E25F6FAA24E88B8400FCFF75 /* Build configuration list for PBXNativeTarget "UICollectionViewCompositionalLayoutDemo" */; 261 | buildPhases = ( 262 | E25F6F8724E88B7F00FCFF75 /* Sources */, 263 | E25F6F8824E88B7F00FCFF75 /* Frameworks */, 264 | E25F6F8924E88B7F00FCFF75 /* Resources */, 265 | ); 266 | buildRules = ( 267 | ); 268 | dependencies = ( 269 | ); 270 | name = UICollectionViewCompositionalLayoutDemo; 271 | productName = UICollectionViewCompositionalLayoutDemo; 272 | productReference = E25F6F8B24E88B7F00FCFF75 /* UICollectionViewCompositionalLayoutDemo.app */; 273 | productType = "com.apple.product-type.application"; 274 | }; 275 | E26A1D6424FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests */ = { 276 | isa = PBXNativeTarget; 277 | buildConfigurationList = E26A1D6E24FB18D200611FA3 /* Build configuration list for PBXNativeTarget "UICollectionViewCompositionalLayoutDemoUITests" */; 278 | buildPhases = ( 279 | E26A1D6124FB18D200611FA3 /* Sources */, 280 | E26A1D6224FB18D200611FA3 /* Frameworks */, 281 | E26A1D6324FB18D200611FA3 /* Resources */, 282 | ); 283 | buildRules = ( 284 | ); 285 | dependencies = ( 286 | E26A1D6B24FB18D200611FA3 /* PBXTargetDependency */, 287 | ); 288 | name = UICollectionViewCompositionalLayoutDemoUITests; 289 | productName = UICollectionViewCompositionalLayoutDemoUITests; 290 | productReference = E26A1D6524FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests.xctest */; 291 | productType = "com.apple.product-type.bundle.ui-testing"; 292 | }; 293 | /* End PBXNativeTarget section */ 294 | 295 | /* Begin PBXProject section */ 296 | E25F6F8324E88B7F00FCFF75 /* Project object */ = { 297 | isa = PBXProject; 298 | attributes = { 299 | LastSwiftUpdateCheck = 1160; 300 | LastUpgradeCheck = 1250; 301 | ORGANIZATIONNAME = Alonso; 302 | TargetAttributes = { 303 | E25F6F8A24E88B7F00FCFF75 = { 304 | CreatedOnToolsVersion = 11.6; 305 | }; 306 | E26A1D6424FB18D200611FA3 = { 307 | CreatedOnToolsVersion = 11.6; 308 | TestTargetID = E25F6F8A24E88B7F00FCFF75; 309 | }; 310 | }; 311 | }; 312 | buildConfigurationList = E25F6F8624E88B7F00FCFF75 /* Build configuration list for PBXProject "UICollectionViewCompositionalLayoutDemo" */; 313 | compatibilityVersion = "Xcode 9.3"; 314 | developmentRegion = en; 315 | hasScannedForEncodings = 0; 316 | knownRegions = ( 317 | en, 318 | Base, 319 | ); 320 | mainGroup = E25F6F8224E88B7F00FCFF75; 321 | productRefGroup = E25F6F8C24E88B7F00FCFF75 /* Products */; 322 | projectDirPath = ""; 323 | projectRoot = ""; 324 | targets = ( 325 | E25F6F8A24E88B7F00FCFF75 /* UICollectionViewCompositionalLayoutDemo */, 326 | E26A1D6424FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests */, 327 | ); 328 | }; 329 | /* End PBXProject section */ 330 | 331 | /* Begin PBXResourcesBuildPhase section */ 332 | E25F6F8924E88B7F00FCFF75 /* Resources */ = { 333 | isa = PBXResourcesBuildPhase; 334 | buildActionMask = 2147483647; 335 | files = ( 336 | E25F6F9B24E88B8400FCFF75 /* LaunchScreen.storyboard in Resources */, 337 | E25F6F9824E88B8400FCFF75 /* Assets.xcassets in Resources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | E26A1D6324FB18D200611FA3 /* Resources */ = { 342 | isa = PBXResourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | /* End PBXResourcesBuildPhase section */ 349 | 350 | /* Begin PBXSourcesBuildPhase section */ 351 | E25F6F8724E88B7F00FCFF75 /* Sources */ = { 352 | isa = PBXSourcesBuildPhase; 353 | buildActionMask = 2147483647; 354 | files = ( 355 | E25F6FBA24E8E59000FCFF75 /* CompositionalLayoutProtocol.swift in Sources */, 356 | E209BD2924F4CEC90038A5EB /* SectionReusableView.swift in Sources */, 357 | E25F6FBE24E8E5DC00FCFF75 /* CompositionalLayoutB.swift in Sources */, 358 | E26A1D7424FB58F700611FA3 /* UITableView+Dequeuing.swift in Sources */, 359 | E26A1D7224FB587600611FA3 /* Dequeuable.swift in Sources */, 360 | E277824A26E1DFE500BCBFAE /* LayoutsProtocols.swift in Sources */, 361 | E2DD7CF924EA2004001C5678 /* LayoutsViewController.swift in Sources */, 362 | E2A4AABC24E9ACDD00AF73E2 /* NumberedCollectionViewCell.swift in Sources */, 363 | E25F6FB324E88E3D00FCFF75 /* LayoutDetailViewController.swift in Sources */, 364 | E28AF4FC24EA2E98000689D1 /* LayoutTableViewCell.swift in Sources */, 365 | E23D840D24FB5FBF003F72D9 /* SectionFooterView.swift in Sources */, 366 | E26A1D7624FB5B5B00611FA3 /* LayoutCellViewModel.swift in Sources */, 367 | E25F6FB524E8924B00FCFF75 /* LayoutDetailView.swift in Sources */, 368 | E230042124EB73D600A34346 /* CompositionalLayoutE.swift in Sources */, 369 | E2B2471C25049AC500C7289C /* LayoutsSection.swift in Sources */, 370 | E2A4AABA24E9A69E00AF73E2 /* CompositionalLayoutD.swift in Sources */, 371 | E22FBB2725297B5200E52A2B /* CompositionalLayoutH.swift in Sources */, 372 | E25F6FBC24E8E5CE00FCFF75 /* CompositionalLayoutA.swift in Sources */, 373 | E28AF4F624EA222C000689D1 /* LayoutsViewModel.swift in Sources */, 374 | E209BD2524F4B9F40038A5EB /* CompositionalLayoutG.swift in Sources */, 375 | E25F6F8F24E88B7F00FCFF75 /* AppDelegate.swift in Sources */, 376 | E23399B024EE16C900EBCFEE /* UIView+Layout.swift in Sources */, 377 | E2726FC826E0912E00F5624E /* LayoutsFactory.swift in Sources */, 378 | E23D840B24FB5FB3003F72D9 /* SectionHeaderView.swift in Sources */, 379 | E26A1D7024FB3F8000611FA3 /* UICollectionView+Dequeuing.swift in Sources */, 380 | E248DBFB24ECCE1B0067D5DB /* CompositionalLayoutF.swift in Sources */, 381 | E2B2471A25048B8200C7289C /* AnyLayoutCellViewModel.swift in Sources */, 382 | E2B92F2526CF80E600AC882C /* CompositionalLayoutI.swift in Sources */, 383 | E25F6F9124E88B7F00FCFF75 /* SceneDelegate.swift in Sources */, 384 | E2A4AAB124E9763100AF73E2 /* CompositionalLayoutC.swift in Sources */, 385 | ); 386 | runOnlyForDeploymentPostprocessing = 0; 387 | }; 388 | E26A1D6124FB18D200611FA3 /* Sources */ = { 389 | isa = PBXSourcesBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | E26A1D6824FB18D200611FA3 /* UICollectionViewCompositionalLayoutDemoUITests.swift in Sources */, 393 | E22DE50B2500892C00881F53 /* SnapshotHelper.swift in Sources */, 394 | ); 395 | runOnlyForDeploymentPostprocessing = 0; 396 | }; 397 | /* End PBXSourcesBuildPhase section */ 398 | 399 | /* Begin PBXTargetDependency section */ 400 | E26A1D6B24FB18D200611FA3 /* PBXTargetDependency */ = { 401 | isa = PBXTargetDependency; 402 | target = E25F6F8A24E88B7F00FCFF75 /* UICollectionViewCompositionalLayoutDemo */; 403 | targetProxy = E26A1D6A24FB18D200611FA3 /* PBXContainerItemProxy */; 404 | }; 405 | /* End PBXTargetDependency section */ 406 | 407 | /* Begin PBXVariantGroup section */ 408 | E25F6F9924E88B8400FCFF75 /* LaunchScreen.storyboard */ = { 409 | isa = PBXVariantGroup; 410 | children = ( 411 | E25F6F9A24E88B8400FCFF75 /* Base */, 412 | ); 413 | name = LaunchScreen.storyboard; 414 | sourceTree = ""; 415 | }; 416 | /* End PBXVariantGroup section */ 417 | 418 | /* Begin XCBuildConfiguration section */ 419 | E25F6FA824E88B8400FCFF75 /* Debug */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ALWAYS_SEARCH_USER_PATHS = NO; 423 | CLANG_ANALYZER_NONNULL = YES; 424 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_ENABLE_OBJC_ARC = YES; 429 | CLANG_ENABLE_OBJC_WEAK = YES; 430 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 431 | CLANG_WARN_BOOL_CONVERSION = YES; 432 | CLANG_WARN_COMMA = YES; 433 | CLANG_WARN_CONSTANT_CONVERSION = YES; 434 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 435 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 436 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 437 | CLANG_WARN_EMPTY_BODY = YES; 438 | CLANG_WARN_ENUM_CONVERSION = YES; 439 | CLANG_WARN_INFINITE_RECURSION = YES; 440 | CLANG_WARN_INT_CONVERSION = YES; 441 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 442 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 443 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 444 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 445 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 446 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 447 | CLANG_WARN_STRICT_PROTOTYPES = YES; 448 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 449 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 450 | CLANG_WARN_UNREACHABLE_CODE = YES; 451 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 452 | COPY_PHASE_STRIP = NO; 453 | DEBUG_INFORMATION_FORMAT = dwarf; 454 | ENABLE_STRICT_OBJC_MSGSEND = YES; 455 | ENABLE_TESTABILITY = YES; 456 | GCC_C_LANGUAGE_STANDARD = gnu11; 457 | GCC_DYNAMIC_NO_PIC = NO; 458 | GCC_NO_COMMON_BLOCKS = YES; 459 | GCC_OPTIMIZATION_LEVEL = 0; 460 | GCC_PREPROCESSOR_DEFINITIONS = ( 461 | "DEBUG=1", 462 | "$(inherited)", 463 | ); 464 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 465 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 466 | GCC_WARN_UNDECLARED_SELECTOR = YES; 467 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 468 | GCC_WARN_UNUSED_FUNCTION = YES; 469 | GCC_WARN_UNUSED_VARIABLE = YES; 470 | IPHONEOS_DEPLOYMENT_TARGET = 13.6; 471 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 472 | MTL_FAST_MATH = YES; 473 | ONLY_ACTIVE_ARCH = YES; 474 | SDKROOT = iphoneos; 475 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 476 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 477 | }; 478 | name = Debug; 479 | }; 480 | E25F6FA924E88B8400FCFF75 /* Release */ = { 481 | isa = XCBuildConfiguration; 482 | buildSettings = { 483 | ALWAYS_SEARCH_USER_PATHS = NO; 484 | CLANG_ANALYZER_NONNULL = YES; 485 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 486 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 487 | CLANG_CXX_LIBRARY = "libc++"; 488 | CLANG_ENABLE_MODULES = YES; 489 | CLANG_ENABLE_OBJC_ARC = YES; 490 | CLANG_ENABLE_OBJC_WEAK = YES; 491 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 492 | CLANG_WARN_BOOL_CONVERSION = YES; 493 | CLANG_WARN_COMMA = YES; 494 | CLANG_WARN_CONSTANT_CONVERSION = YES; 495 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 496 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 497 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 498 | CLANG_WARN_EMPTY_BODY = YES; 499 | CLANG_WARN_ENUM_CONVERSION = YES; 500 | CLANG_WARN_INFINITE_RECURSION = YES; 501 | CLANG_WARN_INT_CONVERSION = YES; 502 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 503 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 504 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 505 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 506 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 507 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 508 | CLANG_WARN_STRICT_PROTOTYPES = YES; 509 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 510 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 511 | CLANG_WARN_UNREACHABLE_CODE = YES; 512 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 513 | COPY_PHASE_STRIP = NO; 514 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 515 | ENABLE_NS_ASSERTIONS = NO; 516 | ENABLE_STRICT_OBJC_MSGSEND = YES; 517 | GCC_C_LANGUAGE_STANDARD = gnu11; 518 | GCC_NO_COMMON_BLOCKS = YES; 519 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 520 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 521 | GCC_WARN_UNDECLARED_SELECTOR = YES; 522 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 523 | GCC_WARN_UNUSED_FUNCTION = YES; 524 | GCC_WARN_UNUSED_VARIABLE = YES; 525 | IPHONEOS_DEPLOYMENT_TARGET = 13.6; 526 | MTL_ENABLE_DEBUG_INFO = NO; 527 | MTL_FAST_MATH = YES; 528 | SDKROOT = iphoneos; 529 | SWIFT_COMPILATION_MODE = wholemodule; 530 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 531 | VALIDATE_PRODUCT = YES; 532 | }; 533 | name = Release; 534 | }; 535 | E25F6FAB24E88B8400FCFF75 /* Debug */ = { 536 | isa = XCBuildConfiguration; 537 | buildSettings = { 538 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 539 | CODE_SIGN_STYLE = Automatic; 540 | DEVELOPMENT_TEAM = E95KU8J3PN; 541 | INFOPLIST_FILE = UICollectionViewCompositionalLayoutDemo/Info.plist; 542 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 543 | LD_RUNPATH_SEARCH_PATHS = ( 544 | "$(inherited)", 545 | "@executable_path/Frameworks", 546 | ); 547 | PRODUCT_BUNDLE_IDENTIFIER = com.Alonso.UICollectionViewCompositionalLayoutDemo; 548 | PRODUCT_NAME = "$(TARGET_NAME)"; 549 | SWIFT_VERSION = 5.0; 550 | TARGETED_DEVICE_FAMILY = "1,2"; 551 | }; 552 | name = Debug; 553 | }; 554 | E25F6FAC24E88B8400FCFF75 /* Release */ = { 555 | isa = XCBuildConfiguration; 556 | buildSettings = { 557 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 558 | CODE_SIGN_STYLE = Automatic; 559 | DEVELOPMENT_TEAM = E95KU8J3PN; 560 | INFOPLIST_FILE = UICollectionViewCompositionalLayoutDemo/Info.plist; 561 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 562 | LD_RUNPATH_SEARCH_PATHS = ( 563 | "$(inherited)", 564 | "@executable_path/Frameworks", 565 | ); 566 | PRODUCT_BUNDLE_IDENTIFIER = com.Alonso.UICollectionViewCompositionalLayoutDemo; 567 | PRODUCT_NAME = "$(TARGET_NAME)"; 568 | SWIFT_VERSION = 5.0; 569 | TARGETED_DEVICE_FAMILY = "1,2"; 570 | }; 571 | name = Release; 572 | }; 573 | E26A1D6C24FB18D200611FA3 /* Debug */ = { 574 | isa = XCBuildConfiguration; 575 | buildSettings = { 576 | CODE_SIGN_STYLE = Automatic; 577 | DEVELOPMENT_TEAM = E95KU8J3PN; 578 | INFOPLIST_FILE = UICollectionViewCompositionalLayoutDemoUITests/Info.plist; 579 | LD_RUNPATH_SEARCH_PATHS = ( 580 | "$(inherited)", 581 | "@executable_path/Frameworks", 582 | "@loader_path/Frameworks", 583 | ); 584 | PRODUCT_BUNDLE_IDENTIFIER = com.Alonso.UICollectionViewCompositionalLayoutDemoUITests; 585 | PRODUCT_NAME = "$(TARGET_NAME)"; 586 | SWIFT_VERSION = 5.0; 587 | TARGETED_DEVICE_FAMILY = "1,2"; 588 | TEST_TARGET_NAME = UICollectionViewCompositionalLayoutDemo; 589 | }; 590 | name = Debug; 591 | }; 592 | E26A1D6D24FB18D200611FA3 /* Release */ = { 593 | isa = XCBuildConfiguration; 594 | buildSettings = { 595 | CODE_SIGN_STYLE = Automatic; 596 | DEVELOPMENT_TEAM = E95KU8J3PN; 597 | INFOPLIST_FILE = UICollectionViewCompositionalLayoutDemoUITests/Info.plist; 598 | LD_RUNPATH_SEARCH_PATHS = ( 599 | "$(inherited)", 600 | "@executable_path/Frameworks", 601 | "@loader_path/Frameworks", 602 | ); 603 | PRODUCT_BUNDLE_IDENTIFIER = com.Alonso.UICollectionViewCompositionalLayoutDemoUITests; 604 | PRODUCT_NAME = "$(TARGET_NAME)"; 605 | SWIFT_VERSION = 5.0; 606 | TARGETED_DEVICE_FAMILY = "1,2"; 607 | TEST_TARGET_NAME = UICollectionViewCompositionalLayoutDemo; 608 | }; 609 | name = Release; 610 | }; 611 | /* End XCBuildConfiguration section */ 612 | 613 | /* Begin XCConfigurationList section */ 614 | E25F6F8624E88B7F00FCFF75 /* Build configuration list for PBXProject "UICollectionViewCompositionalLayoutDemo" */ = { 615 | isa = XCConfigurationList; 616 | buildConfigurations = ( 617 | E25F6FA824E88B8400FCFF75 /* Debug */, 618 | E25F6FA924E88B8400FCFF75 /* Release */, 619 | ); 620 | defaultConfigurationIsVisible = 0; 621 | defaultConfigurationName = Release; 622 | }; 623 | E25F6FAA24E88B8400FCFF75 /* Build configuration list for PBXNativeTarget "UICollectionViewCompositionalLayoutDemo" */ = { 624 | isa = XCConfigurationList; 625 | buildConfigurations = ( 626 | E25F6FAB24E88B8400FCFF75 /* Debug */, 627 | E25F6FAC24E88B8400FCFF75 /* Release */, 628 | ); 629 | defaultConfigurationIsVisible = 0; 630 | defaultConfigurationName = Release; 631 | }; 632 | E26A1D6E24FB18D200611FA3 /* Build configuration list for PBXNativeTarget "UICollectionViewCompositionalLayoutDemoUITests" */ = { 633 | isa = XCConfigurationList; 634 | buildConfigurations = ( 635 | E26A1D6C24FB18D200611FA3 /* Debug */, 636 | E26A1D6D24FB18D200611FA3 /* Release */, 637 | ); 638 | defaultConfigurationIsVisible = 0; 639 | defaultConfigurationName = Release; 640 | }; 641 | /* End XCConfigurationList section */ 642 | }; 643 | rootObject = E25F6F8324E88B7F00FCFF75 /* Project object */; 644 | } 645 | --------------------------------------------------------------------------------