├── .gitignore ├── .ruby-version ├── .swift-version ├── .swiftformat ├── .travis.yml ├── CompositionalLayoutViewController.podspec ├── CompositionalLayoutViewController ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── CollectionViewSection.swift │ ├── CompositionalLayoutViewController.swift │ ├── HighlightableCell.swift │ ├── SectionProvider.swift │ └── SupplementaryItem.swift ├── Example ├── CompositionalLayoutViewController.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ ├── CompositionalLayoutViewController-Example.xcscheme │ │ └── CompositionalLayoutViewController_Tests.xcscheme ├── CompositionalLayoutViewController.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── CompositionalLayoutViewController │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Class │ │ ├── ListViewController.swift │ │ ├── SampleViewController.swift │ │ ├── Section │ │ │ ├── ButtonSection │ │ │ │ ├── ButtonCell.swift │ │ │ │ ├── ButtonCell.xib │ │ │ │ └── ButtonSection.swift │ │ │ ├── ListSection.swift │ │ │ └── TextFormSection │ │ │ │ ├── TextForm.swift │ │ │ │ ├── TextFormCell.swift │ │ │ │ ├── TextFormCell.xib │ │ │ │ ├── TextFormSection.swift │ │ │ │ └── TextFormViewModel.swift │ │ └── String+Validation.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift ├── Podfile ├── Podfile.lock ├── Pods │ ├── Local Podspecs │ │ └── CompositionalLayoutViewController.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ └── project.pbxproj │ ├── SwiftFormat │ │ ├── CommandLineTool │ │ │ └── swiftformat │ │ ├── LICENSE.md │ │ └── README.md │ └── Target Support Files │ │ ├── CompositionalLayoutViewController │ │ ├── CompositionalLayoutViewController-Info.plist │ │ ├── CompositionalLayoutViewController-dummy.m │ │ ├── CompositionalLayoutViewController-prefix.pch │ │ ├── CompositionalLayoutViewController-umbrella.h │ │ ├── CompositionalLayoutViewController.debug.xcconfig │ │ ├── CompositionalLayoutViewController.modulemap │ │ └── CompositionalLayoutViewController.release.xcconfig │ │ ├── Pods-CompositionalLayoutViewController_Example │ │ ├── Pods-CompositionalLayoutViewController_Example-Info.plist │ │ ├── Pods-CompositionalLayoutViewController_Example-acknowledgements.markdown │ │ ├── Pods-CompositionalLayoutViewController_Example-acknowledgements.plist │ │ ├── Pods-CompositionalLayoutViewController_Example-dummy.m │ │ ├── Pods-CompositionalLayoutViewController_Example-frameworks.sh │ │ ├── Pods-CompositionalLayoutViewController_Example-umbrella.h │ │ ├── Pods-CompositionalLayoutViewController_Example.debug.xcconfig │ │ ├── Pods-CompositionalLayoutViewController_Example.modulemap │ │ └── Pods-CompositionalLayoutViewController_Example.release.xcconfig │ │ ├── Pods-CompositionalLayoutViewController_Tests │ │ ├── Pods-CompositionalLayoutViewController_Tests-Info.plist │ │ ├── Pods-CompositionalLayoutViewController_Tests-acknowledgements.markdown │ │ ├── Pods-CompositionalLayoutViewController_Tests-acknowledgements.plist │ │ ├── Pods-CompositionalLayoutViewController_Tests-dummy.m │ │ ├── Pods-CompositionalLayoutViewController_Tests-umbrella.h │ │ ├── Pods-CompositionalLayoutViewController_Tests.debug.xcconfig │ │ ├── Pods-CompositionalLayoutViewController_Tests.modulemap │ │ └── Pods-CompositionalLayoutViewController_Tests.release.xcconfig │ │ └── SwiftFormat │ │ ├── SwiftFormat.debug.xcconfig │ │ └── SwiftFormat.release.xcconfig └── Tests │ ├── Info.plist │ └── Tests.swift ├── Gemfile ├── Gemfile.lock ├── Image └── ss.png ├── LICENSE ├── Package.swift ├── README.md ├── _Pods.xcodeproj └── run_swift_format.sh /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/ruby,swift,macos,xcode,carthage,cocoapods,objective-c 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=ruby,swift,macos,xcode,carthage,cocoapods,objective-c 4 | 5 | ### Carthage ### 6 | # Carthage 7 | # 8 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 9 | # Carthage/Checkouts 10 | 11 | Carthage/Build 12 | 13 | ### CocoaPods ### 14 | ## CocoaPods GitIgnore Template 15 | 16 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 17 | # - Also handy if you have a large number of dependant pods 18 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 19 | Pods/ 20 | 21 | ### macOS ### 22 | # General 23 | .DS_Store 24 | .AppleDouble 25 | .LSOverride 26 | 27 | # Icon must end with two \r 28 | Icon 29 | 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | .com.apple.timemachine.donotpresent 42 | 43 | # Directories potentially created on remote AFP share 44 | .AppleDB 45 | .AppleDesktop 46 | Network Trash Folder 47 | Temporary Items 48 | .apdisk 49 | 50 | ### Objective-C ### 51 | # Xcode 52 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 53 | 54 | ## User settings 55 | xcuserdata/ 56 | 57 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 58 | *.xcscmblueprint 59 | *.xccheckout 60 | 61 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 62 | build/ 63 | DerivedData/ 64 | *.moved-aside 65 | *.pbxuser 66 | !default.pbxuser 67 | *.mode1v3 68 | !default.mode1v3 69 | *.mode2v3 70 | !default.mode2v3 71 | *.perspectivev3 72 | !default.perspectivev3 73 | 74 | ## Obj-C/Swift specific 75 | *.hmap 76 | 77 | ## App packaging 78 | *.ipa 79 | *.dSYM.zip 80 | *.dSYM 81 | 82 | # CocoaPods 83 | # We recommend against adding the Pods directory to your .gitignore. However 84 | # you should judge for yourself, the pros and cons are mentioned at: 85 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 86 | # Pods/ 87 | # Add this line if you want to avoid checking in source code from the Xcode workspace 88 | # *.xcworkspace 89 | 90 | # Carthage 91 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 92 | # Carthage/Checkouts 93 | 94 | Carthage/Build/ 95 | 96 | # fastlane 97 | # It is recommended to not store the screenshots in the git repo. 98 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 99 | # For more information about the recommended setup visit: 100 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 101 | 102 | fastlane/report.xml 103 | fastlane/Preview.html 104 | fastlane/screenshots/**/*.png 105 | fastlane/test_output 106 | 107 | # Code Injection 108 | # After new code Injection tools there's a generated folder /iOSInjectionProject 109 | # https://github.com/johnno1962/injectionforxcode 110 | 111 | iOSInjectionProject/ 112 | 113 | ### Objective-C Patch ### 114 | 115 | ### Ruby ### 116 | *.gem 117 | *.rbc 118 | /.config 119 | /coverage/ 120 | /InstalledFiles 121 | /pkg/ 122 | /spec/reports/ 123 | /spec/examples.txt 124 | /test/tmp/ 125 | /test/version_tmp/ 126 | /tmp/ 127 | 128 | # Used by dotenv library to load environment variables. 129 | # .env 130 | 131 | # Ignore Byebug command history file. 132 | .byebug_history 133 | 134 | ## Specific to RubyMotion: 135 | .dat* 136 | .repl_history 137 | *.bridgesupport 138 | build-iPhoneOS/ 139 | build-iPhoneSimulator/ 140 | 141 | ## Specific to RubyMotion (use of CocoaPods): 142 | # We recommend against adding the Pods directory to your .gitignore. However 143 | # you should judge for yourself, the pros and cons are mentioned at: 144 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 145 | # vendor/Pods/ 146 | 147 | ## Documentation cache and generated files: 148 | /.yardoc/ 149 | /_yardoc/ 150 | /doc/ 151 | /rdoc/ 152 | 153 | ## Environment normalization: 154 | /.bundle/ 155 | /vendor/bundle 156 | /lib/bundler/man/ 157 | 158 | # for a library or gem, you might want to ignore these files since the code is 159 | # intended to run in multiple environments; otherwise, check them in: 160 | # Gemfile.lock 161 | # .ruby-version 162 | # .ruby-gemset 163 | 164 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 165 | .rvmrc 166 | 167 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 168 | # .rubocop-https?--* 169 | 170 | ### Swift ### 171 | # Xcode 172 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 173 | 174 | 175 | 176 | 177 | 178 | 179 | ## Playgrounds 180 | timeline.xctimeline 181 | playground.xcworkspace 182 | 183 | # Swift Package Manager 184 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 185 | # Packages/ 186 | # Package.pins 187 | # Package.resolved 188 | # *.xcodeproj 189 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 190 | # hence it is not needed unless you have added a package configuration file to your project 191 | # .swiftpm 192 | 193 | .build/ 194 | 195 | # CocoaPods 196 | # We recommend against adding the Pods directory to your .gitignore. However 197 | # you should judge for yourself, the pros and cons are mentioned at: 198 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 199 | # Pods/ 200 | # Add this line if you want to avoid checking in source code from the Xcode workspace 201 | # *.xcworkspace 202 | 203 | # Carthage 204 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 205 | # Carthage/Checkouts 206 | 207 | 208 | # Add this lines if you are using Accio dependency management (Deprecated since Xcode 12) 209 | # Dependencies/ 210 | # .accio/ 211 | 212 | # fastlane 213 | # It is recommended to not store the screenshots in the git repo. 214 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 215 | # For more information about the recommended setup visit: 216 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 217 | 218 | 219 | # Code Injection 220 | # After new code Injection tools there's a generated folder /iOSInjectionProject 221 | # https://github.com/johnno1962/injectionforxcode 222 | 223 | 224 | ### Xcode ### 225 | # Xcode 226 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 227 | 228 | 229 | 230 | 231 | ## Gcc Patch 232 | /*.gcno 233 | 234 | ### Xcode Patch ### 235 | *.xcodeproj/* 236 | !*.xcodeproj/project.pbxproj 237 | !*.xcodeproj/xcshareddata/ 238 | !*.xcworkspace/contents.xcworkspacedata 239 | **/xcshareddata/WorkspaceSettings.xcsettings 240 | 241 | # End of https://www.toptal.com/developers/gitignore/api/ruby,swift,macos,xcode,carthage,cocoapods,objective-c -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.1 2 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.7 -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | # format options 2 | --binarygrouping none 3 | --closingparen balanced 4 | --commas inline 5 | --conflictmarkers reject 6 | 7 | --decimalgrouping none 8 | --octalgrouping none 9 | --hexgrouping none 10 | 11 | --elseposition next-line 12 | --guardelse same-line 13 | 14 | --empty void 15 | --exponentcase lowercase 16 | --exponentgrouping disabled 17 | --fractiongrouping disabled 18 | --fragment false 19 | --header ignore 20 | --hexliteralcase uppercase 21 | --ifdef indent 22 | --importgrouping alphabetized 23 | --indent 4 24 | --linebreaks lf 25 | 26 | --patternlet hoist 27 | --nospaceoperators 28 | --self remove 29 | --selfrequired 30 | --stripunusedargs closure-only 31 | --trailingclosures 32 | --trimwhitespace always 33 | --wraparguments before-first 34 | --wrapcollections before-first 35 | --xcodeindentation enabled 36 | --disable redundantReturn, wrapMultilineStatementBraces 37 | 38 | # file options 39 | --exclude Pods, Carthage, R.generated.swift -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/CompositionalLayoutViewController.xcworkspace -scheme CompositionalLayoutViewController-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /CompositionalLayoutViewController.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint CompositionalLayoutViewController.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "CompositionalLayoutViewController" 11 | s.version = "0.7.0" 12 | s.summary = "Lightweight UICollectionViewCompositionalLayout wrapper" 13 | s.swift_versions = "5.7" 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | Declaretive UICollectionViewCompositionalLayout interface to implement complex collection view layout. 22 | DESC 23 | 24 | s.homepage = "https://github.com/oneinc-jp/CompositionalLayoutViewController" 25 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 26 | s.license = { :type => "MIT", :file => "LICENSE" } 27 | s.author = { "Akira" => "akira.matsuda@me.com" } 28 | s.source = { :git => "https://github.com/oneinc-jp/CompositionalLayoutViewController.git", :tag => s.version.to_s } 29 | # s.social_media_url = 'https://twitter.com/' 30 | 31 | s.ios.deployment_target = "13.0" 32 | 33 | s.source_files = "CompositionalLayoutViewController/Classes/**/*" 34 | 35 | # s.resource_bundles = { 36 | # 'CompositionalLayoutViewController' => ['CompositionalLayoutViewController/Assets/*.png'] 37 | # } 38 | 39 | # s.public_header_files = 'Pod/Classes/**/*.h' 40 | # s.frameworks = 'UIKit', 'MapKit' 41 | # s.dependency 'AFNetworking', '~> 2.3' 42 | end 43 | -------------------------------------------------------------------------------- /CompositionalLayoutViewController/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oneinc-jp/CompositionalLayoutViewController/22a2ed014f5a5b064ea82d639bed63e8a79d477c/CompositionalLayoutViewController/Assets/.gitkeep -------------------------------------------------------------------------------- /CompositionalLayoutViewController/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oneinc-jp/CompositionalLayoutViewController/22a2ed014f5a5b064ea82d639bed63e8a79d477c/CompositionalLayoutViewController/Classes/.gitkeep -------------------------------------------------------------------------------- /CompositionalLayoutViewController/Classes/CollectionViewSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewSection.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/01/03. 6 | // 7 | 8 | import UIKit 9 | 10 | public protocol CollectionViewSection { 11 | var identifier: String { get } 12 | var snapshotItems: [AnyHashable] { get } 13 | 14 | func registerCell(collectionView: UICollectionView) 15 | func registerSupplementaryView(collectionView: UICollectionView) 16 | func layoutSection(environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection 17 | func cell(_ collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell? 18 | func supplementaryView(_ collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? 19 | func configureSupplementaryView(_ view: UICollectionReusableView, indexPath: IndexPath) 20 | 21 | func makeUnique(nonce: String) 22 | } 23 | 24 | var nonceKey: UInt8 = 0 25 | public extension CollectionViewSection { 26 | var nonce: String? { 27 | guard let associatedObject = objc_getAssociatedObject( 28 | self, 29 | &nonceKey 30 | ) as? String else { 31 | return nil 32 | } 33 | return associatedObject 34 | } 35 | 36 | var snapshotSection: AnyHashable { 37 | var hasher = Hasher() 38 | snapshotItems.forEach { 39 | hasher.combine($0) 40 | } 41 | hasher.combine(nonce) 42 | hasher.combine(identifier) 43 | return hasher.finalize() 44 | } 45 | 46 | func makeUnique(nonce: String = UUID().uuidString) { 47 | objc_setAssociatedObject( 48 | self, 49 | &nonceKey, 50 | nonce, 51 | .OBJC_ASSOCIATION_RETAIN 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CompositionalLayoutViewController/Classes/CompositionalLayoutViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionalLayoutViewController.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/01/23. 6 | // 7 | 8 | import UIKit 9 | 10 | @MainActor 11 | open class CompositionalLayoutViewController: UIViewController { 12 | public var collectionView: UICollectionView! 13 | public var highlightedColor: UIColor? 14 | public var dataSource: UICollectionViewDiffableDataSource! 15 | public weak var provider: SectionProvider? 16 | public var ignoreEmptySection = false 17 | 18 | override open func viewDidLoad() { 19 | super.viewDidLoad() 20 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout(configuration: layoutConfiguration())) 21 | collectionView.backgroundColor = .systemBackground 22 | collectionView.delegate = self 23 | view.addSubview(collectionView) 24 | collectionView.delaysContentTouches = false 25 | collectionView.translatesAutoresizingMaskIntoConstraints = false 26 | collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 27 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 28 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 29 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 30 | 31 | dataSource = UICollectionViewDiffableDataSource( 32 | collectionView: collectionView 33 | ) { [unowned self] _, indexPath, _ -> UICollectionViewCell? in 34 | return cell(for: indexPath) 35 | } 36 | dataSource.supplementaryViewProvider = { [unowned self] _, kind, indexPath in 37 | return supplementaryView(for: kind, indexPath: indexPath) 38 | } 39 | } 40 | 41 | open func cell(for indexPath: IndexPath) -> UICollectionViewCell? { 42 | guard let provider = provider else { 43 | return nil 44 | } 45 | let section = provider.section(for: indexPath.section) 46 | guard let cell = section.cell(collectionView, indexPath: indexPath) else { 47 | return nil 48 | } 49 | configureCell(cell) 50 | return cell 51 | } 52 | 53 | open func supplementaryView(for kind: String, indexPath: IndexPath) -> UICollectionReusableView? { 54 | guard let provider = provider else { 55 | return nil 56 | } 57 | let section = provider.section(for: indexPath.section) 58 | let view = section.supplementaryView( 59 | collectionView, 60 | kind: kind, 61 | indexPath: indexPath 62 | ) 63 | if let view = view { 64 | section.configureSupplementaryView(view, indexPath: indexPath) 65 | configureSupplementaryView(view, indexPath: indexPath) 66 | } 67 | return view 68 | } 69 | 70 | open func layout(configuration: UICollectionViewCompositionalLayoutConfiguration) -> UICollectionViewCompositionalLayout { 71 | return UICollectionViewCompositionalLayout(sectionProvider: { [unowned self] sectionIndex, environment -> NSCollectionLayoutSection? in 72 | guard let provider = provider else { 73 | return nil 74 | } 75 | let section = provider.section(for: sectionIndex) 76 | let layout = section.layoutSection(environment: environment) 77 | configureSection(section, layout: layout) 78 | return layout 79 | }, configuration: configuration) 80 | } 81 | 82 | open func layoutConfiguration() -> UICollectionViewCompositionalLayoutConfiguration { 83 | return UICollectionViewCompositionalLayoutConfiguration() 84 | } 85 | 86 | open func configureCell(_ cell: UICollectionViewCell) {} 87 | 88 | open func configureSection(_ section: CollectionViewSection, layout: NSCollectionLayoutSection) {} 89 | 90 | open func configureSupplementaryView(_ view: UICollectionReusableView, indexPath: IndexPath) {} 91 | 92 | open func registerViews(_ sections: [CollectionViewSection]) { 93 | for section in sections { 94 | section.registerCell(collectionView: collectionView) 95 | section.registerSupplementaryView(collectionView: collectionView) 96 | } 97 | } 98 | 99 | open func updateDataSource(_ sections: [CollectionViewSection], animateWhenUpdate: Bool = true) { 100 | registerViews(sections) 101 | var snapshot = NSDiffableDataSourceSnapshot() 102 | for section in sections { 103 | if section.snapshotItems.isEmpty { 104 | if ignoreEmptySection == false { 105 | snapshot.appendSections([section.snapshotSection]) 106 | snapshot.appendItems(section.snapshotItems, toSection: section.snapshotSection) 107 | } 108 | } 109 | else { 110 | snapshot.appendSections([section.snapshotSection]) 111 | snapshot.appendItems(section.snapshotItems, toSection: section.snapshotSection) 112 | } 113 | } 114 | dataSource.apply(snapshot, animatingDifferences: animateWhenUpdate) 115 | } 116 | 117 | open func reloadSections() { 118 | guard let provider = provider else { 119 | return 120 | } 121 | updateDataSource(provider.sections) 122 | } 123 | 124 | open func didSelectItem(at indexPath: IndexPath) {} 125 | } 126 | 127 | extension CompositionalLayoutViewController: UICollectionViewDelegate { 128 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 129 | didSelectItem(at: indexPath) 130 | } 131 | 132 | public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { 133 | if let cell = collectionView.cellForItem(at: indexPath) as? HighlightableCell, cell.clvc_isHighlightable { 134 | cell.contentView.backgroundColor = cell.clvc_highlightedColor 135 | } 136 | } 137 | 138 | public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) { 139 | if let cell = collectionView.cellForItem(at: indexPath) as? HighlightableCell, cell.clvc_isHighlightable { 140 | cell.contentView.backgroundColor = cell.clvc_defaultColor 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /CompositionalLayoutViewController/Classes/HighlightableCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HighlightableCell.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/01/06. 6 | // 7 | 8 | import UIKit 9 | 10 | public protocol HighlightableCell where Self: UICollectionViewCell { 11 | var clvc_isHighlightable: Bool { get } 12 | var clvc_highlightedColor: UIColor? { get } 13 | var clvc_defaultColor: UIColor? { get } 14 | } 15 | 16 | public extension HighlightableCell { 17 | var clvc_defaultColor: UIColor? { 18 | return nil 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CompositionalLayoutViewController/Classes/SectionProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionProvider.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol SectionProvider: AnyObject { 11 | var sections: [CollectionViewSection] { get } 12 | 13 | func section(for sectionIndex: Int) -> CollectionViewSection 14 | } 15 | 16 | public extension SectionProvider { 17 | func section(for sectionIndex: Int) -> CollectionViewSection { 18 | return sections[sectionIndex] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CompositionalLayoutViewController/Classes/SupplementaryItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupplementaryItem.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/08/14. 6 | // 7 | 8 | import UIKit 9 | 10 | public protocol SupplementaryItem { 11 | static var elementKind: String { get } 12 | 13 | static func supplementaryItem(absoluteOffset: CGPoint) -> NSCollectionLayoutBoundarySupplementaryItem 14 | } 15 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 11 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 12 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 13 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 14 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 15 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 16 | C49C371C4E1E83B492D31200 /* Pods_CompositionalLayoutViewController_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D759A42478EA941E93F55198 /* Pods_CompositionalLayoutViewController_Example.framework */; }; 17 | E37FBF8AD360BE9B297C172E /* Pods_CompositionalLayoutViewController_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CB8C44FA6C15F272D2A500 /* Pods_CompositionalLayoutViewController_Tests.framework */; }; 18 | E906109326576A63008ACD4F /* SampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E906109226576A63008ACD4F /* SampleViewController.swift */; }; 19 | E906109E26576E15008ACD4F /* String+Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E906109D26576E15008ACD4F /* String+Validation.swift */; }; 20 | E97C12E32715D00800ED1A24 /* ButtonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E97C12DA2715D00700ED1A24 /* ButtonCell.xib */; }; 21 | E97C12E42715D00800ED1A24 /* ButtonSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97C12DB2715D00700ED1A24 /* ButtonSection.swift */; }; 22 | E97C12E52715D00800ED1A24 /* ButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97C12DC2715D00700ED1A24 /* ButtonCell.swift */; }; 23 | E97C12E62715D00800ED1A24 /* TextForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97C12DE2715D00700ED1A24 /* TextForm.swift */; }; 24 | E97C12E72715D00800ED1A24 /* TextFormCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E97C12DF2715D00700ED1A24 /* TextFormCell.xib */; }; 25 | E97C12E82715D00800ED1A24 /* TextFormCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97C12E02715D00700ED1A24 /* TextFormCell.swift */; }; 26 | E97C12E92715D00800ED1A24 /* TextFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97C12E12715D00700ED1A24 /* TextFormViewModel.swift */; }; 27 | E97C12EA2715D00800ED1A24 /* TextFormSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97C12E22715D00800ED1A24 /* TextFormSection.swift */; }; 28 | E97C12EC2715D01000ED1A24 /* ListSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97C12EB2715D01000ED1A24 /* ListSection.swift */; }; 29 | E97C12EE2715D30300ED1A24 /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97C12ED2715D30300ED1A24 /* ListViewController.swift */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 38 | remoteInfo = CompositionalLayoutViewController; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 0E03A8EABD152021E463CE1F /* CompositionalLayoutViewController.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = CompositionalLayoutViewController.podspec; path = ../CompositionalLayoutViewController.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 44 | 26773E24238F6C4E2DEA3BE9 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 45 | 3528888696ADB633A88363B2 /* Pods-CompositionalLayoutViewController_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CompositionalLayoutViewController_Example.release.xcconfig"; path = "Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example.release.xcconfig"; sourceTree = ""; }; 46 | 607FACD01AFB9204008FA782 /* CompositionalLayoutViewController_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CompositionalLayoutViewController_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 50 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 52 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 53 | 607FACE51AFB9204008FA782 /* CompositionalLayoutViewController_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CompositionalLayoutViewController_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 56 | 8265BA2D021232193464EA8B /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 57 | A7A122714DCD249B94636896 /* Pods-CompositionalLayoutViewController_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CompositionalLayoutViewController_Tests.release.xcconfig"; path = "Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests.release.xcconfig"; sourceTree = ""; }; 58 | AAC622A8D1D3D19964B6223E /* Pods-CompositionalLayoutViewController_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CompositionalLayoutViewController_Example.debug.xcconfig"; path = "Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example.debug.xcconfig"; sourceTree = ""; }; 59 | B63D874721A7B7FE36BD705E /* Pods-CompositionalLayoutViewController_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CompositionalLayoutViewController_Tests.debug.xcconfig"; path = "Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests.debug.xcconfig"; sourceTree = ""; }; 60 | D759A42478EA941E93F55198 /* Pods_CompositionalLayoutViewController_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CompositionalLayoutViewController_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | E5CB8C44FA6C15F272D2A500 /* Pods_CompositionalLayoutViewController_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CompositionalLayoutViewController_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | E906109226576A63008ACD4F /* SampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleViewController.swift; sourceTree = ""; }; 63 | E906109D26576E15008ACD4F /* String+Validation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Validation.swift"; sourceTree = ""; }; 64 | E97C12DA2715D00700ED1A24 /* ButtonCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ButtonCell.xib; sourceTree = ""; }; 65 | E97C12DB2715D00700ED1A24 /* ButtonSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonSection.swift; sourceTree = ""; }; 66 | E97C12DC2715D00700ED1A24 /* ButtonCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonCell.swift; sourceTree = ""; }; 67 | E97C12DE2715D00700ED1A24 /* TextForm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextForm.swift; sourceTree = ""; }; 68 | E97C12DF2715D00700ED1A24 /* TextFormCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TextFormCell.xib; sourceTree = ""; }; 69 | E97C12E02715D00700ED1A24 /* TextFormCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFormCell.swift; sourceTree = ""; }; 70 | E97C12E12715D00700ED1A24 /* TextFormViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFormViewModel.swift; sourceTree = ""; }; 71 | E97C12E22715D00800ED1A24 /* TextFormSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFormSection.swift; sourceTree = ""; }; 72 | E97C12EB2715D01000ED1A24 /* ListSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListSection.swift; sourceTree = ""; }; 73 | E97C12ED2715D30300ED1A24 /* ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewController.swift; sourceTree = ""; }; 74 | /* End PBXFileReference section */ 75 | 76 | /* Begin PBXFrameworksBuildPhase section */ 77 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | C49C371C4E1E83B492D31200 /* Pods_CompositionalLayoutViewController_Example.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | E37FBF8AD360BE9B297C172E /* Pods_CompositionalLayoutViewController_Tests.framework in Frameworks */, 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 607FACC71AFB9204008FA782 = { 97 | isa = PBXGroup; 98 | children = ( 99 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 100 | 607FACD21AFB9204008FA782 /* Example for CompositionalLayoutViewController */, 101 | 607FACE81AFB9204008FA782 /* Tests */, 102 | 607FACD11AFB9204008FA782 /* Products */, 103 | CDC4832D3AE2B648FA68807D /* Pods */, 104 | E3F2329A3131B1289FAC179F /* Frameworks */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 607FACD11AFB9204008FA782 /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 607FACD01AFB9204008FA782 /* CompositionalLayoutViewController_Example.app */, 112 | 607FACE51AFB9204008FA782 /* CompositionalLayoutViewController_Tests.xctest */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | 607FACD21AFB9204008FA782 /* Example for CompositionalLayoutViewController */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | E9061086265766FA008ACD4F /* Class */, 121 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 122 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 123 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 124 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 125 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 126 | 607FACD31AFB9204008FA782 /* Supporting Files */, 127 | ); 128 | name = "Example for CompositionalLayoutViewController"; 129 | path = CompositionalLayoutViewController; 130 | sourceTree = ""; 131 | }; 132 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 607FACD41AFB9204008FA782 /* Info.plist */, 136 | ); 137 | name = "Supporting Files"; 138 | sourceTree = ""; 139 | }; 140 | 607FACE81AFB9204008FA782 /* Tests */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 607FACEB1AFB9204008FA782 /* Tests.swift */, 144 | 607FACE91AFB9204008FA782 /* Supporting Files */, 145 | ); 146 | path = Tests; 147 | sourceTree = ""; 148 | }; 149 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 607FACEA1AFB9204008FA782 /* Info.plist */, 153 | ); 154 | name = "Supporting Files"; 155 | sourceTree = ""; 156 | }; 157 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 0E03A8EABD152021E463CE1F /* CompositionalLayoutViewController.podspec */, 161 | 8265BA2D021232193464EA8B /* README.md */, 162 | 26773E24238F6C4E2DEA3BE9 /* LICENSE */, 163 | ); 164 | name = "Podspec Metadata"; 165 | sourceTree = ""; 166 | }; 167 | CDC4832D3AE2B648FA68807D /* Pods */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | AAC622A8D1D3D19964B6223E /* Pods-CompositionalLayoutViewController_Example.debug.xcconfig */, 171 | 3528888696ADB633A88363B2 /* Pods-CompositionalLayoutViewController_Example.release.xcconfig */, 172 | B63D874721A7B7FE36BD705E /* Pods-CompositionalLayoutViewController_Tests.debug.xcconfig */, 173 | A7A122714DCD249B94636896 /* Pods-CompositionalLayoutViewController_Tests.release.xcconfig */, 174 | ); 175 | path = Pods; 176 | sourceTree = ""; 177 | }; 178 | E3F2329A3131B1289FAC179F /* Frameworks */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | D759A42478EA941E93F55198 /* Pods_CompositionalLayoutViewController_Example.framework */, 182 | E5CB8C44FA6C15F272D2A500 /* Pods_CompositionalLayoutViewController_Tests.framework */, 183 | ); 184 | name = Frameworks; 185 | sourceTree = ""; 186 | }; 187 | E9061086265766FA008ACD4F /* Class */ = { 188 | isa = PBXGroup; 189 | children = ( 190 | E97C12D82715D00700ED1A24 /* Section */, 191 | E906109226576A63008ACD4F /* SampleViewController.swift */, 192 | E97C12ED2715D30300ED1A24 /* ListViewController.swift */, 193 | E906109D26576E15008ACD4F /* String+Validation.swift */, 194 | ); 195 | path = Class; 196 | sourceTree = ""; 197 | }; 198 | E97C12D82715D00700ED1A24 /* Section */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | E97C12D92715D00700ED1A24 /* ButtonSection */, 202 | E97C12DD2715D00700ED1A24 /* TextFormSection */, 203 | E97C12EB2715D01000ED1A24 /* ListSection.swift */, 204 | ); 205 | path = Section; 206 | sourceTree = ""; 207 | }; 208 | E97C12D92715D00700ED1A24 /* ButtonSection */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | E97C12DA2715D00700ED1A24 /* ButtonCell.xib */, 212 | E97C12DB2715D00700ED1A24 /* ButtonSection.swift */, 213 | E97C12DC2715D00700ED1A24 /* ButtonCell.swift */, 214 | ); 215 | path = ButtonSection; 216 | sourceTree = ""; 217 | }; 218 | E97C12DD2715D00700ED1A24 /* TextFormSection */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | E97C12DE2715D00700ED1A24 /* TextForm.swift */, 222 | E97C12DF2715D00700ED1A24 /* TextFormCell.xib */, 223 | E97C12E02715D00700ED1A24 /* TextFormCell.swift */, 224 | E97C12E12715D00700ED1A24 /* TextFormViewModel.swift */, 225 | E97C12E22715D00800ED1A24 /* TextFormSection.swift */, 226 | ); 227 | path = TextFormSection; 228 | sourceTree = ""; 229 | }; 230 | /* End PBXGroup section */ 231 | 232 | /* Begin PBXNativeTarget section */ 233 | 607FACCF1AFB9204008FA782 /* CompositionalLayoutViewController_Example */ = { 234 | isa = PBXNativeTarget; 235 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "CompositionalLayoutViewController_Example" */; 236 | buildPhases = ( 237 | 4F7E97C5C83DB1047DE858B1 /* [CP] Check Pods Manifest.lock */, 238 | 607FACCC1AFB9204008FA782 /* Sources */, 239 | 607FACCD1AFB9204008FA782 /* Frameworks */, 240 | 607FACCE1AFB9204008FA782 /* Resources */, 241 | E299877AAB2D185E9418691E /* [CP] Embed Pods Frameworks */, 242 | ); 243 | buildRules = ( 244 | ); 245 | dependencies = ( 246 | ); 247 | name = CompositionalLayoutViewController_Example; 248 | productName = CompositionalLayoutViewController; 249 | productReference = 607FACD01AFB9204008FA782 /* CompositionalLayoutViewController_Example.app */; 250 | productType = "com.apple.product-type.application"; 251 | }; 252 | 607FACE41AFB9204008FA782 /* CompositionalLayoutViewController_Tests */ = { 253 | isa = PBXNativeTarget; 254 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "CompositionalLayoutViewController_Tests" */; 255 | buildPhases = ( 256 | 913284CE0F748D660FDD6426 /* [CP] Check Pods Manifest.lock */, 257 | 607FACE11AFB9204008FA782 /* Sources */, 258 | 607FACE21AFB9204008FA782 /* Frameworks */, 259 | 607FACE31AFB9204008FA782 /* Resources */, 260 | ); 261 | buildRules = ( 262 | ); 263 | dependencies = ( 264 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 265 | ); 266 | name = CompositionalLayoutViewController_Tests; 267 | productName = Tests; 268 | productReference = 607FACE51AFB9204008FA782 /* CompositionalLayoutViewController_Tests.xctest */; 269 | productType = "com.apple.product-type.bundle.unit-test"; 270 | }; 271 | /* End PBXNativeTarget section */ 272 | 273 | /* Begin PBXProject section */ 274 | 607FACC81AFB9204008FA782 /* Project object */ = { 275 | isa = PBXProject; 276 | attributes = { 277 | LastSwiftUpdateCheck = 0830; 278 | LastUpgradeCheck = 0830; 279 | ORGANIZATIONNAME = CocoaPods; 280 | TargetAttributes = { 281 | 607FACCF1AFB9204008FA782 = { 282 | CreatedOnToolsVersion = 6.3.1; 283 | LastSwiftMigration = 1250; 284 | }; 285 | 607FACE41AFB9204008FA782 = { 286 | CreatedOnToolsVersion = 6.3.1; 287 | LastSwiftMigration = 1250; 288 | TestTargetID = 607FACCF1AFB9204008FA782; 289 | }; 290 | }; 291 | }; 292 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "CompositionalLayoutViewController" */; 293 | compatibilityVersion = "Xcode 3.2"; 294 | developmentRegion = English; 295 | hasScannedForEncodings = 0; 296 | knownRegions = ( 297 | English, 298 | en, 299 | Base, 300 | ); 301 | mainGroup = 607FACC71AFB9204008FA782; 302 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 303 | projectDirPath = ""; 304 | projectRoot = ""; 305 | targets = ( 306 | 607FACCF1AFB9204008FA782 /* CompositionalLayoutViewController_Example */, 307 | 607FACE41AFB9204008FA782 /* CompositionalLayoutViewController_Tests */, 308 | ); 309 | }; 310 | /* End PBXProject section */ 311 | 312 | /* Begin PBXResourcesBuildPhase section */ 313 | 607FACCE1AFB9204008FA782 /* Resources */ = { 314 | isa = PBXResourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 318 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 319 | E97C12E32715D00800ED1A24 /* ButtonCell.xib in Resources */, 320 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 321 | E97C12E72715D00800ED1A24 /* TextFormCell.xib in Resources */, 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | 607FACE31AFB9204008FA782 /* Resources */ = { 326 | isa = PBXResourcesBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | }; 332 | /* End PBXResourcesBuildPhase section */ 333 | 334 | /* Begin PBXShellScriptBuildPhase section */ 335 | 4F7E97C5C83DB1047DE858B1 /* [CP] Check Pods Manifest.lock */ = { 336 | isa = PBXShellScriptBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | ); 340 | inputFileListPaths = ( 341 | ); 342 | inputPaths = ( 343 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 344 | "${PODS_ROOT}/Manifest.lock", 345 | ); 346 | name = "[CP] Check Pods Manifest.lock"; 347 | outputFileListPaths = ( 348 | ); 349 | outputPaths = ( 350 | "$(DERIVED_FILE_DIR)/Pods-CompositionalLayoutViewController_Example-checkManifestLockResult.txt", 351 | ); 352 | runOnlyForDeploymentPostprocessing = 0; 353 | shellPath = /bin/sh; 354 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 355 | showEnvVarsInLog = 0; 356 | }; 357 | 913284CE0F748D660FDD6426 /* [CP] Check Pods Manifest.lock */ = { 358 | isa = PBXShellScriptBuildPhase; 359 | buildActionMask = 2147483647; 360 | files = ( 361 | ); 362 | inputFileListPaths = ( 363 | ); 364 | inputPaths = ( 365 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 366 | "${PODS_ROOT}/Manifest.lock", 367 | ); 368 | name = "[CP] Check Pods Manifest.lock"; 369 | outputFileListPaths = ( 370 | ); 371 | outputPaths = ( 372 | "$(DERIVED_FILE_DIR)/Pods-CompositionalLayoutViewController_Tests-checkManifestLockResult.txt", 373 | ); 374 | runOnlyForDeploymentPostprocessing = 0; 375 | shellPath = /bin/sh; 376 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 377 | showEnvVarsInLog = 0; 378 | }; 379 | E299877AAB2D185E9418691E /* [CP] Embed Pods Frameworks */ = { 380 | isa = PBXShellScriptBuildPhase; 381 | buildActionMask = 2147483647; 382 | files = ( 383 | ); 384 | inputPaths = ( 385 | "${PODS_ROOT}/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example-frameworks.sh", 386 | "${BUILT_PRODUCTS_DIR}/CompositionalLayoutViewController/CompositionalLayoutViewController.framework", 387 | "${BUILT_PRODUCTS_DIR}/Reusable/Reusable.framework", 388 | ); 389 | name = "[CP] Embed Pods Frameworks"; 390 | outputPaths = ( 391 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CompositionalLayoutViewController.framework", 392 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reusable.framework", 393 | ); 394 | runOnlyForDeploymentPostprocessing = 0; 395 | shellPath = /bin/sh; 396 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example-frameworks.sh\"\n"; 397 | showEnvVarsInLog = 0; 398 | }; 399 | /* End PBXShellScriptBuildPhase section */ 400 | 401 | /* Begin PBXSourcesBuildPhase section */ 402 | 607FACCC1AFB9204008FA782 /* Sources */ = { 403 | isa = PBXSourcesBuildPhase; 404 | buildActionMask = 2147483647; 405 | files = ( 406 | E97C12E42715D00800ED1A24 /* ButtonSection.swift in Sources */, 407 | E97C12EC2715D01000ED1A24 /* ListSection.swift in Sources */, 408 | E97C12EE2715D30300ED1A24 /* ListViewController.swift in Sources */, 409 | E906109E26576E15008ACD4F /* String+Validation.swift in Sources */, 410 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 411 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 412 | E97C12E82715D00800ED1A24 /* TextFormCell.swift in Sources */, 413 | E906109326576A63008ACD4F /* SampleViewController.swift in Sources */, 414 | E97C12E62715D00800ED1A24 /* TextForm.swift in Sources */, 415 | E97C12E92715D00800ED1A24 /* TextFormViewModel.swift in Sources */, 416 | E97C12EA2715D00800ED1A24 /* TextFormSection.swift in Sources */, 417 | E97C12E52715D00800ED1A24 /* ButtonCell.swift in Sources */, 418 | ); 419 | runOnlyForDeploymentPostprocessing = 0; 420 | }; 421 | 607FACE11AFB9204008FA782 /* Sources */ = { 422 | isa = PBXSourcesBuildPhase; 423 | buildActionMask = 2147483647; 424 | files = ( 425 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, 426 | ); 427 | runOnlyForDeploymentPostprocessing = 0; 428 | }; 429 | /* End PBXSourcesBuildPhase section */ 430 | 431 | /* Begin PBXTargetDependency section */ 432 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 433 | isa = PBXTargetDependency; 434 | target = 607FACCF1AFB9204008FA782 /* CompositionalLayoutViewController_Example */; 435 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 436 | }; 437 | /* End PBXTargetDependency section */ 438 | 439 | /* Begin PBXVariantGroup section */ 440 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 441 | isa = PBXVariantGroup; 442 | children = ( 443 | 607FACDA1AFB9204008FA782 /* Base */, 444 | ); 445 | name = Main.storyboard; 446 | sourceTree = ""; 447 | }; 448 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 449 | isa = PBXVariantGroup; 450 | children = ( 451 | 607FACDF1AFB9204008FA782 /* Base */, 452 | ); 453 | name = LaunchScreen.xib; 454 | sourceTree = ""; 455 | }; 456 | /* End PBXVariantGroup section */ 457 | 458 | /* Begin XCBuildConfiguration section */ 459 | 607FACED1AFB9204008FA782 /* Debug */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ALWAYS_SEARCH_USER_PATHS = NO; 463 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 464 | CLANG_CXX_LIBRARY = "libc++"; 465 | CLANG_ENABLE_MODULES = YES; 466 | CLANG_ENABLE_OBJC_ARC = YES; 467 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 468 | CLANG_WARN_BOOL_CONVERSION = YES; 469 | CLANG_WARN_COMMA = YES; 470 | CLANG_WARN_CONSTANT_CONVERSION = YES; 471 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 472 | CLANG_WARN_EMPTY_BODY = YES; 473 | CLANG_WARN_ENUM_CONVERSION = YES; 474 | CLANG_WARN_INFINITE_RECURSION = YES; 475 | CLANG_WARN_INT_CONVERSION = YES; 476 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 477 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 478 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 479 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 480 | CLANG_WARN_STRICT_PROTOTYPES = YES; 481 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 482 | CLANG_WARN_UNREACHABLE_CODE = YES; 483 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 484 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 485 | COPY_PHASE_STRIP = NO; 486 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 487 | ENABLE_STRICT_OBJC_MSGSEND = YES; 488 | ENABLE_TESTABILITY = YES; 489 | GCC_C_LANGUAGE_STANDARD = gnu99; 490 | GCC_DYNAMIC_NO_PIC = NO; 491 | GCC_NO_COMMON_BLOCKS = YES; 492 | GCC_OPTIMIZATION_LEVEL = 0; 493 | GCC_PREPROCESSOR_DEFINITIONS = ( 494 | "DEBUG=1", 495 | "$(inherited)", 496 | ); 497 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 498 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 499 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 500 | GCC_WARN_UNDECLARED_SELECTOR = YES; 501 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 502 | GCC_WARN_UNUSED_FUNCTION = YES; 503 | GCC_WARN_UNUSED_VARIABLE = YES; 504 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 505 | MTL_ENABLE_DEBUG_INFO = YES; 506 | ONLY_ACTIVE_ARCH = YES; 507 | SDKROOT = iphoneos; 508 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 509 | }; 510 | name = Debug; 511 | }; 512 | 607FACEE1AFB9204008FA782 /* Release */ = { 513 | isa = XCBuildConfiguration; 514 | buildSettings = { 515 | ALWAYS_SEARCH_USER_PATHS = NO; 516 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 517 | CLANG_CXX_LIBRARY = "libc++"; 518 | CLANG_ENABLE_MODULES = YES; 519 | CLANG_ENABLE_OBJC_ARC = YES; 520 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 521 | CLANG_WARN_BOOL_CONVERSION = YES; 522 | CLANG_WARN_COMMA = YES; 523 | CLANG_WARN_CONSTANT_CONVERSION = YES; 524 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 525 | CLANG_WARN_EMPTY_BODY = YES; 526 | CLANG_WARN_ENUM_CONVERSION = YES; 527 | CLANG_WARN_INFINITE_RECURSION = YES; 528 | CLANG_WARN_INT_CONVERSION = YES; 529 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 530 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 531 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 532 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 533 | CLANG_WARN_STRICT_PROTOTYPES = YES; 534 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 535 | CLANG_WARN_UNREACHABLE_CODE = YES; 536 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 537 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 538 | COPY_PHASE_STRIP = NO; 539 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 540 | ENABLE_NS_ASSERTIONS = NO; 541 | ENABLE_STRICT_OBJC_MSGSEND = YES; 542 | GCC_C_LANGUAGE_STANDARD = gnu99; 543 | GCC_NO_COMMON_BLOCKS = YES; 544 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 545 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 546 | GCC_WARN_UNDECLARED_SELECTOR = YES; 547 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 548 | GCC_WARN_UNUSED_FUNCTION = YES; 549 | GCC_WARN_UNUSED_VARIABLE = YES; 550 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 551 | MTL_ENABLE_DEBUG_INFO = NO; 552 | SDKROOT = iphoneos; 553 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 554 | VALIDATE_PRODUCT = YES; 555 | }; 556 | name = Release; 557 | }; 558 | 607FACF01AFB9204008FA782 /* Debug */ = { 559 | isa = XCBuildConfiguration; 560 | baseConfigurationReference = AAC622A8D1D3D19964B6223E /* Pods-CompositionalLayoutViewController_Example.debug.xcconfig */; 561 | buildSettings = { 562 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 563 | INFOPLIST_FILE = CompositionalLayoutViewController/Info.plist; 564 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 565 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 566 | MODULE_NAME = ExampleApp; 567 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 568 | PRODUCT_NAME = "$(TARGET_NAME)"; 569 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 570 | SWIFT_VERSION = 5.0; 571 | }; 572 | name = Debug; 573 | }; 574 | 607FACF11AFB9204008FA782 /* Release */ = { 575 | isa = XCBuildConfiguration; 576 | baseConfigurationReference = 3528888696ADB633A88363B2 /* Pods-CompositionalLayoutViewController_Example.release.xcconfig */; 577 | buildSettings = { 578 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 579 | INFOPLIST_FILE = CompositionalLayoutViewController/Info.plist; 580 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 581 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 582 | MODULE_NAME = ExampleApp; 583 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 584 | PRODUCT_NAME = "$(TARGET_NAME)"; 585 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 586 | SWIFT_VERSION = 5.0; 587 | }; 588 | name = Release; 589 | }; 590 | 607FACF31AFB9204008FA782 /* Debug */ = { 591 | isa = XCBuildConfiguration; 592 | baseConfigurationReference = B63D874721A7B7FE36BD705E /* Pods-CompositionalLayoutViewController_Tests.debug.xcconfig */; 593 | buildSettings = { 594 | FRAMEWORK_SEARCH_PATHS = ( 595 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 596 | "$(inherited)", 597 | ); 598 | GCC_PREPROCESSOR_DEFINITIONS = ( 599 | "DEBUG=1", 600 | "$(inherited)", 601 | ); 602 | INFOPLIST_FILE = Tests/Info.plist; 603 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 604 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 605 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 606 | PRODUCT_NAME = "$(TARGET_NAME)"; 607 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 608 | SWIFT_VERSION = 5.0; 609 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompositionalLayoutViewController_Example.app/CompositionalLayoutViewController_Example"; 610 | }; 611 | name = Debug; 612 | }; 613 | 607FACF41AFB9204008FA782 /* Release */ = { 614 | isa = XCBuildConfiguration; 615 | baseConfigurationReference = A7A122714DCD249B94636896 /* Pods-CompositionalLayoutViewController_Tests.release.xcconfig */; 616 | buildSettings = { 617 | FRAMEWORK_SEARCH_PATHS = ( 618 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 619 | "$(inherited)", 620 | ); 621 | INFOPLIST_FILE = Tests/Info.plist; 622 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 623 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 624 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 625 | PRODUCT_NAME = "$(TARGET_NAME)"; 626 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 627 | SWIFT_VERSION = 5.0; 628 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompositionalLayoutViewController_Example.app/CompositionalLayoutViewController_Example"; 629 | }; 630 | name = Release; 631 | }; 632 | /* End XCBuildConfiguration section */ 633 | 634 | /* Begin XCConfigurationList section */ 635 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "CompositionalLayoutViewController" */ = { 636 | isa = XCConfigurationList; 637 | buildConfigurations = ( 638 | 607FACED1AFB9204008FA782 /* Debug */, 639 | 607FACEE1AFB9204008FA782 /* Release */, 640 | ); 641 | defaultConfigurationIsVisible = 0; 642 | defaultConfigurationName = Release; 643 | }; 644 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "CompositionalLayoutViewController_Example" */ = { 645 | isa = XCConfigurationList; 646 | buildConfigurations = ( 647 | 607FACF01AFB9204008FA782 /* Debug */, 648 | 607FACF11AFB9204008FA782 /* Release */, 649 | ); 650 | defaultConfigurationIsVisible = 0; 651 | defaultConfigurationName = Release; 652 | }; 653 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "CompositionalLayoutViewController_Tests" */ = { 654 | isa = XCConfigurationList; 655 | buildConfigurations = ( 656 | 607FACF31AFB9204008FA782 /* Debug */, 657 | 607FACF41AFB9204008FA782 /* Release */, 658 | ); 659 | defaultConfigurationIsVisible = 0; 660 | defaultConfigurationName = Release; 661 | }; 662 | /* End XCConfigurationList section */ 663 | }; 664 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 665 | } 666 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController.xcodeproj/xcshareddata/xcschemes/CompositionalLayoutViewController-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 101 | 107 | 108 | 109 | 110 | 112 | 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController.xcodeproj/xcshareddata/xcschemes/CompositionalLayoutViewController_Tests.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 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira on 05/21/2021. 6 | // Copyright (c) 2021 Akira. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(_ application: UIApplication) { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | func applicationDidEnterBackground(_ application: UIApplication) { 26 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(_ application: UIApplication) { 31 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 32 | } 33 | 34 | func applicationDidBecomeActive(_ application: UIApplication) { 35 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 36 | } 37 | 38 | func applicationWillTerminate(_ application: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/ListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewController.swift 3 | // CompositionalLayoutViewController_Example 4 | // 5 | // Created by Akira Matsuda on 2021/10/12. 6 | // Copyright © 2021 CocoaPods. All rights reserved. 7 | // 8 | 9 | import CompositionalLayoutViewController 10 | 11 | struct ListItem: Hashable { 12 | let title: String? 13 | private let identifier = UUID() 14 | } 15 | 16 | class ListViewController: CompositionalLayoutViewController, SectionProvider { 17 | var sections = [CollectionViewSection]() 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | collectionView.contentInset.top = 32 22 | provider = self 23 | 24 | sections = [ 25 | ListSection( 26 | items: [ 27 | ListItem(title: "a"), 28 | ListItem(title: "b"), 29 | ListItem(title: "c") 30 | ] 31 | ) { cell, item in 32 | var content = cell.defaultContentConfiguration() 33 | content.text = item.title 34 | return content 35 | }, 36 | ButtonSection( 37 | buttonTitle: "Button", 38 | action: .handler { 39 | print("button") 40 | } 41 | ) 42 | ] 43 | reloadSections() 44 | } 45 | 46 | override func layoutConfiguration() -> UICollectionViewCompositionalLayoutConfiguration { 47 | let config = UICollectionViewCompositionalLayoutConfiguration() 48 | config.interSectionSpacing = 17 49 | return config 50 | } 51 | 52 | override func didSelectItem(at indexPath: IndexPath) { 53 | print(sections[indexPath.section].snapshotItems[indexPath.row]) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/SampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleViewController.swift 3 | // CompositionalLayoutViewController_Example 4 | // 5 | // Created by Akira Matsuda on 2021/05/21. 6 | // 7 | 8 | import Combine 9 | import CompositionalLayoutViewController 10 | 11 | struct LoginInfo { 12 | let email: String 13 | let password: String 14 | } 15 | 16 | class SampleViewController: CompositionalLayoutViewController, SectionProvider { 17 | private var cancellable = Set() 18 | private let emailFormViewModel = TextFormViewModel( 19 | initialText: nil, 20 | textForm: .init( 21 | placeholder: "Email", 22 | keyboardType: .emailAddress, 23 | autocorrectionType: .no, 24 | autocapitalizationType: .none, 25 | contentType: .username, 26 | validationHandler: { text in 27 | guard let text = text else { 28 | return false 29 | } 30 | return text.isValidEmailAddress() 31 | }, 32 | validationAppearance: .init( 33 | textColor: .red 34 | ) 35 | ) 36 | ) 37 | 38 | private let passwordFormViewModel = TextFormViewModel( 39 | initialText: nil, 40 | textForm: .init( 41 | placeholder: "Password", 42 | isSecureTextEntry: true, 43 | autocorrectionType: .no, 44 | autocapitalizationType: .none, 45 | contentType: .password 46 | ) 47 | ) 48 | 49 | private var validateEmail: AnyPublisher { 50 | return emailFormViewModel.$text 51 | .map { email in 52 | guard let email = email, email.isValidEmailAddress() else { 53 | return nil 54 | } 55 | return email 56 | } 57 | .eraseToAnyPublisher() 58 | } 59 | 60 | private var validateCredentials: AnyPublisher { 61 | return Publishers.CombineLatest(validateEmail, passwordFormViewModel.$text) 62 | .map { email, password in 63 | guard let email = email, 64 | let password = password, 65 | !email.isEmpty, !password.isEmpty else { 66 | return nil 67 | } 68 | return LoginInfo(email: email, password: password) 69 | } 70 | .eraseToAnyPublisher() 71 | } 72 | 73 | var sections = [CollectionViewSection]() 74 | 75 | override func viewDidLoad() { 76 | super.viewDidLoad() 77 | collectionView.contentInset.top = 32 78 | provider = self 79 | 80 | let loginButtonSection = ButtonSection( 81 | buttonTitle: "Login", 82 | action: .handler { [unowned self] in 83 | print("Login button pressed \(String(describing: emailFormViewModel.text)):\(String(describing: passwordFormViewModel.text))") 84 | } 85 | ) 86 | loginButtonSection.isEnabled = false 87 | validateCredentials.map { 88 | return $0 != nil 89 | } 90 | .receive(on: RunLoop.main) 91 | .assign(to: \.isEnabled, on: loginButtonSection) 92 | .store(in: &cancellable) 93 | 94 | sections = [ 95 | TextFormSection.form([ 96 | emailFormViewModel, 97 | passwordFormViewModel 98 | ]), 99 | loginButtonSection 100 | ] 101 | reloadSections() 102 | } 103 | 104 | override func layoutConfiguration() -> UICollectionViewCompositionalLayoutConfiguration { 105 | let config = UICollectionViewCompositionalLayoutConfiguration() 106 | config.interSectionSpacing = 17 107 | return config 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/ButtonSection/ButtonCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonCell.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/05/16. 6 | // 7 | 8 | import Reusable 9 | import UIKit 10 | 11 | protocol ButtonCellDelegate: AnyObject { 12 | func didButtonPress() 13 | } 14 | 15 | class ButtonCell: UICollectionViewCell, NibReusable { 16 | static let defaultHeight: CGFloat = 55 17 | 18 | @IBOutlet private var button: UIButton! 19 | weak var delegate: ButtonCellDelegate? 20 | var title: String? { 21 | didSet { 22 | button.setTitle(title, for: .normal) 23 | } 24 | } 25 | 26 | var buttonBackgroundColor: UIColor? { 27 | get { 28 | return button.backgroundColor 29 | } 30 | set { 31 | button.backgroundColor = newValue 32 | } 33 | } 34 | 35 | var buttonTitleFont: UIFont? { 36 | get { 37 | return button.titleLabel?.font 38 | } 39 | set { 40 | button.titleLabel?.font = newValue 41 | } 42 | } 43 | 44 | var isEnabled = true { 45 | didSet { 46 | button.isEnabled = isEnabled 47 | button.alpha = isEnabled ? 1 : 0.4 48 | } 49 | } 50 | 51 | override func awakeFromNib() { 52 | super.awakeFromNib() 53 | // Initialization code 54 | button.layer.cornerRadius = 7 55 | } 56 | 57 | func setTitleColor(_ color: UIColor?, for state: UIButton.State) { 58 | button.setTitleColor(color, for: state) 59 | } 60 | 61 | @IBAction private func buttonPressed(_ sender: Any) { 62 | delegate?.didButtonPress() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/ButtonSection/ButtonCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/ButtonSection/ButtonSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyAccountLogOutButtonSection.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/05/16. 6 | // 7 | 8 | import Combine 9 | import CompositionalLayoutViewController 10 | import UIKit 11 | 12 | class ButtonSection: CollectionViewSection { 13 | struct Appearance { 14 | let backgroundColor: UIColor? 15 | let titleColor: UIColor? 16 | let font: UIFont? 17 | } 18 | 19 | enum Action { 20 | case handler(() -> Void) 21 | } 22 | 23 | private var cancellable = Set() 24 | 25 | var identifier: String { 26 | return buttonTitle 27 | } 28 | 29 | var snapshotItems: [AnyHashable] { 30 | return [buttonTitle] 31 | } 32 | 33 | var buttonTitle: String 34 | var appearance: Appearance? 35 | var action: Action 36 | @Published var isEnabled = true 37 | 38 | init(buttonTitle: String, action: Action, appearance: Appearance? = nil) { 39 | self.buttonTitle = buttonTitle 40 | self.action = action 41 | self.appearance = appearance 42 | } 43 | 44 | func registerCell(collectionView: UICollectionView) { 45 | collectionView.register(cellType: ButtonCell.self) 46 | } 47 | 48 | func registerSupplementaryView(collectionView: UICollectionView) {} 49 | 50 | func layoutSection(environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { 51 | let itemSize = NSCollectionLayoutSize( 52 | widthDimension: .fractionalWidth(1), 53 | heightDimension: .fractionalHeight(1) 54 | ) 55 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 56 | let groupSize = NSCollectionLayoutSize( 57 | widthDimension: .fractionalWidth(1), 58 | heightDimension: .absolute(ButtonCell.defaultHeight) 59 | ) 60 | let group = NSCollectionLayoutGroup.horizontal( 61 | layoutSize: groupSize, 62 | subitems: [item] 63 | ) 64 | let section = NSCollectionLayoutSection(group: group) 65 | section.contentInsets = .init( 66 | top: 0, 67 | leading: 23, 68 | bottom: 0, 69 | trailing: 23 70 | ) 71 | return section 72 | } 73 | 74 | func cell(_ collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell? { 75 | cancellable.removeAll() 76 | let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: ButtonCell.self) 77 | cell.title = buttonTitle 78 | if let appearance = appearance { 79 | cell.setTitleColor(appearance.titleColor, for: .normal) 80 | cell.buttonBackgroundColor = appearance.backgroundColor 81 | cell.buttonTitleFont = appearance.font 82 | } 83 | cell.delegate = self 84 | $isEnabled.sink { enabled in 85 | cell.isEnabled = enabled 86 | }.store(in: &cancellable) 87 | return cell 88 | } 89 | 90 | func supplementaryView(_ collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? { 91 | return nil 92 | } 93 | 94 | func configureSupplementaryView(_ view: UICollectionReusableView, indexPath: IndexPath) {} 95 | } 96 | 97 | extension ButtonSection: ButtonCellDelegate { 98 | func didButtonPress() { 99 | switch action { 100 | case let .handler(handler): 101 | handler() 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/ListSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableSection.swift 3 | // CompositionalLayoutViewController_Example 4 | // 5 | // Created by Akira Matsuda on 2021/10/12. 6 | // Copyright © 2021 CocoaPods. All rights reserved. 7 | // 8 | 9 | import CompositionalLayoutViewController 10 | import UIKit 11 | 12 | class ListSection: CollectionViewSection { 13 | var identifier: String { 14 | return "list-section" 15 | } 16 | 17 | var snapshotItems: [AnyHashable] { 18 | return items 19 | } 20 | 21 | private let items: [ViewModel] 22 | private let appearance: UICollectionLayoutListConfiguration.Appearance 23 | private let cellCongicuration: (UICollectionViewListCell, ViewModel) -> UIListContentConfiguration 24 | private var cellRegistration: UICollectionView.CellRegistration! 25 | 26 | init(items: [ViewModel], cellCongicuration: @escaping ((UICollectionViewListCell, ViewModel) -> UIListContentConfiguration), appearance: UICollectionLayoutListConfiguration.Appearance = .plain) { 27 | self.items = items 28 | self.appearance = appearance 29 | self.cellCongicuration = cellCongicuration 30 | prepare() 31 | } 32 | 33 | private func prepare() { 34 | cellRegistration = UICollectionView.CellRegistration< 35 | UICollectionViewListCell, 36 | ViewModel 37 | > { [weak self] cell, _, item in 38 | guard let weakSelf = self else { return } 39 | cell.contentConfiguration = weakSelf.cellCongicuration(cell, item) 40 | } 41 | } 42 | 43 | func registerCell(collectionView: UICollectionView) {} 44 | 45 | func registerSupplementaryView(collectionView: UICollectionView) {} 46 | 47 | func layoutSection(environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { 48 | return NSCollectionLayoutSection.list( 49 | using: UICollectionLayoutListConfiguration(appearance: appearance), 50 | layoutEnvironment: environment 51 | ) 52 | } 53 | 54 | func cell(_ collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell? { 55 | return collectionView.dequeueConfiguredReusableCell( 56 | using: cellRegistration, 57 | for: indexPath, 58 | item: items[indexPath.row] 59 | ) 60 | } 61 | 62 | func supplementaryView(_ collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? { 63 | return nil 64 | } 65 | 66 | func configureSupplementaryView(_ view: UICollectionReusableView, indexPath: IndexPath) {} 67 | } 68 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/TextFormSection/TextForm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextForm.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/05/19. 6 | // 7 | 8 | import UIKit 9 | 10 | struct TextForm { 11 | struct ValidationAppearance { 12 | let textColor: UIColor 13 | } 14 | 15 | var placeholder: String? 16 | var isSecureTextEntry = false 17 | var keyboardType: UIKeyboardType = .default 18 | var spellCheckingType: UITextSpellCheckingType = .default 19 | var autocorrectionType: UITextAutocorrectionType = .default 20 | var autocapitalizationType: UITextAutocapitalizationType = .sentences 21 | var contentType: UITextContentType? 22 | var validationHandler: ((String?) -> Bool)? 23 | var validationAppearance: ValidationAppearance = .init(textColor: .systemRed) 24 | } 25 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/TextFormSection/TextFormCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFormCell.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/05/19. 6 | // 7 | 8 | import Combine 9 | import Reusable 10 | import UIKit 11 | 12 | class TextFormCell: UICollectionViewCell, NibReusable { 13 | static let defaultHeight: CGFloat = 49 14 | 15 | @IBOutlet private var textField: UITextField! 16 | private var cancellable = Set() 17 | private var shouldValidate = false 18 | 19 | var isSecureTextEntry: Bool { 20 | get { 21 | return textField.isSecureTextEntry 22 | } 23 | set { 24 | textField.isSecureTextEntry = newValue 25 | } 26 | } 27 | 28 | @Published var text: String? { 29 | didSet { 30 | textField.text = text 31 | } 32 | } 33 | 34 | var viewModel: TextFormViewModel? { 35 | didSet { 36 | guard let viewModel = viewModel else { 37 | return 38 | } 39 | cancellable.removeAll() 40 | textField.text = viewModel.text 41 | textField.isSecureTextEntry = viewModel.textForm.isSecureTextEntry 42 | textField.placeholder = viewModel.textForm.placeholder 43 | textField.keyboardType = viewModel.textForm.keyboardType 44 | textField.spellCheckingType = viewModel.textForm.spellCheckingType 45 | textField.autocorrectionType = viewModel.textForm.autocorrectionType 46 | textField.autocapitalizationType = viewModel.textForm.autocapitalizationType 47 | if let contentType = viewModel.textForm.contentType { 48 | textField.textContentType = contentType 49 | } 50 | viewModel.shuldFocuseSubject.sink { [weak self] _ in 51 | guard let weakSelf = self else { 52 | return 53 | } 54 | weakSelf.textField.becomeFirstResponder() 55 | }.store(in: &cancellable) 56 | NotificationCenter.default.publisher( 57 | for: UITextField.textDidChangeNotification, 58 | object: textField 59 | ).sink { [weak self] _ in 60 | guard let weakSelf = self, let viewModel = weakSelf.viewModel else { 61 | return 62 | } 63 | if weakSelf.shouldValidate, 64 | let handler = viewModel.textForm.validationHandler, 65 | handler(weakSelf.textField.text) == false { 66 | weakSelf.textField.textColor = viewModel.textForm.validationAppearance.textColor 67 | } 68 | else { 69 | weakSelf.textField.textColor = .darkText 70 | } 71 | viewModel.text = weakSelf.textField.text 72 | }.store(in: &cancellable) 73 | } 74 | } 75 | 76 | override func awakeFromNib() { 77 | super.awakeFromNib() 78 | // Initialization code 79 | textField.delegate = self 80 | layer.cornerRadius = 7 81 | } 82 | 83 | @discardableResult 84 | func focuse() -> Bool { 85 | return textField.becomeFirstResponder() 86 | } 87 | 88 | @discardableResult 89 | func resign() -> Bool { 90 | return textField.resignFirstResponder() 91 | } 92 | } 93 | 94 | extension TextFormCell: UITextFieldDelegate { 95 | func textFieldDidEndEditing(_ textField: UITextField) { 96 | shouldValidate = true 97 | if let viewModel = viewModel, 98 | let handler = viewModel.textForm.validationHandler, 99 | handler(textField.text) == false { 100 | textField.textColor = viewModel.textForm.validationAppearance.textColor 101 | } 102 | else { 103 | textField.textColor = .darkText 104 | } 105 | } 106 | 107 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 108 | shouldValidate = true 109 | if let viewModel = viewModel, 110 | let handler = viewModel.textForm.validationHandler, 111 | handler(textField.text) == false { 112 | textField.textColor = viewModel.textForm.validationAppearance.textColor 113 | } 114 | else { 115 | textField.textColor = .darkText 116 | } 117 | if viewModel?.focuseNext() == false { 118 | resign() 119 | } 120 | return true 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/TextFormSection/TextFormCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/TextFormSection/TextFormSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFormSection.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/05/19. 6 | // 7 | 8 | import Combine 9 | import CompositionalLayoutViewController 10 | import Reusable 11 | import UIKit 12 | 13 | extension Array where Element == TextFormViewModel { 14 | func link() { 15 | var iterator = makeIterator() 16 | var current: TextFormViewModel? = iterator.next() 17 | var previous: TextFormViewModel? 18 | while let next = iterator.next() { 19 | current?.previousForm = previous 20 | current?.nextForm = next 21 | previous = current 22 | current = next 23 | } 24 | } 25 | } 26 | 27 | extension TextFormSection { 28 | static func form(_ forms: [TextFormViewModel]) -> TextFormSection { 29 | return .init( 30 | items: forms 31 | ) 32 | } 33 | } 34 | 35 | class TextFormSection: CollectionViewSection { 36 | var identifier: String { 37 | return "text-form-section" 38 | } 39 | 40 | var snapshotItems: [AnyHashable] { 41 | return items 42 | } 43 | 44 | let items: [TextFormViewModel] 45 | 46 | init(items: [TextFormViewModel]) { 47 | self.items = items 48 | self.items.link() 49 | } 50 | 51 | func registerCell(collectionView: UICollectionView) { 52 | collectionView.register(cellType: TextFormCell.self) 53 | } 54 | 55 | func registerSupplementaryView(collectionView: UICollectionView) {} 56 | 57 | func layoutSection(environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { 58 | let itemSize = NSCollectionLayoutSize( 59 | widthDimension: .fractionalWidth(1), 60 | heightDimension: .fractionalHeight(1) 61 | ) 62 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 63 | let groupSize = NSCollectionLayoutSize( 64 | widthDimension: .fractionalWidth(1), 65 | heightDimension: .absolute(TextFormCell.defaultHeight) 66 | ) 67 | let group = NSCollectionLayoutGroup.horizontal( 68 | layoutSize: groupSize, 69 | subitems: [item] 70 | ) 71 | let section = NSCollectionLayoutSection(group: group) 72 | section.interGroupSpacing = 12 73 | section.contentInsets = .init( 74 | top: 0, 75 | leading: 23, 76 | bottom: 0, 77 | trailing: 23 78 | ) 79 | return section 80 | } 81 | 82 | func cell(_ collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell? { 83 | let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: TextFormCell.self) 84 | cell.viewModel = items[indexPath.row] 85 | return cell 86 | } 87 | 88 | func supplementaryView(_ collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? { 89 | return nil 90 | } 91 | 92 | func configureSupplementaryView(_ view: UICollectionReusableView, indexPath: IndexPath) {} 93 | } 94 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/Section/TextFormSection/TextFormViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFormViewModel.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira Matsuda on 2021/05/19. 6 | // 7 | 8 | import Combine 9 | import CompositionalLayoutViewController 10 | import UIKit 11 | 12 | class TextFormViewModel: Hashable { 13 | static func == (lhs: TextFormViewModel, rhs: TextFormViewModel) -> Bool { 14 | return lhs.hashValue == rhs.hashValue 15 | } 16 | 17 | func hash(into hasher: inout Hasher) { 18 | hasher.combine(text) 19 | } 20 | 21 | @Published var text: String? 22 | let textForm: TextForm 23 | var nextForm: TextFormViewModel? 24 | var previousForm: TextFormViewModel? 25 | private(set) var shuldFocuseSubject = PassthroughSubject() 26 | 27 | init(initialText: String?, textForm: TextForm) { 28 | text = initialText 29 | self.textForm = textForm 30 | } 31 | 32 | @discardableResult 33 | func focuseNext() -> Bool { 34 | nextForm?.shuldFocuseSubject.send(()) 35 | guard let form = nextForm else { 36 | return false 37 | } 38 | form.shuldFocuseSubject.send(()) 39 | return true 40 | } 41 | 42 | @discardableResult 43 | func focusePrevious() -> Bool { 44 | guard let form = previousForm else { 45 | return false 46 | } 47 | form.shuldFocuseSubject.send(()) 48 | return true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Class/String+Validation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Validation.swift 3 | // CompositionalLayoutViewController_Example 4 | // 5 | // Created by Akira Matsuda on 2021/05/21. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | func isValidEmailAddress() -> Bool { 12 | let regularExpression = "[A-Z0-9a-z._+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}" 13 | return NSPredicate(format: "SELF MATCHES %@", regularExpression).evaluate(with: self) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/CompositionalLayoutViewController/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CompositionalLayoutViewController 4 | // 5 | // Created by Akira on 05/21/2021. 6 | // Copyright (c) 2021 Akira. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | // Do any additional setup after loading the view, typically from a nib. 15 | } 16 | 17 | override func didReceiveMemoryWarning() { 18 | super.didReceiveMemoryWarning() 19 | // Dispose of any resources that can be recreated. 20 | } 21 | 22 | @IBAction func presentViewController(_ sender: Any) { 23 | // navigationController?.pushViewController(SampleViewController(), animated: true) 24 | navigationController?.pushViewController(ListViewController(), animated: true) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '13.0' 4 | 5 | target 'CompositionalLayoutViewController_Example' do 6 | pod 'CompositionalLayoutViewController', :path => '../' 7 | 8 | pod 'Reusable' 9 | pod 'SwiftFormat/CLI' 10 | 11 | target 'CompositionalLayoutViewController_Tests' do 12 | inherit! :search_paths 13 | 14 | 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CompositionalLayoutViewController (0.5.0) 3 | - Reusable (4.1.1): 4 | - Reusable/Storyboard (= 4.1.1) 5 | - Reusable/View (= 4.1.1) 6 | - Reusable/Storyboard (4.1.1) 7 | - Reusable/View (4.1.1) 8 | - SwiftFormat/CLI (0.48.2) 9 | 10 | DEPENDENCIES: 11 | - CompositionalLayoutViewController (from `../`) 12 | - Reusable 13 | - SwiftFormat/CLI 14 | 15 | SPEC REPOS: 16 | trunk: 17 | - Reusable 18 | - SwiftFormat 19 | 20 | EXTERNAL SOURCES: 21 | CompositionalLayoutViewController: 22 | :path: "../" 23 | 24 | SPEC CHECKSUMS: 25 | CompositionalLayoutViewController: dd293dab0778c30dd53e14d3d50eeae15fc18d2b 26 | Reusable: 53a9acf5c536f229b31b5865782414b508252ddb 27 | SwiftFormat: 480a8c281371d0ae142b2b877ef542462c01c39f 28 | 29 | PODFILE CHECKSUM: 9cf45443a1e096e986416671596aa81a6489d948 30 | 31 | COCOAPODS: 1.11.2 32 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/CompositionalLayoutViewController.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CompositionalLayoutViewController", 3 | "version": "0.5.0", 4 | "summary": "Lightweight UICollectionViewCompositionalLayout wrapper", 5 | "swift_versions": "5.4", 6 | "description": "Declaretive UICollectionViewCompositionalLayout interface to implement complex collection view layout.", 7 | "homepage": "https://github.com/oneinc-jp/CompositionalLayoutViewController", 8 | "license": { 9 | "type": "MIT", 10 | "file": "LICENSE" 11 | }, 12 | "authors": { 13 | "Akira": "akira.matsuda@me.com" 14 | }, 15 | "source": { 16 | "git": "https://github.com/oneinc-jp/CompositionalLayoutViewController.git", 17 | "tag": "0.5.0" 18 | }, 19 | "platforms": { 20 | "ios": "13.0" 21 | }, 22 | "source_files": "CompositionalLayoutViewController/Classes/**/*", 23 | "swift_version": "5.4" 24 | } 25 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CompositionalLayoutViewController (0.5.0) 3 | - Reusable (4.1.1): 4 | - Reusable/Storyboard (= 4.1.1) 5 | - Reusable/View (= 4.1.1) 6 | - Reusable/Storyboard (4.1.1) 7 | - Reusable/View (4.1.1) 8 | - SwiftFormat/CLI (0.48.2) 9 | 10 | DEPENDENCIES: 11 | - CompositionalLayoutViewController (from `../`) 12 | - Reusable 13 | - SwiftFormat/CLI 14 | 15 | SPEC REPOS: 16 | trunk: 17 | - Reusable 18 | - SwiftFormat 19 | 20 | EXTERNAL SOURCES: 21 | CompositionalLayoutViewController: 22 | :path: "../" 23 | 24 | SPEC CHECKSUMS: 25 | CompositionalLayoutViewController: dd293dab0778c30dd53e14d3d50eeae15fc18d2b 26 | Reusable: 53a9acf5c536f229b31b5865782414b508252ddb 27 | SwiftFormat: 480a8c281371d0ae142b2b877ef542462c01c39f 28 | 29 | PODFILE CHECKSUM: 9cf45443a1e096e986416671596aa81a6489d948 30 | 31 | COCOAPODS: 1.11.2 32 | -------------------------------------------------------------------------------- /Example/Pods/SwiftFormat/CommandLineTool/swiftformat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oneinc-jp/CompositionalLayoutViewController/22a2ed014f5a5b064ea82d639bed63e8a79d477c/Example/Pods/SwiftFormat/CommandLineTool/swiftformat -------------------------------------------------------------------------------- /Example/Pods/SwiftFormat/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Nick Lockwood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Example/Pods/SwiftFormat/README.md: -------------------------------------------------------------------------------- 1 | ![](EditorExtension/Application/Assets.xcassets/AppIcon.appiconset/icon_256x256.png) 2 | 3 | [![PayPal](https://img.shields.io/badge/paypal-donate-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9ZGWNK5FEZFF6&source=url) 4 | [![Travis](https://api.travis-ci.org/nicklockwood/SwiftFormat.svg?branch=master)](https://travis-ci.org/nicklockwood/SwiftFormat) 5 | [![Codecov](https://codecov.io/gh/nicklockwood/SwiftFormat/graphs/badge.svg)](https://codecov.io/gh/nicklockwood/SwiftFormat) 6 | [![Swift 4.2](https://img.shields.io/badge/swift-4.2-red.svg?style=flat)](https://developer.apple.com/swift) 7 | [![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT) 8 | [![Twitter](https://img.shields.io/badge/twitter-@nicklockwood-blue.svg)](http://twitter.com/nicklockwood) 9 | 10 | Table of Contents 11 | ----------------- 12 | 13 | - [What?](#what-is-this) 14 | - [Why?](#why-would-i-want-to-do-that) 15 | - [How?](#how-do-i-install-it) 16 | - [Command-line tool](#command-line-tool) 17 | - [Xcode source editor extension](#xcode-source-editor-extension) 18 | - [Xcode build phase](#xcode-build-phase) 19 | - [Via Applescript](#via-applescript) 20 | - [VSCode plugin](#vscode-plugin) 21 | - [Sublime Text plugin](#sublime-text-plugin) 22 | - [Git pre-commit hook](#git-pre-commit-hook) 23 | - [On CI using Danger](#on-ci-using-danger) 24 | - [Configuration](#configuration) 25 | - [Options](#options) 26 | - [Rules](#rules) 27 | - [Swift version](#swift-version) 28 | - [Config file](#config-file) 29 | - [Globs](#globs) 30 | - [Linting](#linting) 31 | - [Cache](#cache) 32 | - [File headers](#file-headers) 33 | - [FAQ](#faq) 34 | - [Known issues](#known-issues) 35 | - [Tip Jar](#tip-jar) 36 | - [Credits](#credits) 37 | 38 | 39 | What is this? 40 | ---------------- 41 | 42 | SwiftFormat is a code library and command-line tool for reformatting Swift code on macOS or Linux. 43 | 44 | SwiftFormat goes above and beyond what you might expect from a code formatter. In addition to adjusting white space it can insert or remove implicit `self`, remove redundant parentheses, and correct many other deviations from the standard Swift idioms. 45 | 46 | Why would I want to do that? 47 | ----------------------------- 48 | 49 | Many programmers have a preferred style for formatting their code, and others seem entirely blind to the existing formatting conventions of a project (to the enragement of their colleagues). 50 | 51 | When collaborating on a project, it can be helpful to agree on a common coding style, but enforcing that manually is tedious and error-prone, and can lead to arguments if some participants take it more seriously than others. 52 | 53 | Having a tool to automatically enforce a common style eliminates those issues, and lets you focus on the behavior of the code, not its presentation. 54 | 55 | 56 | How do I install it? 57 | --------------------- 58 | 59 | That depends - There are several ways you can use SwiftFormat: 60 | 61 | 1. As a command-line tool that you run manually, or as part of some other toolchain 62 | 2. As a Source Editor Extension that you can invoke via the Editor > SwiftFormat menu within Xcode 63 | 3. As a build phase in your Xcode project, so that it runs every time you press Cmd-R or Cmd-B, or 64 | 4. As a Git pre-commit hook, so that it runs on any files you've changed before you check them in 65 | 66 | 67 | Command-line tool 68 | ------------------- 69 | 70 | **NOTE:** if you are using any of the following methods to install SwiftFormat on macOS 10.14.3 or earlier and are experiencing a crash on launch, you may need to install the [Swift 5 Runtime Support for Command Line Tools](https://support.apple.com/kb/DL1998). See [known issues](#known-issues) for details. 71 | 72 | **Installation:** 73 | 74 | You can install the `swiftformat` command-line tool on macOS using [Homebrew](http://brew.sh/). Assuming you already have Homebrew installed, just type: 75 | 76 | ```bash 77 | $ brew install swiftformat 78 | ``` 79 | 80 | To update to the latest version once installed: 81 | 82 | ```bash 83 | $ brew upgrade swiftformat 84 | ``` 85 | 86 | Alternatively, you can install the tool on macOS or Linux by using [Mint](https://github.com/yonaskolb/Mint) as follows: 87 | 88 | ```bash 89 | $ mint install nicklockwood/SwiftFormat 90 | ``` 91 | 92 | And then run it using: 93 | 94 | ```bash 95 | $ mint run swiftformat 96 | ``` 97 | 98 | Or if you prefer, you can check out and build SwiftFormat manually on macOS or Linux as follows: 99 | 100 | ```bash 101 | $ git clone https://github.com/nicklockwood/SwiftFormat 102 | $ cd SwiftFormat 103 | $ swift build -c release 104 | ``` 105 | 106 | If you are installing SwiftFormat into your project directory, you can use [CocoaPods](https://cocoapods.org/) on macOS to automatically install the swiftformat binary along with your other pods - see the Xcode build phase instructions below for details. 107 | 108 | If you would prefer not to use a package manager, you can build the command-line app manually: 109 | 110 | 1. open `SwiftFormat.xcodeproj` and build the `SwiftFormat (Application)` scheme. 111 | 112 | 2. Drag the `swiftformat` binary into `/usr/local/bin/` (this is a hidden folder, but you can use the Finder's `Go > Go to Folder...` menu to open it). 113 | 114 | 3. Open `~/.bash_profile` in your favorite text editor (this is a hidden file, but you can type `open ~/.bash_profile` in the terminal to open it). 115 | 116 | 4. Add the following line to the file: `alias swiftformat="/usr/local/bin/swiftformat --indent 4"` (you can omit the `--indent 4`, or replace it with something else. Run `swiftformat --help` to see the available options). 117 | 118 | 5. Save the `.bash_profile` file and run the command `source ~/.bash_profile` for the changes to take effect. 119 | 120 | **Usage:** 121 | 122 | If you followed the installation instructions above, you can now just type 123 | 124 | ```bash 125 | $ swiftformat . 126 | ``` 127 | 128 | (that's a space and then a period after the command) in the terminal to format any Swift files in the current directory. In place of the `.`, you can instead type an absolute or relative path to the file or directory that you want to format. 129 | 130 | **WARNING:** `swiftformat .` will overwrite any Swift files it finds in the current directory, and any subfolders therein. If you run it in your home directory, it will probably reformat every Swift file on your hard drive. 131 | 132 | To use it safely, do the following: 133 | 134 | 1. Choose a file or directory that you want to apply the changes to. 135 | 136 | 2. Make sure that you have committed all your changes to that code safely in git (or whatever source control system you use). 137 | 138 | 3. (Optional) In Terminal, type `swiftformat --inferoptions "/path/to/your/code/"`. This will suggest a set of formatting options to use that match your existing project style (but you are free to ignore these and use the defaults, or your own settings if you prefer). 139 | 140 | The path can point to either a single Swift file or a directory of files. It can be either be absolute, or relative to the current directory. The `""` quotes around the path are optional, but if the path contains spaces then you either need to use quotes, or escape each space with `\`. You may include multiple paths separated by spaces. 141 | 142 | 4. In Terminal, type `swiftformat "/path/to/your/code/"`. The same rules apply as above with respect to paths, and multiple space-delimited paths are allowed. 143 | 144 | If you used `--inferoptions` to generate a suggested set of options in step 3, you should copy and paste them into the command, either before or after the path(s) to your source files. 145 | 146 | If you have created a [config file](#config-file), you can specify its path using `--config "/path/to/your/config-file/"`. Alternatively, if you name the file `.swiftformat` and place it inside the project you are formatting, it will be picked up automatically. 147 | 148 | 5. Press enter to begin formatting. Once the formatting is complete, use your source control system to check the changes, and verify that no undesirable changes have been introduced. If they have, revert the changes, tweak the options and try again. 149 | 150 | 6. (Optional) commit the changes. 151 | 152 | Following these instructions *should* ensure that you avoid catastrophic data loss, but in the unlikely event that it wipes your hard drive, **please note that I accept no responsibility**. 153 | 154 | **Using Standard Input/Output:** 155 | 156 | If you prefer, you can use unix pipes to include SwiftFormat as part of a command chain. For example, this is an alternative way to format a file: 157 | 158 | ```bash 159 | $ cat /path/to/file.swift | swiftformat --output /path/to/file.swift 160 | ``` 161 | 162 | Omitting the `--output /path/to/file.swift` will print the formatted file to Standard Output (stdout). You can also pass "stdout" explicitly as the output path: 163 | 164 | ```bash 165 | $ cat /path/to/file.swift | swiftformat --output stdout 166 | ``` 167 | 168 | Or you can use `>` to specify the output path as follows: 169 | 170 | ```bash 171 | $ cat /path/to/file.swift | swiftformat > /path/to/file.swift 172 | ``` 173 | 174 | If you do not supply an input file, SwiftFormat will automatically take its input from Standard Input (stdin), but will time-out if no input is received immediately and display the help screen. To make it explicit, pass "stdin" as the input path: 175 | 176 | ```bash 177 | $ cat /path/to/file.swift | swiftformat stdin 178 | ``` 179 | 180 | When using stdin, SwiftFormat does not have access to the file path of the input, so features that rely on the file location (such as inserting the creation date into header comments, or detecting `.swiftformat` configuration files in the file path) will not work. To solve this, you can provide the file path using the `--stdinpath` argument: 181 | 182 | ```bash 183 | $ cat /path/to/file.swift | swiftformat stdin --stdinpath /path/to/file.swift 184 | ``` 185 | 186 | 187 | Xcode source editor extension 188 | ----------------------------- 189 | 190 | **Installation:** 191 | 192 | Like the command-line tool, you can install the SwiftFormat for Xcode extension application via [Homebrew](http://brew.sh/). Assuming you already have Homebrew installed, type: 193 | 194 | ```bash 195 | $ brew install --cask swiftformat-for-xcode 196 | ``` 197 | 198 | This will install SwiftFormat for Xcode in your Applications folder. Double-click the app to launch it, and then follow the on-screen instructions. 199 | 200 | **NOTE:** The app should be correctly signed, but if you get a Gatekeeper warning when trying to open it you can bypass this by right-clicking (or control-clicking) the app and selecting `Open`. 201 | 202 | To update to the latest version once installed use: 203 | 204 | ```bash 205 | $ brew upgrade --cask swiftformat-for-xcode 206 | ``` 207 | 208 | Alternatively, if you prefer not to use Homebrew, you'll find the latest version of the SwiftFormat for Xcode application inside the EditorExtension folder included in the SwiftFormat repository. Download and unpack the zip archive, then drag `SwiftFormat for Xcode.app` into your `Applications` folder. 209 | 210 | **Usage:** 211 | 212 | Once you have launched the app and restarted Xcode, you'll find a SwiftFormat option under Xcode's Editor menu. 213 | 214 | You can configure the formatting [rules](#rules) and [options](#options) using the SwiftFormat for Xcode host application. There is currently no way to override these per-project, however, you can import and export different configurations using the File menu. You will need to do this again each time you switch projects. 215 | 216 | The format of the configuration file is described in the [Config section](#config-file) below. 217 | 218 | **Note:** SwiftFormat for Xcode cannot automatically detect changes to an imported configuration file. If you update the `.swiftformat` file for your project, you will need to manually re-import that file into SwiftFormat for Xcode in order for the Xcode source editor extension to use the new configuration. 219 | 220 | 221 | Xcode build phase 222 | ------------------- 223 | 224 | **NOTE:** Adding this script will overwrite your source files as you work on them, which has the annoying side-effect of clearing the undo history. You may wish to add the script to your test target rather than your main target, so that it is invoked only when you run the unit tests, and not every time you build the app. 225 | 226 | Alternatively, you might want to consider running SwiftFormat in [lint](#linting) mode as part of your normal build, and then running a formatting pass manually, or as part of a less-frequent build target (such as the tests). 227 | 228 | ### Using Swift Package Manager 229 | 230 | To set up SwiftFormat as an Xcode build phase, do the following: 231 | 232 | #### 1) Create a BuildTools folder & Package.swift 233 | 234 | 1. Create a folder called `BuildTools` in the same folder as your xcodeproj file 235 | 2. In this folder, create a file called `Package.swift`, with the following contents: 236 | ```swift 237 | // swift-tools-version:5.1 238 | import PackageDescription 239 | 240 | let package = Package( 241 | name: "BuildTools", 242 | platforms: [.macOS(.v10_11)], 243 | dependencies: [ 244 | .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.41.2"), 245 | ], 246 | targets: [.target(name: "BuildTools", path: "")] 247 | ) 248 | ``` 249 | 3. If you are running Xcode 11.4 or later, in the `BuildTools` folder create a file called `Empty.swift` with nothing in it. This is to satisfy a change in Swift Package Manager. 250 | 251 | #### 2) Add a Build phases to your app target 252 | 253 | 1. Click on your project in the file list, choose your target under `TARGETS`, click the `Build Phases` tab 254 | 2. Add a `New Run Script Phase` by clicking the little plus icon in the top left 255 | 3. Drag the new `Run Script` phase **above** the `Compile Sources` phase, expand it and paste the following script: 256 | 257 | ```bash 258 | cd BuildTools 259 | SDKROOT=macosx 260 | #swift package update #Uncomment this line temporarily to update the version used to the latest matching your BuildTools/Package.swift file 261 | swift run -c release swiftformat "$SRCROOT" 262 | ``` 263 | 264 | You can also use `swift run -c release --package-path BuildTools swiftformat "$SRCROOT"` if you need a more complex script and `cd BuildTools` breaks stuff. 265 | 266 | **NOTE:** You may wish to check BuildTools/Package.swift into your source control so that the version used by your run-script phase is kept in version control. It is recommended to add the following to your .gitignore file: `BuildTools/.build` and `BuildTools/.swiftpm`. 267 | 268 | ### Using Cocoapods 269 | 270 | #### 1) Add the SwiftFormat CLI to your Podfile 271 | 272 | 1. Add the `swiftformat` binary to your project directory via [CocoaPods](https://cocoapods.org/), by adding the following line to your Podfile then running `pod install`: 273 | 274 | ```ruby 275 | pod 'SwiftFormat/CLI' 276 | ``` 277 | 278 | **NOTE:** This will only install the pre-built command-line app, not the source code for the SwiftFormat framework. 279 | 280 | **NOTE (2):** When installing this way, GateKeeper may block swiftformat from running until you open it manually the first time by right-clicking in the Finder and selecting "Open". 281 | 282 | #### 2) Add a Build phase to your app target 283 | 284 | 1. Click on your project in the file list, choose your target under `TARGETS`, click the `Build Phases` tab 285 | 2. Add a `New Run Script Phase` by clicking the little plus icon in the top left 286 | 3. Drag the new `Run Script` phase **above** the `Compile Sources` phase, expand it and paste the following script: 287 | 288 | ```bash 289 | "${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat" "$SRCROOT" 290 | ``` 291 | 292 | ### Alternative: Locally installed SwiftFormat 293 | 294 | Alternatively, you could use a locally installed swiftformat command-line tool instead by putting the following in your Run Script build phase: 295 | 296 | ```bash 297 | if which swiftformat >/dev/null; then 298 | swiftformat . 299 | else 300 | echo "warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat" 301 | fi 302 | ``` 303 | 304 | This is not recommended for shared projects however, as different team members using different versions of SwiftFormat may result in noise in the commit history as code gets reformatted inconsistently. 305 | 306 | 307 | Via AppleScript 308 | ---------------- 309 | 310 | To run SwiftFormat on the frontmost Xcode document (project or workspace) you can use the following AppleScript: 311 | 312 | ```applescript 313 | tell application "Xcode" 314 | set frontWindow to the first window 315 | set myPath to path of document of frontWindow 316 | do shell script "cd " & myPath & ";cd ..; /usr/local/bin/swiftformat ." 317 | end tell 318 | ``` 319 | 320 | Some Apps you can trigger this from are [BetterTouchTool](https://folivora.ai), [Alfred](https://www.alfredapp.com) or [Keyboard Maestro](https://www.keyboardmaestro.com/main/). Another option is to define a QuickAction for Xcode via Automator and then assign a keyboard shortcut for it in the System Preferences. 321 | 322 | 323 | VSCode plugin 324 | -------------- 325 | 326 | If you prefer to use Microsoft's [VSCode](https://code.visualstudio.com) editor for writing Swift, [Valentin Knabel](https://github.com/vknabel) has created a [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=vknabel.vscode-swiftformat) for SwiftFormat. 327 | 328 | 329 | Sublime Text plugin 330 | -------------------- 331 | 332 | If you prefer to use the [Sublime Text](https://www.sublimetext.com) editor, try the [Sublime-Swift-Format plugin](https://github.com/aerobounce/Sublime-Swift-Format) by [Aerobounce](https://github.com/aerobounce). 333 | 334 | 335 | Git pre-commit hook 336 | --------------------- 337 | 338 | 1. Follow the instructions for installing the SwiftFormat command-line tool. 339 | 340 | 2. Install [git-format-staged](https://github.com/hallettj/git-format-staged). 341 | 342 | 3. Edit or create a `.git/hooks/pre-commit` file in your project folder. The .git folder is hidden but should already exist if you are using Git with your project, so open it with the terminal, or the Finder's `Go > Go to Folder...` menu. 343 | 344 | 4. Add the following line in the pre-commit file. The `{}` will be replaced automatically by the path to the Swift file being formatted: 345 | 346 | ```bash 347 | #!/bin/bash 348 | git-format-staged --formatter "swiftformat stdin --stdinpath '{}'" "*.swift" 349 | ``` 350 | 351 | (Note that this example uses your locally installed version of SwiftFormat, not a separate copy in your project repository. You can replace `swiftformat` with the path to a copy inside your project if you prefer.) 352 | 353 | 5. enable the hook by typing `chmod +x .git/hooks/pre-commit` in the terminal. 354 | 355 | The pre-commit hook will now run whenever you run `git commit`. Running `git commit --no-verify` will skip the pre-commit hook. 356 | 357 | **NOTE:** If you are using Git via a GUI client such as [Tower](https://www.git-tower.com), [additional steps](https://www.git-tower.com/help/mac/faq-and-tips/faq/hook-scripts) may be needed. 358 | 359 | **NOTE (2):** Unlike the Xcode build phase approach, git pre-commit hook won't be checked in to source control, and there's no way to guarantee that all users of the project are using the same version of SwiftFormat. For a collaborative project, you might want to consider a *post*-commit hook instead, which would run on your continuous integration server. 360 | 361 | On CI using Danger 362 | ------------------- 363 | 364 | To setup SwiftFormat to be used by your continuous integration system using [Danger](http://danger.systems/ruby/), do the following: 365 | 366 | 1. Follow the [`instructions`](http://danger.systems/guides/getting_started.html) to setup Danger. 367 | 2. Add the [`danger-swiftformat`](https://github.com/garriguv/danger-ruby-swiftformat) plugin to your `Gemfile`. 368 | 3. Add the following to your `Dangerfile`: 369 | 370 | ```ruby 371 | swiftformat.binary_path = "/path/to/swiftformat" # optional 372 | swiftformat.additional_args = "--indent tab --self insert" # optional 373 | swiftformat.check_format(fail_on_error: true) 374 | ``` 375 | 376 | **NOTE:** It is recommended to add the `swiftformat` binary to your project directory to ensure the same version is used each time (see the [Xcode build phase](#xcode-build-phase) instructions above). 377 | 378 | Configuration 379 | ------------- 380 | 381 | SwiftFormat's configuration is split between **rules** and **options**. Rules are functions in the SwiftFormat library that apply changes to the code. Options are settings that control the behavior of the rules. 382 | 383 | 384 | Options 385 | ------- 386 | 387 | The options available in SwiftFormat can be displayed using the `--options` command-line argument. The default value for each option is indicated in the help text. 388 | 389 | Rules are configured by adding `--[option_name] [value]` to your command-line arguments, or by creating a `.swiftformat` [config file](#config-file) and placing it in your project directory. 390 | 391 | A given option may affect multiple rules. Use `--ruleinfo [rule_name]` command for details about which options affect a given rule, or see the [Rules.md](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md) file. 392 | 393 | You can configure options for specific files or code ranges by using `swiftformat:options` directive in comments inside your Swift file. To temporarily set one or more options inside a source file, use: 394 | 395 | ```swift 396 | // swiftformat:options --indent 2 --allman true 397 | ``` 398 | 399 | To apply an options override only to a particular line, use the `:next` modifier: 400 | 401 | ```swift 402 | // swiftformat:options:next --semicolons inline 403 | doTheThing(); print("Did the thing") 404 | ``` 405 | 406 | 407 | Rules 408 | ----- 409 | 410 | SwiftFormat includes over 50 rules, and new ones are added all the time. An up-to-date list can be found in [Rules.md](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md) along with documentation for how they are used. 411 | 412 | The list of available rules can be displayed within the command-line app using the `--rules` argument. Rules can be either enabled or disabled. Most are enabled by default. Disabled rules are marked with "(disabled)" when displayed using `--rules`. 413 | 414 | You can use the `--ruleinfo [rule_name]` command to get information about a specific rule. Pass a comma-delimited list of rule names to get information for multiple rules at once, or use `--ruleinfo` with no argument for info on all rules. 415 | 416 | You can disable rules individually using `--disable` followed by a list of one or more comma-delimited rule names, or enable opt-in rules using `--enable` followed by the rule names: 417 | 418 | ```bash 419 | --disable redundantSelf,trailingClosures 420 | --enable isEmpty 421 | ``` 422 | 423 | If you prefer, you can use multiple `--enable`/`--disable` arguments instead of using commas: 424 | 425 | ```bash 426 | --disable indent 427 | --disable linebreaks 428 | --disable redundantSelf 429 | ``` 430 | 431 | Alternatively, you can use the line continuation character `\` to wrap a single argument over multiple line: 432 | 433 | ```bash 434 | --disable \ 435 | indent, \ 436 | linebreaks, \ 437 | redundantSelf 438 | ``` 439 | 440 | To avoid automatically opting-in to new rules when SwiftFormat is updated, use the`--rules` argument to *only* enable the rules you specify: 441 | 442 | ```bash 443 | --rules indent,linebreaks 444 | ``` 445 | 446 | As above, you may include multiple `--rules` arguments, or use the line continuation character `\` to wrap the rules onto separate lines: 447 | 448 | ```bash 449 | --rules redundantSelf 450 | --rules \ 451 | indent, \ 452 | linebreaks 453 | ``` 454 | 455 | To see exactly which rules were applied to a given file, you can use the `--verbose` command-line option to force SwiftFormat to print a more detailed log as it applies the formatting. **NOTE:** running in verbose mode is slower than the default mode. 456 | 457 | You can disable rules for specific files or code ranges by using `swiftformat:` directives in comments inside your Swift file. To temporarily disable one or more rules inside a source file, use: 458 | 459 | ```swift 460 | // swiftformat:disable [ [rule<3> ...]] 461 | ``` 462 | 463 | To enable the rule(s) again, use: 464 | 465 | ```swift 466 | // swiftformat:enable [ [rule<3> ...]] 467 | ``` 468 | 469 | To disable all rules use: 470 | 471 | ```swift 472 | // swiftformat:disable all 473 | ``` 474 | 475 | And to enable them all again, use: 476 | 477 | ```swift 478 | // swiftformat:enable all 479 | ``` 480 | 481 | To temporarily prevent one or more rules being applied to just the next line, use: 482 | 483 | ```swift 484 | // swiftformat:disable:next [ [rule<3> ...]] 485 | let foo = bar // rule(s) will be disabled for this line 486 | let bar = baz // rule(s) will be re-enabled for this line 487 | ``` 488 | 489 | There is no need to manually re-enable a rule after using the `next` directive. 490 | 491 | **NOTE:** The `swiftformat:enable` directives only serves to counter a previous `swiftformat:disable` directive in the same file. It is not possible to use `swiftformat:enable` to enable a rule that was not already enabled when formatting started. 492 | 493 | 494 | Swift version 495 | ------------- 496 | 497 | Most SwiftFormat rules are version-agnostic, but some are applicable only to newer Swift versions. These rules will be disabled automatically if the Swift version is not specified, so to make sure that the full functionality is available you should specify the version of Swift that is used by your project. 498 | 499 | You can specify the Swift version in one of two ways: 500 | 501 | The preferred option is to add a `.swift-version` file to your project directory. This is a text file that should contain the minimum Swift version supported by your project, and is a standard already used by other tools. 502 | 503 | The `.swift-version` file applies hierarchically; If you have submodules in your project that use a different Swift version, you can add separate `.swift-version` files to those directories. 504 | 505 | The other option to specify the Swift version using the `--swiftversion` command line argument. Note that this will be overridden by any `.swift-version` files encountered while processing. 506 | 507 | 508 | Config file 509 | ----------- 510 | 511 | Although it is possible to configure SwiftFormat directly by using the command-line [options](#options) and [rules](#rules) detailed above, it is sometimes more convenient to create a configuration file, which can be added to your project and shared with other developers. 512 | 513 | A SwiftFormat configuration file consists of one or more command-line options, split onto separate lines, e.g: 514 | 515 | ``` 516 | --allman true 517 | --indent tab 518 | --disable elseOnSameLine,semicolons 519 | ``` 520 | 521 | While formatting, SwiftFormat will automatically check inside each subdirectory for the presence of a `.swiftformat` file and will apply any options that it finds there to the files in that directory. 522 | 523 | This allows you to override certain rules or formatting options just for a particular directory of files. You can also specify excluded files relative to that directory using `--exclude`, which may be more convenient than specifying them at the top-level: 524 | 525 | ``` 526 | --exclude Pods,Generated 527 | ``` 528 | 529 | The `--exclude` option takes a comma-delimited list of file or directory paths to exclude from formatting. Excluded paths are relative to the config file containing the `--exclude` command. The excluded paths can include wildcards, specified using Unix "Glob" syntax, as [documented below](#globs). 530 | 531 | Config files named ".swiftformat" will be processed automatically, however, you can select an additional configuration file to use for formatting using the `--config "path/to/config/file"` command-line argument. A configuration file selected using `--config` does not need to be named ".swiftformat", and can be located outside of the project directory. 532 | 533 | The config file format is designed to be edited by hand. You may include blank lines for readability, and can also add comments using a hash prefix (#), e.g. 534 | 535 | ``` 536 | # format options 537 | --allman true 538 | --indent tab # tabs FTW! 539 | 540 | # file options 541 | --exclude Pods 542 | 543 | # rules 544 | --disable elseOnSameLine,semicolons 545 | ``` 546 | 547 | If you would prefer not to edit the configuration file by hand, you can use the [SwiftFormat for Xcode](#xcode-source-editor-extension) app to edit the configuration and export a configuration file. You can also use the swiftformat command-line-tool's `--inferoptions` command to generate a config file from your existing project, like this: 548 | 549 | ```bash 550 | $ cd /path/to/project 551 | $ swiftformat --inferoptions . --output .swiftformat 552 | ``` 553 | 554 | Globs 555 | ----- 556 | 557 | When excluding files from formatting using the `--exclude` option, you may wish to make use of wildcard paths (aka "Globs") to match all files that match a particular naming convention without having to manually list them all. 558 | 559 | SwiftFormat's glob syntax is based on Ruby's implementation, which varies slightly from the Unix standard. The following patterns are supported: 560 | 561 | * `*` - A single star matches zero or more characters in a filename, but *not* a `/`. 562 | 563 | * `**` - A double star will match anything, including one or more `/`. 564 | 565 | * `?` - A question mark will match any single character except `/`. 566 | 567 | * `[abc]` - Matches any single character inside the brackets. 568 | 569 | * `[a-z]` - Matches a single character in the specified range in the brackets. 570 | 571 | * `{foo,bar}` - Matches any one of the comma-delimited strings inside the braces. 572 | 573 | Examples: 574 | 575 | * `foo.swift` - Matches the file "foo.swift" in the same directory as the config file. 576 | 577 | * `*.swift` - Matches any Swift file in the same directory as the config file. 578 | 579 | * `foo/bar.swift` - Matches the file "bar.swift" in the directory "foo". 580 | 581 | * `**/foo.swift` - Matches any file named "foo.swift" in the project. 582 | 583 | * `**/*.swift` - Matches any Swift file in the project. 584 | 585 | * `**/Generated` - Matches any folder called `Generated` in the project. 586 | 587 | * `**/*_generated.swift` - Matches any Swift file with the suffix "_generated" in the project. 588 | 589 | 590 | Linting 591 | ------- 592 | 593 | SwiftFormat is primarily designed as a formatter rather than a linter, i.e. it is designed to fix your code rather than tell you what's wrong with it. However, sometimes it can be useful to verify that code has been formatted in a context where it is not desirable to actually change it. 594 | 595 | A typical example would be as part of a CI (Continuous Integration) process, where you may wish to have an automated script that checks committed code for style violations. While you can use a separate tool such as [SwiftLint](https://github.com/realm/SwiftLint) for this, it makes sense to be able to validate the formatting against the exact same rules as you are using to apply it. 596 | 597 | In order to run SwiftFormat as a linter, you can use the `--lint` command-line option: 598 | 599 | ```bash 600 | $ swiftformat --lint path/to/project 601 | ``` 602 | 603 | This runs the same rules as format mode, and all the same configuration options apply, however, no files will be modified. Instead, SwiftFormat will format each file in memory and then compare the result against the input and report the lines that required changes. 604 | 605 | The `--lint` option is similar to `--dryrun`, but `--lint` returns warnings for every line that required changes, and will return a nonzero error code if any changes are detected, which is useful if you want it to fail a build step on your CI server. 606 | 607 | If you would prefer `--lint` not to fail your build, you can use the `--lenient` option to force SwiftFormat to return success in `--lint` mode even when formatting issues were detected. 608 | 609 | ```bash 610 | $ swiftformat --lint --lenient path/to/project 611 | ``` 612 | 613 | By default, `--lint` will only report lines that require formatting, but you can use the additional `--verbose` flag to display additional info about which files were checked, even if there were no changes needed. 614 | 615 | If you would prefer not to see a warning for each and every formatting change, you can use the `--quiet` flag to suppress all output except errors. 616 | 617 | 618 | Cache 619 | ------ 620 | 621 | SwiftFormat uses a cache file to avoid reformatting files that haven't changed. For a large project, this can significantly reduce processing time. 622 | 623 | By default, the cache is stored in `~/Library/Caches/com.charcoaldesign.swiftformat` on macOS, or `/var/tmp/com.charcoaldesign.swiftformat` on Linux. Use the command-line option `--cache ignore` to ignore the cached version and re-apply formatting to all files. Alternatively, you can use `--cache clear` to delete the cache (or you can just manually delete the cache file). 624 | 625 | The cache is shared between all projects. The file is fairly small, as it only stores the path and size for each file, not the contents. If you do start experiencing slowdown due to the cache growing too large, you might want to consider using a separate cache file for each project. 626 | 627 | You can specify a custom cache file location by passing a path as the `--cache` option value. For example, you might want to store the cache file inside your project directory. It is fine to check in the cache file if you want to share it between different users of your project, as the paths stored in the cache are relative to the location of the formatted files. 628 | 629 | 630 | File headers 631 | ------------- 632 | 633 | SwiftFormat can be configured to strip or replace the header comments in every file with a template. The "header comment" is defined as a comment block that begins on the first nonblank line in the file, and is followed by at least one blank line. This may consist of a single comment body, or multiple comments on consecutive lines: 634 | 635 | ```swift 636 | // This is a header comment 637 | ``` 638 | 639 | ```swift 640 | // This is a regular comment 641 | func foo(bar: Int) -> Void { ... } 642 | ``` 643 | 644 | The header template is a string that you provide using the `--header` command-line option. Passing a value of `ignore` (the default) will leave the header comments unmodified. Passing `strip` or an empty string `""` will remove them. If you wish to provide a custom header template, the format is as follows: 645 | 646 | For a single-line template: `--header "Copyright (c) 2017 Foobar Industries"` 647 | 648 | For a multiline comment, mark linebreaks with `\n`: `--header "First line\nSecond line"` 649 | 650 | You can optionally include Swift comment markup in the template if you wish: `--header "/*--- Header comment ---*/"` 651 | 652 | If you do not include comment markup, each line in the template will be prepended with `//` and a single space. 653 | 654 | It is common practice to include the file name, creation date and/or the current year in a comment header copyright notice. To do that, you can use the following placeholders: 655 | 656 | * `{file}` - the name of the file 657 | * `{year}` - the current year 658 | * `{created}` - the date on which the file was created 659 | * `{created.year}` - the year in which the file was created 660 | 661 | For example, a header template of: 662 | 663 | ```bash 664 | --header "{file}\nCopyright (c) {year} Foobar Industries\nCreated by John Smith on {created}." 665 | ``` 666 | 667 | Will be formatted as: 668 | 669 | ```swift 670 | // SomeFile.swift 671 | // Copyright (c) 2019 Foobar Industries 672 | // Created by John Smith on 01/02/2016. 673 | ``` 674 | 675 | **NOTE:** the `{year}` value and `{created}` date format are determined from the current locale and timezone of the machine running the script. 676 | 677 | 678 | FAQ 679 | ----- 680 | 681 | *Q. How is this different from SwiftLint?* 682 | 683 | > A. SwiftLint is primarily designed to find and report code smells and style violations in your code. SwiftFormat is designed to fix them. While SwiftLint can autocorrect some issues, and SwiftFormat has some support for [linting](#linting), their primary functions are different. 684 | 685 | 686 | *Q. Can SwiftFormat and SwiftLint be used together?* 687 | 688 | > A. Absolutely! The style rules encouraged by both tools are quite similar, and SwiftFormat even fixes some style violations that SwiftLint warns about but can't currently autocorrect. 689 | 690 | 691 | *Q. What platforms does SwiftFormat support?* 692 | 693 | > A. SwiftFormat works on macOS 10.13 (High Sierra) and above, and also runs on Ubuntu Linux. 694 | 695 | 696 | *Q. What versions of Swift are supported?* 697 | 698 | > A. The SwiftFormat framework and command-line tool can be compiled using Swift 4.2 and above, and can format programs written in Swift 4.x or 5. Swift 3.x is no longer actively supported. If you are still using Swift 3.x or earlier and find that SwiftFormat breaks your code, the best solution is probably to revert to an earlier SwiftFormat release, or enable only a small subset of rules. Use the `--swiftversion` argument to enable additional rules specific to later Swift versions. 699 | 700 | 701 | *Q. SwiftFormat made changes I didn't want it to. How can I find out which rules to disable?* 702 | 703 | > A. If you run SwiftFormat using the `--verbose` option, it will tell you which rules were applied to each file. You can then selectively disable certain rules using the `--disable` argument (see below). 704 | 705 | 706 | *Q. People on my team have different SwiftFormat versions installed. How can we ensure consistent formatting? 707 | 708 | > A. You can specify a `--minversion` argument in your project's .swiftformat` file to fail the build if developers attempt to use an older SwiftFormat version. 709 | 710 | 711 | *Q. How can I modify the formatting rules?* 712 | 713 | > A. Many configuration options are exposed in the command-line interface or `.swiftformat` configuration file. You can either set these manually, or use the `--inferoptions` argument to automatically generate the configuration from your existing project. 714 | 715 | > If there is a rule that you don't like, and which cannot be configured to your liking via the command-line options, you can disable one or more rules by using the `--disable` argument, followed by the name of the rules, separated by commas. You can display a list of all supported rules using the `--rules` argument, and their behaviors are documented above this section in the README. 716 | 717 | > If you are using the Xcode source editor extension, rules and options can be configured using the [SwiftFormat for Xcode](#xcode-source-editor-extension) host application. Unfortunately, due to limitation of the Extensions API, there is no way to configure these on a per-project basis. 718 | 719 | > If the options you want aren't exposed, and disabling the rule doesn't solve the problem, the rules are implemented in the file `Rules.swift`, so you can modify them and build a new version of the command-line tool. If you think your changes might be generally useful, make a pull request. 720 | 721 | 722 | Q. I don't want to be surprised by new rules added when I upgrade SwiftFormat. How can I prevent this? 723 | 724 | > A. You can use the `--rules` argument to specify an exclusive list of rules to run. If new rules are added, they won't be enabled if you have specified a `--rules` list in your SwiftFormat configuration. 725 | 726 | 727 | *Q. Why can't I set the indent width or choose between tabs/spaces in the [SwiftFormat for Xcode](#xcode-source-editor-extension) options?* 728 | 729 | > Indent width and tabs/spaces can be configured in Xcode on a per project-basis. You'll find the option under "Text Settings" in the Files inspector of the right-hand sidebar. 730 | 731 | 732 | *Q. After applying SwiftFormat, my code won't compile. Is that a bug?* 733 | 734 | > A. SwiftFormat should ideally never break your code. Check the [known issues](#known-issues), and if it's not already listed there, or the suggested workaround doesn't solve your problem, please [open an issue on Github](https://github.com/nicklockwood/SwiftFormat/issues). 735 | 736 | 737 | *Q. Can I use SwiftFormat to lint my code without changing it?* 738 | 739 | > A. Yes, see the [linting](#linting) section above for details. 740 | 741 | 742 | *Q. Can I use the `SwiftFormat.framework` inside another app?* 743 | 744 | > A. Yes, the SwiftFormat framework can be included in an app or test target, and used for many kinds of parsing and processing of Swift source code besides formatting. The SwiftFormat framework is available as a [CocoaPod](https://cocoapods.org/pods/SwiftFormat) for easy integration. 745 | 746 | 747 | Known issues 748 | --------------- 749 | 750 | * If a type initializer or factory method returns an implicitly unwrapped optional value then the `redundantType` rule may remove the explicit type in a situation where it's actually required. To work around this you can either use `--redundanttype explicit`, or use the `// swiftformat:disable:next redundantType` comment directive to disable the rule at the call site (or just disable the `redundantType` rule completely). 751 | 752 | * When using the `initCoderUnavailable` rule, if an `init` that is marked as unavailable is overridden elsewhere in the program then it will cause a compilation error. The recommended workaround is to remove the override (which shouldn't affect the program behavior if the init was really unused) or use the `// swiftformat:disable:next initCoderUnavailable` comment directive to disable the rule for the overridden init (or just disable the `initCoderUnavailable` rule completely). 753 | 754 | * When using the `extensionAccessControl` rule with the `--extensionacl on-extension` option, if you have public methods defined on an internal type defined in another file, the resultant public extension will no longer compile. The recommended solution is to manually remove the `public` modifier (this won't change the program behavior) or disable the `extensionAccessControl` rule. 755 | 756 | * When using the `preferKeyPath` rule, conversion of `compactMap { $0.foo }` to `compactMap(\.foo)` or `flatMap { $0.foo }` to `flatMap(\.foo)` will result in code that fails to compile if `foo` is not an `Optional` property. This is due to a difference in the way that Swift handles type inference for closures vs keyPaths, as discussed [here](https://bugs.swift.org/browse/SR-13347). The recommended workaround is to replace `compactMap()` or `flatMap()` with `map()` in these cases, which will not change the behavior of the code. 757 | 758 | * When using the `--self remove` option, the `redundantSelf` rule will remove references to `self` in autoclosure arguments, which may change the meaning of the code, or cause it not to compile. To work around this issue, use the `--selfrequired` option to provide a comma-delimited list of methods to be excluded from the rule. The `expect()` function from the popular [Nimble](https://github.com/Quick/Nimble) unit testing framework is already excluded by default. If you are using the `--self insert` option then this is not an issue. 759 | 760 | * If you assign `SomeClass.self` to a variable and then instantiate an instance of the class using that variable, Swift requires that you use an explicit `.init()`, however, the `redundantInit` rule is not currently capable of detecting this situation and will remove the `.init`. To work around this issue, use the `// swiftformat:disable:next redundantInit` comment directive to disable the rule for any affected lines of code (or just disable the `redundantInit` rule completely). 761 | 762 | * The `--self insert` option can only recognize locally declared member variables, not ones inherited from superclasses or extensions in other files, so it cannot insert missing `self` references for those. Note that the reverse is not true: `--self remove` should remove *all* redundant `self` references. 763 | 764 | * The `trailingClosures` rule can generate ambiguous code if a function has multiple optional closure arguments, or if multiple functions have signatures differing only by the name of the closure argument. For this reason, the rule is limited to anonymous closure arguments by default. You can use the `--trailingclosures` and `--nevertrailing` arguments to explicitly opt in or out of trailing closure support for specific functions. 765 | 766 | * The `isEmpty` rule will convert `count == 0` to `isEmpty` even for types that do not have an `isEmpty` method, such as `NSArray`/`NSDictionary`/etc. Use of Foundation collections in Swift code is pretty rare, but just in case, the rule is disabled by default. 767 | 768 | * If a file begins with a comment, the `stripHeaders` rule will remove it if it is followed by a blank line. To avoid this, make sure that the first comment is directly followed by a line of code. 769 | 770 | * When running a version of SwiftFormat built using Xcode 10.2 on macOS 10.14.3 or earlier, you may experience a crash with the error "dyld: Library not loaded: @rpath/libswiftCore.dylib". To fix this, you need to install the [Swift 5 Runtime Support for Command Line Tools](https://support.apple.com/kb/DL1998). These tools are included by default in macOS 10.14.4 and later. 771 | 772 | 773 | Tip Jar 774 | ----------- 775 | 776 | SwiftFormat is not a commercially-funded product, it's a labor of love given freely to the community. If you find it useful, please consider making a donation. 777 | 778 | [![Donate via PayPal](https://www.paypalobjects.com/en_GB/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9ZGWNK5FEZFF6&source=url) 779 | 780 | 781 | Credits 782 | ------------ 783 | 784 | * [Tony Arnold](https://github.com/tonyarnold) - SwiftFormat for Xcode 785 | * [Vincent Bernier](https://github.com/vinceburn) - SwiftFormat for Xcode settings UI 786 | * [Vikram Kriplaney](https://github.com/markiv) - SwiftFormat for Xcode icon and search feature 787 | * [Hyperphonic](https://github.com/hyperphonic0) - Xcode 12 compatibility for SwiftFormat 788 | * [Maxime Marinel](https://github.com/bourvill) - Git pre-commit hook script 789 | * [Romain Pouclet](https://github.com/palleas) - Homebrew formula 790 | * [Aerobounce](https://github.com/aerobounce) - Homebrew cask and Sublime Text plugin 791 | * [Cal Stephens](https://github.com/calda) - Several new formatting rules and options 792 | * [Facundo Menzella](https://github.com/acumenzella) - Several new formatting rules 793 | * [Ali Akhtarzada](https://github.com/aliak00) - Several path-related CLI enhancements 794 | * [Yonas Kolb](https://github.com/yonaskolb) - Swift Package Manager integration 795 | * [Wolfgang Lutz](https://github.com/Lutzifer) - AppleScript integration instructions 796 | * [Balázs Kilvády](https://github.com/balitm) - Xcode lint warning integration 797 | * [Anthony Miller](https://github.com/AnthonyMDev) - Improvements to wrap/indent logic 798 | * [Shingo Takagi](https://github.com/zizi4n5) - Several brace-related bug fixes 799 | * [Benedek Kozma](https://github.com/cyberbeni) - Lint-only rules option 800 | * [Juri Pakaste](https://github.com/juri) - Filelist feature 801 | * [Jim Puls](https://github.com/puls) - Big Sur icon update 802 | * [Daniele Formichelli](https://github.com/danyf90) - JSON reporter 803 | * [Mahdi Bchatnia](https://github.com/inket) - Linux build workflow 804 | * [Nick Lockwood](https://github.com/nicklockwood) - Everything else 805 | 806 | ([Full list of contributors](https://github.com/nicklockwood/SwiftFormat/graphs/contributors)) 807 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CompositionalLayoutViewController/CompositionalLayoutViewController-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.5.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CompositionalLayoutViewController/CompositionalLayoutViewController-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_CompositionalLayoutViewController : NSObject 3 | @end 4 | @implementation PodsDummy_CompositionalLayoutViewController 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CompositionalLayoutViewController/CompositionalLayoutViewController-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CompositionalLayoutViewController/CompositionalLayoutViewController-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double CompositionalLayoutViewControllerVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char CompositionalLayoutViewControllerVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CompositionalLayoutViewController/CompositionalLayoutViewController.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CompositionalLayoutViewController/CompositionalLayoutViewController.modulemap: -------------------------------------------------------------------------------- 1 | framework module CompositionalLayoutViewController { 2 | umbrella header "CompositionalLayoutViewController-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CompositionalLayoutViewController/CompositionalLayoutViewController.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## CompositionalLayoutViewController 5 | 6 | Copyright (c) 2021 Akira 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Reusable 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2016 AliSoftware 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | 51 | 52 | ## SwiftFormat 53 | 54 | MIT License 55 | 56 | Copyright (c) 2016 Nick Lockwood 57 | 58 | Permission is hereby granted, free of charge, to any person obtaining a copy 59 | of this software and associated documentation files (the "Software"), to deal 60 | in the Software without restriction, including without limitation the rights 61 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 62 | copies of the Software, and to permit persons to whom the Software is 63 | furnished to do so, subject to the following conditions: 64 | 65 | The above copyright notice and this permission notice shall be included in all 66 | copies or substantial portions of the Software. 67 | 68 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 69 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 70 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 71 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 72 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 73 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 74 | SOFTWARE. 75 | 76 | Generated by CocoaPods - https://cocoapods.org 77 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2021 Akira <akira.matsuda@me.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | CompositionalLayoutViewController 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | The MIT License (MIT) 47 | 48 | Copyright (c) 2016 AliSoftware 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy 51 | of this software and associated documentation files (the "Software"), to deal 52 | in the Software without restriction, including without limitation the rights 53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | copies of the Software, and to permit persons to whom the Software is 55 | furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in all 58 | copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 66 | SOFTWARE. 67 | 68 | License 69 | MIT 70 | Title 71 | Reusable 72 | Type 73 | PSGroupSpecifier 74 | 75 | 76 | FooterText 77 | MIT License 78 | 79 | Copyright (c) 2016 Nick Lockwood 80 | 81 | Permission is hereby granted, free of charge, to any person obtaining a copy 82 | of this software and associated documentation files (the "Software"), to deal 83 | in the Software without restriction, including without limitation the rights 84 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 85 | copies of the Software, and to permit persons to whom the Software is 86 | furnished to do so, subject to the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be included in all 89 | copies or substantial portions of the Software. 90 | 91 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 92 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 93 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 94 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 95 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 96 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 97 | SOFTWARE. 98 | 99 | License 100 | MIT 101 | Title 102 | SwiftFormat 103 | Type 104 | PSGroupSpecifier 105 | 106 | 107 | FooterText 108 | Generated by CocoaPods - https://cocoapods.org 109 | Title 110 | 111 | Type 112 | PSGroupSpecifier 113 | 114 | 115 | StringsTable 116 | Acknowledgements 117 | Title 118 | Acknowledgements 119 | 120 | 121 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_CompositionalLayoutViewController_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_CompositionalLayoutViewController_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/CompositionalLayoutViewController/CompositionalLayoutViewController.framework" 180 | install_framework "${BUILT_PRODUCTS_DIR}/Reusable/Reusable.framework" 181 | fi 182 | if [[ "$CONFIGURATION" == "Release" ]]; then 183 | install_framework "${BUILT_PRODUCTS_DIR}/CompositionalLayoutViewController/CompositionalLayoutViewController.framework" 184 | install_framework "${BUILT_PRODUCTS_DIR}/Reusable/Reusable.framework" 185 | fi 186 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 187 | wait 188 | fi 189 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_CompositionalLayoutViewController_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_CompositionalLayoutViewController_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController" "${PODS_CONFIGURATION_BUILD_DIR}/Reusable" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController/CompositionalLayoutViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reusable/Reusable.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "CompositionalLayoutViewController" -framework "Reusable" -framework "UIKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_CompositionalLayoutViewController_Example { 2 | umbrella header "Pods-CompositionalLayoutViewController_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Example/Pods-CompositionalLayoutViewController_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController" "${PODS_CONFIGURATION_BUILD_DIR}/Reusable" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController/CompositionalLayoutViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reusable/Reusable.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "CompositionalLayoutViewController" -framework "Reusable" -framework "UIKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_CompositionalLayoutViewController_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_CompositionalLayoutViewController_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_CompositionalLayoutViewController_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_CompositionalLayoutViewController_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController" "${PODS_CONFIGURATION_BUILD_DIR}/Reusable" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController/CompositionalLayoutViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reusable/Reusable.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "CompositionalLayoutViewController" -framework "Reusable" -framework "UIKit" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_CompositionalLayoutViewController_Tests { 2 | umbrella header "Pods-CompositionalLayoutViewController_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-CompositionalLayoutViewController_Tests/Pods-CompositionalLayoutViewController_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController" "${PODS_CONFIGURATION_BUILD_DIR}/Reusable" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CompositionalLayoutViewController/CompositionalLayoutViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reusable/Reusable.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "CompositionalLayoutViewController" -framework "Reusable" -framework "UIKit" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import CompositionalLayoutViewController 2 | @testable import CompositionalLayoutViewController_Example 3 | import XCTest 4 | 5 | import UIKit 6 | 7 | class TestSection: CollectionViewSection { 8 | var identifier: String { 9 | return "test-section" 10 | } 11 | 12 | var snapshotItems: [AnyHashable] { 13 | return [title] 14 | } 15 | 16 | var title: String 17 | 18 | init(title: String) { 19 | self.title = title 20 | } 21 | 22 | func registerCell(collectionView: UICollectionView) {} 23 | 24 | func registerSupplementaryView(collectionView: UICollectionView) {} 25 | 26 | func layoutSection(environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { 27 | let itemSize = NSCollectionLayoutSize( 28 | widthDimension: .fractionalWidth(1), 29 | heightDimension: .fractionalHeight(1) 30 | ) 31 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 32 | let groupSize = NSCollectionLayoutSize( 33 | widthDimension: .fractionalWidth(1), 34 | heightDimension: .absolute(100) 35 | ) 36 | let group = NSCollectionLayoutGroup.horizontal( 37 | layoutSize: groupSize, 38 | subitems: [item] 39 | ) 40 | let section = NSCollectionLayoutSection(group: group) 41 | section.contentInsets = .init( 42 | top: 0, 43 | leading: 23, 44 | bottom: 0, 45 | trailing: 23 46 | ) 47 | return section 48 | } 49 | 50 | func cell(_ collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell? { 51 | return nil 52 | } 53 | 54 | func supplementaryView(_ collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? { 55 | return nil 56 | } 57 | 58 | func configureSupplementaryView(_ view: UICollectionReusableView, indexPath: IndexPath) {} 59 | } 60 | 61 | class Tests: XCTestCase { 62 | override func setUp() { 63 | super.setUp() 64 | // Put setup code here. This method is called before the invocation of each test method in the class. 65 | } 66 | 67 | override func tearDown() { 68 | // Put teardown code here. This method is called after the invocation of each test method in the class. 69 | super.tearDown() 70 | } 71 | 72 | func testNonce() { 73 | // This is an example of a functional test case. 74 | let section = TestSection(title: "") 75 | XCTAssertNil(section.nonce) 76 | section.makeUnique() 77 | if section.nonce != nil { 78 | return 79 | } 80 | XCTFail() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # A sample Gemfile 2 | source "https://rubygems.org" 3 | 4 | gem "cocoapods" 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (7.0.4.3) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | addressable (2.8.1) 12 | public_suffix (>= 2.0.2, < 6.0) 13 | algoliasearch (1.27.5) 14 | httpclient (~> 2.8, >= 2.8.3) 15 | json (>= 1.5.1) 16 | atomos (0.1.3) 17 | claide (1.1.0) 18 | cocoapods (1.12.0) 19 | addressable (~> 2.8) 20 | claide (>= 1.0.2, < 2.0) 21 | cocoapods-core (= 1.12.0) 22 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 23 | cocoapods-downloader (>= 1.6.0, < 2.0) 24 | cocoapods-plugins (>= 1.0.0, < 2.0) 25 | cocoapods-search (>= 1.0.0, < 2.0) 26 | cocoapods-trunk (>= 1.6.0, < 2.0) 27 | cocoapods-try (>= 1.1.0, < 2.0) 28 | colored2 (~> 3.1) 29 | escape (~> 0.0.4) 30 | fourflusher (>= 2.3.0, < 3.0) 31 | gh_inspector (~> 1.0) 32 | molinillo (~> 0.8.0) 33 | nap (~> 1.0) 34 | ruby-macho (>= 2.3.0, < 3.0) 35 | xcodeproj (>= 1.21.0, < 2.0) 36 | cocoapods-core (1.12.0) 37 | activesupport (>= 5.0, < 8) 38 | addressable (~> 2.8) 39 | algoliasearch (~> 1.0) 40 | concurrent-ruby (~> 1.1) 41 | fuzzy_match (~> 2.0.4) 42 | nap (~> 1.0) 43 | netrc (~> 0.11) 44 | public_suffix (~> 4.0) 45 | typhoeus (~> 1.0) 46 | cocoapods-deintegrate (1.0.5) 47 | cocoapods-downloader (1.6.3) 48 | cocoapods-plugins (1.0.0) 49 | nap 50 | cocoapods-search (1.0.1) 51 | cocoapods-trunk (1.6.0) 52 | nap (>= 0.8, < 2.0) 53 | netrc (~> 0.11) 54 | cocoapods-try (1.2.0) 55 | colored2 (3.1.2) 56 | concurrent-ruby (1.2.2) 57 | escape (0.0.4) 58 | ethon (0.16.0) 59 | ffi (>= 1.15.0) 60 | ffi (1.15.5) 61 | fourflusher (2.3.1) 62 | fuzzy_match (2.0.4) 63 | gh_inspector (1.1.3) 64 | httpclient (2.8.3) 65 | i18n (1.12.0) 66 | concurrent-ruby (~> 1.0) 67 | json (2.6.3) 68 | minitest (5.18.0) 69 | molinillo (0.8.0) 70 | nanaimo (0.3.0) 71 | nap (1.1.0) 72 | netrc (0.11.0) 73 | public_suffix (4.0.7) 74 | rexml (3.2.5) 75 | ruby-macho (2.5.1) 76 | typhoeus (1.4.0) 77 | ethon (>= 0.9.0) 78 | tzinfo (2.0.6) 79 | concurrent-ruby (~> 1.0) 80 | xcodeproj (1.22.0) 81 | CFPropertyList (>= 2.3.3, < 4.0) 82 | atomos (~> 0.1.3) 83 | claide (>= 1.0.2, < 2.0) 84 | colored2 (~> 3.1) 85 | nanaimo (~> 0.3.0) 86 | rexml (~> 3.2.4) 87 | 88 | PLATFORMS 89 | x86_64-darwin-20 90 | 91 | DEPENDENCIES 92 | cocoapods 93 | 94 | BUNDLED WITH 95 | 2.2.17 96 | -------------------------------------------------------------------------------- /Image/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oneinc-jp/CompositionalLayoutViewController/22a2ed014f5a5b064ea82d639bed63e8a79d477c/Image/ss.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Akira 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "CompositionalLayoutViewController", 7 | platforms: [ 8 | .iOS(.v13) 9 | ], 10 | products: [ 11 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 12 | .library( 13 | name: "CompositionalLayoutViewController", 14 | targets: ["CompositionalLayoutViewController"] 15 | ) 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "CompositionalLayoutViewController", 26 | dependencies: [], 27 | path: "CompositionalLayoutViewController/Classes" 28 | ) 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CompositionalLayoutViewController 2 | 3 | [![CI Status](https://img.shields.io/travis/Akira/CompositionalLayoutViewController.svg?style=flat)](https://travis-ci.org/Akira/CompositionalLayoutViewController) 4 | [![Version](https://img.shields.io/cocoapods/v/CompositionalLayoutViewController.svg?style=flat)](https://cocoapods.org/pods/CompositionalLayoutViewController) 5 | [![License](https://img.shields.io/cocoapods/l/CompositionalLayoutViewController.svg?style=flat)](https://cocoapods.org/pods/CompositionalLayoutViewController) 6 | [![Platform](https://img.shields.io/cocoapods/p/CompositionalLayoutViewController.svg?style=flat)](https://cocoapods.org/pods/CompositionalLayoutViewController) 7 | 8 | ![](Image/ss.png) 9 | 10 | ## Example 11 | 12 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 13 | 14 | ## Requirements 15 | 16 | iOS 13.0+ 17 | 18 | ## Installation 19 | 20 | CompositionalLayoutViewController is available through [CocoaPods](https://cocoapods.org). To install 21 | it, simply add the following line to your Podfile: 22 | 23 | ```ruby 24 | pod 'CompositionalLayoutViewController' 25 | ``` 26 | 27 | ## Usage 28 | There are four steps to declare collection view. 29 | 30 | Firstly, to compose complex collection view sections, you implement `Section` class that inherits `CollectionViewSection`. 31 | ```swift 32 | class TextFormSection: CollectionViewSection { 33 | ... 34 | } 35 | ``` 36 | 37 | Secondly, implement your sections layout. 38 | ```swift 39 | func layoutSection(environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection 40 | ``` 41 | 42 | Thirdly, register cells and supplementary views for collection view in `func registerCell(collectionView: UICollectionView)` and `func registerSupplementaryView(collectionView: UICollectionView)`. 43 | ```swift 44 | func registerCell(collectionView: UICollectionView) { 45 | // register cells here 46 | } 47 | ``` 48 | 49 | ```swift 50 | func registerSupplementaryView(collectionView: UICollectionView) { 51 | // register supplementary views here 52 | } 53 | ``` 54 | Make sure that you implement `func supplementaryView(_ collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView?`. 55 | You may have to deque `UICollectionReusableView` from a collection view. 56 | 57 | These cells and supplementary views are configured following methods. 58 | ```swift 59 | func cell(_ collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell? 60 | ``` 61 | 62 | ```swift 63 | func configureSupplementaryView(_ view: UICollectionReusableView, indexPath: IndexPath) 64 | ``` 65 | 66 | You also have to declare `var snapshotItems: [AnyHashable]` in your section class. 67 | This variable contains datas for each cells. 68 | ```swift 69 | var snapshotItems: [AnyHashable] { 70 | // return AnyHashable items 71 | } 72 | ``` 73 | 74 | Finally, you can declare collection view sections in subclass of `CompositionalLayoutViewController` as follows: 75 | ```swift 76 | sections = [ 77 | TextFormSection( 78 | items: [ 79 | .init( 80 | initialText: nil, 81 | textForm: .init( 82 | placeholder: "Email", 83 | validationHandler: { text in 84 | guard let text = text else { 85 | return false 86 | } 87 | return text.isValidEmailAddress() 88 | }, 89 | validationAppearance: .init( 90 | textColor: .red 91 | ) 92 | ) 93 | ), 94 | .init( 95 | initialText: nil, 96 | textForm: .init( 97 | placeholder: "Password", 98 | isSecureTextEntry: true 99 | ) 100 | ) 101 | ] 102 | ), 103 | ButtonSection( 104 | buttonTitle: "Login", 105 | action: .handler({ 106 | print("Login button pressed") 107 | }) 108 | ) 109 | ] 110 | reloadSections() 111 | ``` 112 | 113 | And also don't forget to assign `SectionProvider` that manages an array of sections. 114 | ```swift 115 | public protocol SectionProvider: AnyObject { 116 | var sections: [CollectionViewSection] { get } 117 | func section(for sectionIndex: Int) -> CollectionViewSection 118 | } 119 | ``` 120 | ```swift 121 | override func viewDidLoad() { 122 | super.viewDidLoad() 123 | ... 124 | provider = // assign your provider e.g. presenter in VIPER 125 | ... 126 | } 127 | ``` 128 | 129 | To handle cell selection, override `func didSelectItem(at indexPath: IndexPath)` method in subclass of `CompositionalLayoutViewController`. 130 | 131 | See example code to lean advanced usage. 132 | 133 | ## Author 134 | 135 | Akira, akira.matsuda@me.com 136 | 137 | ## License 138 | 139 | CompositionalLayoutViewController is available under the MIT license. See the LICENSE file for more info. 140 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /run_swift_format.sh: -------------------------------------------------------------------------------- 1 | ./Example/Pods/SwiftFormat/CommandLineTool/swiftformat . --------------------------------------------------------------------------------