├── .swift-version ├── docs └── assets │ ├── pods.png │ ├── catalog.png │ └── tests.png ├── WORKSPACE ├── .jazzy.yaml ├── AUTHORS ├── .clang-format ├── .kokoro ├── example ├── Catalog.xcworkspace │ └── contents.xcworkspacedata ├── Podfile ├── components │ └── Resistor │ │ ├── Resistor.podspec │ │ ├── src │ │ ├── Resistor.h │ │ ├── RESSeriesResistor.h │ │ ├── RESParallelResistor.h │ │ ├── RESSeriesResistor.m │ │ └── RESParallelResistor.m │ │ ├── tests │ │ └── unit │ │ │ └── RESParallelResistorTests.m │ │ └── examples │ │ ├── ParallelResistorExample.m │ │ └── SeriesExample.m ├── CatalogExamples.podspec ├── CatalogUnitTests.podspec ├── catalog │ ├── UnitTests │ │ ├── Info.plist │ │ └── XcodeCrashFix7.2.1.m │ ├── Catalog │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Info.plist │ │ └── Base.lproj │ │ │ └── LaunchScreen.storyboard │ ├── UnitTests.xcodeproj │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── UnitTests.xcscheme │ │ └── project.pbxproj │ └── Catalog.xcodeproj │ │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Catalog.xcscheme │ │ └── project.pbxproj └── Podfile.lock ├── src ├── SnapshotByConvention │ ├── CBCEnvironment.h │ └── CBCEnvironment.m ├── CatalogByConvention.h ├── SwiftUI │ └── SwiftUIExampleWrapper.swift ├── private │ ├── CBCRuntime.h │ └── CBCRuntime.m ├── CBCCatalogExample.h ├── CBCNodeListViewController.h └── CBCNodeListViewController.m ├── CatalogByConvention.podspec ├── .travis.yml ├── CONTRIBUTING.md ├── .gitignore ├── BUILD ├── README.md ├── CHANGELOG.md └── LICENSE /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /docs/assets/pods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-foundation/cocoapods-catalog-by-convention/HEAD/docs/assets/pods.png -------------------------------------------------------------------------------- /docs/assets/catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-foundation/cocoapods-catalog-by-convention/HEAD/docs/assets/catalog.png -------------------------------------------------------------------------------- /docs/assets/tests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-foundation/cocoapods-catalog-by-convention/HEAD/docs/assets/tests.png -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | git_repository( 2 | name = "build_bazel_rules_apple", 3 | remote = "https://github.com/bazelbuild/rules_apple.git", 4 | commit = "7ea0557", 5 | ) 6 | -------------------------------------------------------------------------------- /.jazzy.yaml: -------------------------------------------------------------------------------- 1 | module: CatalogByConvention 2 | module_version: 1.0.0 3 | umbrella_header: src/CatalogByConvention.h 4 | objc: true 5 | sdk: iphonesimulator 6 | github_url: https://github.com/material-foundation/cocoapods-catalog-by-convention 7 | github_file_prefix: https://github.com/material-foundation/cocoapods-catalog-by-convention/tree/v2.0.0 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of CocoaPods Catalog by Convention authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history with git log. 6 | 7 | Google Inc. 8 | and other contributors 9 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | 3 | AllowShortFunctionsOnASingleLine: Inline 4 | AllowShortIfStatementsOnASingleLine: false 5 | AllowShortLoopsOnASingleLine: false 6 | AlwaysBreakBeforeMultilineStrings: false 7 | BinPackParameters: false 8 | ColumnLimit: 100 9 | IndentWrappedFunctionNames: true 10 | ObjCSpaceBeforeProtocolList: true 11 | PointerBindsToType: false 12 | SortIncludes: true 13 | -------------------------------------------------------------------------------- /.kokoro: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fail on any error. 4 | set -e 5 | 6 | if [ ! -d .kokoro-ios-runner ]; then 7 | git clone https://github.com/material-foundation/kokoro-ios-runner.git .kokoro-ios-runner 8 | fi 9 | 10 | pushd .kokoro-ios-runner 11 | git fetch > /dev/null 12 | git checkout v2.1.1 > /dev/null 13 | popd 14 | 15 | ./.kokoro-ios-runner/bazel.sh //:CatalogByConvention 16 | 17 | echo "Success!" 18 | -------------------------------------------------------------------------------- /example/Catalog.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/Podfile: -------------------------------------------------------------------------------- 1 | abstract_target 'Catalog' do 2 | pod 'CatalogByConvention', :path => '../' 3 | workspace 'Catalog.xcworkspace' 4 | use_frameworks! 5 | 6 | pod 'Resistor', :path => 'components/Resistor' 7 | 8 | target "Catalog" do 9 | project 'catalog/Catalog.xcodeproj' 10 | 11 | # Conventions 12 | pod 'CatalogExamples', :path => './' 13 | end 14 | 15 | target "UnitTests" do 16 | project 'catalog/UnitTests.xcodeproj' 17 | 18 | # Conventions 19 | pod 'CatalogUnitTests', :path => './' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/SnapshotByConvention/CBCEnvironment.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /** Attempts to reduce flakiness by disabling certain features known to cause flakiness. */ 4 | FOUNDATION_EXTERN void CBCReduceFlakiness(void); 5 | 6 | /** Enables Bold Text mode as though it had been toggled by Xcode's accessibility overrides. */ 7 | FOUNDATION_EXTERN void CBCEnableBoldTextMode(void) API_AVAILABLE(ios(13)); 8 | 9 | /** Set dynamic type by a specific @c UIContentSizeCategory value. */ 10 | FOUNDATION_EXTERN void CBCSetDynamicType(UIContentSizeCategory sizeCategory) API_AVAILABLE(ios(13)); 11 | -------------------------------------------------------------------------------- /example/components/Resistor/Resistor.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Resistor" 3 | s.version = "1.0.0" 4 | s.summary = "An example component." 5 | s.homepage = "https://github.com/google/catalog-by-convention" 6 | s.authors = "Google Inc." 7 | s.license = 'Apache 2.0' 8 | s.source = { :git => "https://github.com/google/catalog-by-convention.git", :tag => "v#{s.version}" } 9 | s.requires_arc = true 10 | 11 | s.public_header_files = "src/*.h" 12 | s.source_files = "src/*.{h,m,swift}" 13 | s.header_mappings_dir = "src/*" 14 | end 15 | -------------------------------------------------------------------------------- /example/CatalogExamples.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CatalogExamples" 3 | s.version = "1.0.0" 4 | s.summary = "Convention for catalog examples." 5 | s.homepage = "https://github.com/material-foundation/cocoapods-catalog-by-convention" 6 | s.authors = "Google Inc." 7 | s.license = 'Apache 2.0' 8 | s.source = { :git => "https://github.com/material-foundation/cocoapods-catalog-by-convention.git", :tag => "v#{s.version}" } 9 | s.requires_arc = true 10 | 11 | # Conventions 12 | s.source_files = 'components/*/examples/*.{h,m,swift}' 13 | s.public_header_files = 'components/*/examples/*.h' 14 | s.resources = ['components/*/examples/resources/*'] 15 | end 16 | -------------------------------------------------------------------------------- /src/CatalogByConvention.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "CBCNodeListViewController.h" 18 | -------------------------------------------------------------------------------- /example/components/Resistor/src/Resistor.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "RESParallelResistor.h" 18 | #import "RESSeriesResistor.h" 19 | -------------------------------------------------------------------------------- /example/CatalogUnitTests.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CatalogUnitTests" 3 | s.version = "1.0.0" 4 | s.summary = "Convention specification for the catalog examples." 5 | s.homepage = "https://github.com/material-foundation/cocoapods-catalog-by-convention" 6 | s.authors = "Google Inc." 7 | s.license = 'Apache 2.0' 8 | s.source = { :git => "https://github.com/material-foundation/cocoapods-catalog-by-convention.git", :tag => "v#{s.version}" } 9 | s.requires_arc = true 10 | 11 | # The conventions 12 | s.source_files = 'components/*/tests/unit/*.{h,m,swift}' 13 | s.resources = ['components/*/tests/unit/resources/*'] 14 | s.framework = 'XCTest' 15 | 16 | # Component dependencies 17 | s.dependency 'Resistor' 18 | end 19 | -------------------------------------------------------------------------------- /CatalogByConvention.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CatalogByConvention" 3 | s.version = "2.5.2" 4 | s.authors = "Google Inc." 5 | s.summary = "Tools for building a Catalog by Convention." 6 | s.homepage = "https://github.com/material-foundation/cocoapods-catalog-by-convention" 7 | s.license = 'Apache 2.0' 8 | s.source = { :git => "https://github.com/material-foundation/cocoapods-catalog-by-convention.git", :tag => "v#{s.version}" } 9 | s.platform = :ios,:tvos 10 | s.ios.deployment_target = '8.0' 11 | s.tvos.deployment_target = '9.0' 12 | s.requires_arc = true 13 | 14 | s.public_header_files = "src/*.h" 15 | s.source_files = "src/*.{h,m,swift}", "src/private/*.{h,m,swift}" 16 | s.header_mappings_dir = "src" 17 | end 18 | -------------------------------------------------------------------------------- /example/components/Resistor/src/RESSeriesResistor.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import 18 | 19 | @interface RESSeriesResistor : NSObject 20 | @end 21 | -------------------------------------------------------------------------------- /example/components/Resistor/src/RESParallelResistor.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import 18 | 19 | @interface RESParallelResistor : NSObject 20 | @end 21 | -------------------------------------------------------------------------------- /example/catalog/UnitTests/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/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CatalogByConvention (2.3.1) 3 | - CatalogExamples (1.0.0) 4 | - CatalogUnitTests (1.0.0): 5 | - Resistor 6 | - Resistor (1.0.0) 7 | 8 | DEPENDENCIES: 9 | - CatalogByConvention (from `../`) 10 | - CatalogExamples (from `./`) 11 | - CatalogUnitTests (from `./`) 12 | - Resistor (from `components/Resistor`) 13 | 14 | EXTERNAL SOURCES: 15 | CatalogByConvention: 16 | :path: ../ 17 | CatalogExamples: 18 | :path: ./ 19 | CatalogUnitTests: 20 | :path: ./ 21 | Resistor: 22 | :path: components/Resistor 23 | 24 | SPEC CHECKSUMS: 25 | CatalogByConvention: f4b95f8905470807a5022eabd1d3d9ce07f6a66f 26 | CatalogExamples: 7a95e6ea7befbd43c5ceb1427a9b161f6d8fc34e 27 | CatalogUnitTests: 2fbf7f2e894dd3777f11573a7a5314adb1e4fad7 28 | Resistor: a17e39cab5f42993c2b3ede22ce3829b707a9ac8 29 | 30 | PODFILE CHECKSUM: bb59c09c71f8777bbe79af5ae920e3d58849ab41 31 | 32 | COCOAPODS: 1.4.0 33 | -------------------------------------------------------------------------------- /example/components/Resistor/src/RESSeriesResistor.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "RESSeriesResistor.h" 18 | 19 | @implementation RESSeriesResistor 20 | 21 | - (instancetype)init { 22 | self = [super init]; 23 | if (self) { 24 | NSLog(@"Initialized series"); 25 | } 26 | return self; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /example/components/Resistor/src/RESParallelResistor.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "RESParallelResistor.h" 18 | 19 | @implementation RESParallelResistor 20 | 21 | - (instancetype)init { 22 | self = [super init]; 23 | if (self) { 24 | NSLog(@"Initialized parallel"); 25 | } 26 | return self; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8.1 3 | sudo: false 4 | notifications: 5 | email: false 6 | env: 7 | global: 8 | - LC_CTYPE=en_US.UTF-8 9 | - LANG=en_US.UTF-8 10 | matrix: 11 | - DESTINATION="OS=9.3,name=iPhone 6s Plus" 12 | - DESTINATION="OS=9.2,name=iPhone 6 Plus" 13 | - DESTINATION="OS=9.1,name=iPhone 6s" 14 | - DESTINATION="OS=9.0,name=iPhone 6 Plus" 15 | - DESTINATION="OS=8.4,name=iPhone 6" 16 | - DESTINATION="OS=8.3,name=iPhone 5S" 17 | - DESTINATION="OS=8.2,name=iPhone 5" 18 | - DESTINATION="OS=8.1,name=iPhone 4s" 19 | before_install: 20 | - gem install cocoapods --no-rdoc --no-ri --no-document --quiet 21 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 22 | - pod install --project-directory=example/ 23 | script: 24 | - set -o pipefail 25 | - xcodebuild -workspace example/Catalog.xcworkspace -scheme Catalog -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c; 26 | -------------------------------------------------------------------------------- /example/components/Resistor/tests/unit/RESParallelResistorTests.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import 18 | #import "Resistor.h" 19 | 20 | @interface RESParallelResitorTests : XCTestCase 21 | @end 22 | 23 | @implementation RESParallelResitorTests 24 | 25 | - (void)testCreatingResistor { 26 | RESParallelResistor *resistor = [RESParallelResistor new]; 27 | XCTAssertNotNil(resistor); 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /example/catalog/UnitTests/XcodeCrashFix7.2.1.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // 18 | // This file is here solely to keep the simulator from crashing on Xcode Version 7.2.1 (7C1002). 19 | // Without this file, the following error occurs when attempting to launch the tests: 20 | // 21 | // Test target encountered an error (Early unexpected exit, operation never finished 22 | // bootstrapping - no restart will be attempted) 23 | // 24 | -------------------------------------------------------------------------------- /src/SwiftUI/SwiftUIExampleWrapper.swift: -------------------------------------------------------------------------------- 1 | #if canImport(SwiftUI) 2 | import SwiftUI 3 | import UIKit 4 | 5 | /// A view controller to host SwiftUI views wrapped by a UIHostingControllers. 6 | /// Sample use, where MySwiftUIExample is a SwiftUI View: 7 | /// 8 | /// class MySwiftUIExampleWrapper: SwiftUIExampleWrapper { 9 | /// override func viewDidLoad() { 10 | /// super.viewDidLoad() 11 | /// addChildHostingController(UIHostingController(rootView: MySwiftUIExample())) 12 | /// } 13 | /// } 14 | open class SwiftUIExampleWrapper: UIViewController { 15 | public func addChildHostingController(_ swiftUIHostingController: UIViewController) { 16 | swiftUIHostingController.view.translatesAutoresizingMaskIntoConstraints = false 17 | addChild(swiftUIHostingController) 18 | view.addSubview(swiftUIHostingController.view) 19 | swiftUIHostingController.didMove(toParent: self) 20 | 21 | swiftUIHostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = 22 | true 23 | swiftUIHostingController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 24 | swiftUIHostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = 25 | true 26 | swiftUIHostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = 27 | true 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /example/catalog/Catalog/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /example/catalog/Catalog/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import CatalogByConvention 18 | import UIKit 19 | 20 | @main 21 | class AppDelegate: UIResponder, UIApplicationDelegate { 22 | 23 | var window: UIWindow? 24 | 25 | func application( 26 | _ application: UIApplication, 27 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 28 | ) -> Bool { 29 | self.window = UIWindow(frame: UIScreen.main.bounds) 30 | 31 | let rootViewController = CBCNodeListViewController(node: CBCCreateNavigationTree()) 32 | rootViewController.title = "Catalog by Convention" 33 | 34 | let navController = UINavigationController(rootViewController: rootViewController) 35 | self.window?.rootViewController = navController 36 | 37 | self.window!.makeKeyAndVisible() 38 | return true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/components/Resistor/examples/ParallelResistorExample.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "Resistor.h" 18 | 19 | #import 20 | 21 | @interface ParallelResistorExample : UIViewController 22 | @end 23 | 24 | @implementation ParallelResistorExample 25 | 26 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 27 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 28 | if (self) { 29 | self.title = @"Parallel resistors"; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | 37 | self.view.backgroundColor = [UIColor whiteColor]; 38 | } 39 | 40 | @end 41 | 42 | @implementation ParallelResistorExample (CatalogByConvention) 43 | 44 | + (NSArray *)catalogBreadcrumbs { 45 | return @[ @"Resistor", @"Parallel" ]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /example/components/Resistor/examples/SeriesExample.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "Resistor.h" 18 | 19 | #import 20 | 21 | @interface SeriesExample : UIViewController 22 | @end 23 | 24 | @implementation SeriesExample 25 | 26 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 27 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 28 | if (self) { 29 | self.title = @"Series"; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | 37 | self.view.backgroundColor = [UIColor whiteColor]; 38 | } 39 | 40 | @end 41 | 42 | @implementation SeriesExample (CatalogByConvention) 43 | 44 | + (NSArray *> *)catalogBreadcrumbs { 45 | return @[ @[ @"Resistor", @"Series"], @[ @"Film", @"Series" ], @[@"Botany", @"Series"] ]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | 6 | Before we can use your code, you must sign the 7 | [Google Individual Contributor License Agreement] 8 | (https://cla.developers.google.com/about/google-individual) 9 | (CLA), which you can do online. The CLA is necessary mainly because you own the 10 | copyright to your changes, even after your contribution becomes part of our 11 | codebase, so we need your permission to use and distribute your code. We also 12 | need to be sure of various other things—for instance that you'll tell us if you 13 | know that your code infringes on other people's patents. You don't have to sign 14 | the CLA until after you've submitted your code for review and a member has 15 | approved it, but you must do it before we can put your code into our codebase. 16 | Before you start working on a larger contribution, you should get in touch with 17 | us first through the issue tracker with your idea so that we can help out and 18 | possibly guide you. Coordinating up front makes it much easier to avoid 19 | frustration later on. 20 | 21 | ### Code reviews 22 | 23 | All submissions, including submissions by project members, require review. 24 | We use GitHub pull requests for this purpose. 25 | 26 | ### The small print 27 | 28 | Contributions made by corporations are covered by a different agreement than 29 | the one above, the 30 | [Software Grant and Corporate Contributor License Agreement] 31 | (https://cla.developers.google.com/about/google-corporate). 32 | -------------------------------------------------------------------------------- /example/catalog/Catalog/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 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | bazel-* 3 | .kokoro-ios-runner 4 | 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xcuserstate 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | # CocoaPods 35 | # 36 | # We recommend against adding the Pods directory to your .gitignore. However 37 | # you should judge for yourself, the pros and cons are mentioned at: 38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 39 | # 40 | Pods/ 41 | 42 | # Carthage 43 | # 44 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 45 | # Carthage/Checkouts 46 | 47 | Carthage/Build 48 | 49 | # fastlane 50 | # 51 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 52 | # screenshots whenever they are needed. 53 | # For more information about the recommended setup visit: 54 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 55 | 56 | fastlane/report.xml 57 | fastlane/screenshots 58 | 59 | #Code Injection 60 | # 61 | # After new code Injection tools there's a generated folder /iOSInjectionProject 62 | # https://github.com/johnno1962/injectionforxcode 63 | 64 | iOSInjectionProject/ 65 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | # Description: 2 | # Tools for building a Catalog by Convention. 3 | 4 | licenses(["notice"]) # Apache 2.0 5 | 6 | exports_files(["LICENSE"]) 7 | 8 | objc_library( 9 | name = "CatalogByConvention", 10 | srcs = glob([ 11 | "src/*.m", 12 | "src/private/*.m", 13 | ]), 14 | hdrs = glob([ 15 | "src/*.h", 16 | "src/private/*.h", 17 | ]), 18 | includes = ["src"], 19 | visibility = ["//visibility:public"], 20 | copts = [ 21 | "-Wall", # Standard known-to-be-bugs warnings. 22 | "-Wcast-align", # Casting a pointer such that alignment is broken. 23 | "-Wconversion", # Numeric conversion warnings. 24 | "-Wdocumentation", # Documentation checks. 25 | "-Werror", # All warnings as errors. 26 | "-Wextra", # Many useful extra warnings. 27 | "-Wimplicit-atomic-properties", # Dynamic properties should be non-atomic. 28 | "-Wmissing-prototypes", # Global function is defined without a previous prototype. 29 | "-Wno-error=deprecated", # Deprecation warnings are never errors. 30 | "-Wno-error=deprecated-implementations", # Deprecation warnings are never errors. 31 | "-Wno-sign-conversion", # Do not warn on sign conversions. 32 | "-Wno-unused-parameter", # Do not warn on unused parameters. 33 | "-Woverlength-strings", # Strings longer than the C maximum. 34 | "-Wshadow", # Local variable shadows another variable, parameter, etc. 35 | "-Wstrict-selector-match", # Compiler can't figure out the right selector. 36 | "-Wundeclared-selector", # Compiler doesn't see a selector. 37 | "-Wunreachable-code", # Code will never be reached. 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /example/catalog/Catalog/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/catalog/UnitTests.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/private/CBCRuntime.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | #pragma mark Class invocations 21 | 22 | /** Invokes +catalogMetadata on the class and returns the NSDictionary value */ 23 | FOUNDATION_EXTERN NSDictionary *CBCCatalogMetadataFromClass(Class aClass); 24 | 25 | /** 26 | Returns NO only if the example implements +minimumOSVersion and returns a version that is less than 27 | the current process's operating system version. 28 | */ 29 | FOUNDATION_EXTERN BOOL CBCCanRunClassOnCurrentOperatingSystem(Class aClass); 30 | 31 | #pragma mark Runtime enumeration 32 | 33 | /** Returns all Objective-C and Swift classes available to the runtime. */ 34 | FOUNDATION_EXTERN NSArray *CBCGetAllCompatibleClasses(void); 35 | 36 | /** Returns an array of classes that respond to a given static method selector. */ 37 | FOUNDATION_EXTERN NSArray *CBCClassesRespondingToSelector(NSArray *classes, 38 | SEL selector); 39 | 40 | /** 41 | Internal helper method that allows invoking aClass with selector and puts 42 | the return value in retValue. 43 | */ 44 | void CBCCatalogInvokeFromClassAndSelector(Class aClass, SEL selector, void *retValue); 45 | 46 | #pragma mark UIViewController instantiation 47 | 48 | /** 49 | Creates a view controller instance from the provided class. 50 | 51 | If the provided class implements +(NSString *)catalogStoryboardName, a UIStoryboard instance will 52 | be created with the returned name. The returned view controller will be instantiated by invoking 53 | -instantiateInitialViewController on the UIStoryboard instance. 54 | */ 55 | FOUNDATION_EXTERN UIViewController *CBCViewControllerFromClass(Class aClass, NSDictionary *metadata); 56 | 57 | #pragma mark Fix View Debugging 58 | 59 | /** 60 | Fixes View Debugging in Xcode when running on iOS 8 and below. See 61 | http://stackoverflow.com/questions/36313850/debug-view-hierarchy-in-xcode-7-3-fails 62 | */ 63 | FOUNDATION_EXTERN void CBCFixViewDebuggingIfNeeded(void); 64 | -------------------------------------------------------------------------------- /src/CBCCatalogExample.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import 18 | 19 | /** 20 | The CBCCatalogExample protocol defines methods that examples are expected to implement in order to 21 | customize their location and behavior in the Catalog by Convention. 22 | 23 | Examples should not formally conform to this protocol. Examples should simply implement these 24 | methods by convention. 25 | */ 26 | @protocol CBCCatalogExample 27 | 28 | /** 29 | Returns a dictionary with metaata information for the example. 30 | */ 31 | + (nonnull NSDictionary *)catalogMetadata; 32 | 33 | @optional 34 | 35 | /** Return a list of breadcrumbs defining the navigation path taken to reach this example. */ 36 | + (nonnull NSArray *)catalogBreadcrumbs 37 | __attribute__((deprecated("use catalogMetadata[CBCBreadcrumbs] instead."))); 38 | 39 | /** 40 | Return a BOOL stating whether this example should be treated as the primary demo of the component. 41 | */ 42 | + (BOOL)catalogIsPrimaryDemo 43 | __attribute__((deprecated("use catalogMetadata[CBCIsPrimaryDemo] instead.")));; 44 | 45 | /** 46 | Return a BOOL stating whether this example is presentable and should be part of the catalog app. 47 | */ 48 | + (BOOL)catalogIsPresentable 49 | __attribute__((deprecated("use catalogMetadata[CBCIsPresentable] instead."))); 50 | 51 | /** 52 | Return a BOOL stating whether this example is in debug mode and should appear as the initial view controller. 53 | */ 54 | + (BOOL)catalogIsDebug 55 | __attribute__((deprecated("use catalogMetadata[CBCIsDebug] instead."))); 56 | 57 | /** 58 | Return the name of a UIStoryboard from which the example's view controller should be instantiated. 59 | */ 60 | - (nonnull NSString *)catalogStoryboardName 61 | __attribute__((deprecated("use catalogMetadata[CBCStoryboardName] instead."))); 62 | 63 | /** 64 | Return the minimum OS version that this example supports being ran on. 65 | 66 | If this method is not implemented, then it's assumed that the example can run on any OS version 67 | lower than the `deprecatedOSVersion`, if specified. 68 | */ 69 | - (NSOperatingSystemVersion)minimumOSVersion; 70 | 71 | /** 72 | Return the OS version that this example is deprecated at. 73 | 74 | If this method is not implemented, then it's assumed that the example can run on any OS version 75 | larger than or equal to the `minimumOSVersion`, if specified. 76 | */ 77 | - (NSOperatingSystemVersion)deprecatedOSVersion; 78 | 79 | /** Return a description of the example. */ 80 | - (nonnull NSString *)catalogDescription 81 | __attribute__((deprecated("use catalogMetadata[CBCDescription] instead."))); 82 | 83 | /** Return a link to related information or resources. */ 84 | - (nonnull NSURL *)catalogRelatedInfo 85 | __attribute__((deprecated("use catalogMetadata[CBCRelatedInfo] instead."))); 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /example/catalog/Catalog.xcodeproj/xcshareddata/xcschemes/Catalog.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/CBCNodeListViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import 18 | 19 | /** This key represents a strings array of the breadcrumbs showing the hierarchy of the example */ 20 | FOUNDATION_EXTERN NSString *_Nonnull const CBCBreadcrumbs; 21 | /** This key represents a boolean value if the example is for debugging */ 22 | FOUNDATION_EXTERN NSString *_Nonnull const CBCIsDebug; 23 | /** This key represents a string for the description for the example */ 24 | FOUNDATION_EXTERN NSString *_Nonnull const CBCDescription; 25 | /** This key represents a boolean value if to present the example in the Catalog app or not */ 26 | FOUNDATION_EXTERN NSString *_Nonnull const CBCIsPresentable; 27 | /** This key represents a boolean value if the example is the primary demo */ 28 | FOUNDATION_EXTERN NSString *_Nonnull const CBCIsPrimaryDemo; 29 | /** This key represents an NSURL value providing related info for the example */ 30 | FOUNDATION_EXTERN NSString *_Nonnull const CBCRelatedInfo; 31 | /** This key represents a string value of the storyboard name for the example */ 32 | FOUNDATION_EXTERN NSString *_Nonnull const CBCStoryboardName; 33 | /** This key represents an NSArray of strings for the keywords for the example */ 34 | FOUNDATION_EXTERN NSString *_Nonnull const CBCKeywords; 35 | 36 | @class CBCNode; 37 | 38 | /** 39 | An instance of CBCNodeListViewController is able to represent a non-example CBCNode instance as a 40 | UITableView. 41 | */ 42 | @interface CBCNodeListViewController : UIViewController 43 | 44 | /** Initializes a CBCNodeViewController instance with a non-example node. */ 45 | - (nonnull instancetype)initWithNode:(nonnull CBCNode *)node; 46 | 47 | - (nonnull instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE; 48 | 49 | @property(nonatomic, strong, nonnull) UITableView *tableView; 50 | 51 | /** The node that this view controller must represent. */ 52 | @property(nonatomic, strong, nonnull, readonly) CBCNode *node; 53 | 54 | /** The preferred mechanism for fetching a node for a given index path. Supports grouping. */ 55 | - (nonnull CBCNode *)nodeForIndexPath:(nonnull NSIndexPath *)indexPath; 56 | 57 | @property(nonatomic) BOOL searchEnabled; 58 | 59 | - (void)updateFilters:(nonnull NSString *)filter enabled:(BOOL)enabled; 60 | 61 | - (void)findSearchText:(nonnull NSString *)searchtext; 62 | 63 | @end 64 | 65 | /** 66 | Returns the root of a CBCNode tree representing the complete catalog navigation hierarchy. 67 | 68 | Only classes that implement +catalogBreadcrumbs and return at least one breadcrumb will be part of 69 | the tree. 70 | */ 71 | FOUNDATION_EXTERN CBCNode *_Nonnull CBCCreateNavigationTree(void); 72 | 73 | /** 74 | Returns the root of a CBCNode tree representing only the presentable catalog navigation hierarchy. 75 | 76 | Only classes that implement +catalogIsPresentable with a return value of YES, 77 | and +catalogBreadcrumbs and return at least one breadcrumb will be part of the tree. 78 | */ 79 | FOUNDATION_EXTERN CBCNode *_Nonnull CBCCreatePresentableNavigationTree(void); 80 | 81 | /** 82 | A node describes a single navigable page in the Catalog by Convention. 83 | 84 | A node either has children or it is an example. 85 | 86 | - If a node has children, then the node should be represented by a list of some sort. 87 | - If a node is an example, then the example controller can be instantiated with 88 | createExampleViewController. 89 | */ 90 | @interface CBCNode : NSObject 91 | 92 | /** Nodes cannot be created by clients. */ 93 | - (nonnull instancetype)init NS_UNAVAILABLE; 94 | 95 | /** The title for this node. */ 96 | @property(nonatomic, copy, nonnull, readonly) NSString *title; 97 | 98 | /** The children of this node. */ 99 | @property(nonatomic, strong, nonnull) NSArray *children; 100 | 101 | /** 102 | The example you wish to debug as the initial view controller. 103 | If there are multiple examples with catalogIsDebug returning YES 104 | the debugLeaf will hold the example that has been iterated on last 105 | in the hierarchy tree. 106 | */ 107 | @property(nonatomic, strong, nullable) CBCNode *debugLeaf; 108 | 109 | /** 110 | This NSDictionary holds all the metadata related to this CBCNode. 111 | If it is an example noe, a primary demo, related info, 112 | if presentable in Catalog, etc. 113 | */ 114 | @property(nonatomic, strong, nonnull) NSDictionary *metadata; 115 | 116 | /** The group of this node. */ 117 | @property(nonatomic, strong, nullable) NSString *group; 118 | 119 | /** Returns YES if this is an example node. */ 120 | - (BOOL)isExample; 121 | 122 | /** 123 | Returns YES if this the primary demo for this component. 124 | 125 | Can only return YES if isExample also returns YES. 126 | */ 127 | - (BOOL)isPrimaryDemo; 128 | 129 | /** Returns YES if this is a presentable example. */ 130 | - (BOOL)isPresentable; 131 | 132 | /** Returns String representation of exampleViewController class name if it exists */ 133 | - (nullable NSString *)exampleViewControllerName; 134 | 135 | /** 136 | Returns an instance of a UIViewController for presentation purposes. 137 | 138 | Check that isExample returns YES before invoking. 139 | */ 140 | - (nonnull UIViewController *)createExampleViewController; 141 | 142 | /** 143 | Returns a description of the example. 144 | 145 | Check that isExample returns YES before invoking. 146 | */ 147 | - (nullable NSString *)exampleDescription; 148 | 149 | /** Returns a link to related information for the example. */ 150 | - (nullable NSURL *)exampleRelatedInfo; 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /src/SnapshotByConvention/CBCEnvironment.m: -------------------------------------------------------------------------------- 1 | #import "CBCEnvironment.h" 2 | 3 | #import 4 | #import 5 | 6 | static BOOL IsSubviewOfUIAlertControllerView(UIView *view) { 7 | static dispatch_once_t onceToken; 8 | static Class UIAlertControllerViewClass = nil; 9 | dispatch_once(&onceToken, ^{ 10 | UIAlertControllerViewClass = NSClassFromString(@"_UIAlertControllerView"); 11 | }); 12 | 13 | UIView *iterator = view; 14 | while (iterator) { 15 | if ([iterator isMemberOfClass:UIAlertControllerViewClass]) { 16 | return YES; 17 | } 18 | iterator = iterator.superview; 19 | } 20 | return NO; 21 | } 22 | 23 | @implementation UIVisualEffectView (CatalogByConventionFlakinessReduction) 24 | 25 | - (void)cbc_setEffect:(UIVisualEffect *)effect { 26 | if (@available(iOS 14, *)) { 27 | // Never allow effects to be set, and if they are set, pin the background color so that the 28 | // view isn't transparent. 29 | [self cbc_setEffect:nil]; 30 | self.backgroundColor = [UIColor systemBackgroundColor]; 31 | } else { 32 | [self cbc_setEffect:effect]; 33 | } 34 | } 35 | 36 | - (void)cbc_addSubview:(UIView *)view { 37 | if (@available(iOS 14, *)) { 38 | static dispatch_once_t onceToken; 39 | static Class UIVisualEffectBackdropViewClass = nil; 40 | dispatch_once(&onceToken, ^{ 41 | UIVisualEffectBackdropViewClass = NSClassFromString(@"_UIVisualEffectBackdropView"); 42 | }); 43 | 44 | if ([view isMemberOfClass:UIVisualEffectBackdropViewClass]) { 45 | // Ignore adding this view. Instead, pin the background of the UIVisualEffectView to a 46 | // solid color. 47 | self.backgroundColor = [UIColor systemBackgroundColor]; 48 | return; 49 | } 50 | } 51 | 52 | // Call the pre-swizzled implementation in the general case. 53 | [self cbc_addSubview:view]; 54 | } 55 | 56 | @end 57 | 58 | // Technically an extension for _UIDimmingKnockoutBackdropView, but it's a private API so we extend 59 | // UIView instead. 60 | @implementation UIView (CatalogByConventionFlakinessReduction) 61 | 62 | - (void)cbc_setUIDimmingKnockoutBackdropViewCornerRadius:(CGFloat)cornerRadius { 63 | if (@available(iOS 14, *)) { 64 | // To avoid over-disabling corner radii, we only ignore the corner radius if we're a subview of 65 | // a _UIAlertControllerView. 66 | if (IsSubviewOfUIAlertControllerView(self)) { 67 | [self cbc_setUIDimmingKnockoutBackdropViewCornerRadius:0]; 68 | return; 69 | } 70 | } 71 | 72 | // Call the pre-swizzled implementation in the general case. 73 | [self cbc_setUIDimmingKnockoutBackdropViewCornerRadius:cornerRadius]; 74 | } 75 | 76 | @end 77 | 78 | @implementation CALayer (CatalogByConventionFlakinessReduction) 79 | 80 | - (void)cbc_setCornerRadius:(CGFloat)cornerRadius { 81 | if (@available(iOS 14, *)) { 82 | static dispatch_once_t onceToken; 83 | static Class UIDropShadowViewClass = nil; 84 | dispatch_once(&onceToken, ^{ 85 | UIDropShadowViewClass = NSClassFromString(@"UIDropShadowView"); 86 | }); 87 | 88 | // Disable rounded corners in modally presented view controllers. The rounded corner behavior 89 | // is governed by the subviews of UIDropShadowView, so we check to see if this layer's view is a 90 | // subview of a UIDropShadowView. We're able to get the layer's view by looking at the delegate, 91 | // which will always be set to the corresponding UIView for layer-backed UIViews. 92 | if ([self.delegate isKindOfClass:[UIView class]]) { 93 | UIView *view = (UIView *)self.delegate; 94 | if ([view.superview isMemberOfClass:UIDropShadowViewClass]) { 95 | [self cbc_setCornerRadius:0]; 96 | return; 97 | } 98 | } 99 | } 100 | 101 | // Call the pre-swizzled implementation in the general case. 102 | [self cbc_setCornerRadius:cornerRadius]; 103 | } 104 | 105 | @end 106 | 107 | // General purpose instance method swizzler. 108 | static void Swizzle(Class aClass, SEL originalSelector, SEL swizzledSelector) { 109 | Method originalMethod = class_getInstanceMethod(aClass, originalSelector); 110 | Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector); 111 | 112 | BOOL didAddMethod = 113 | class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), 114 | method_getTypeEncoding(swizzledMethod)); 115 | if (didAddMethod) { 116 | class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), 117 | method_getTypeEncoding(originalMethod)); 118 | } else { 119 | method_exchangeImplementations(originalMethod, swizzledMethod); 120 | } 121 | } 122 | 123 | static void DisableBlurEffects(void) { 124 | Swizzle([UIVisualEffectView class], @selector(setEffect:), @selector(cbc_setEffect:)); 125 | Swizzle([UIVisualEffectView class], @selector(addSubview:), @selector(cbc_addSubview:)); 126 | } 127 | 128 | static void DisableRoundedAlerts(void) { 129 | Swizzle(NSClassFromString(@"_UIDimmingKnockoutBackdropView"), @selector(setCornerRadius:), 130 | @selector(cbc_setUIDimmingKnockoutBackdropViewCornerRadius:)); 131 | } 132 | 133 | static void DisableRoundedModalViewControllers(void) { 134 | // We need to swizzle CALayer's setCornerRadius because UIKit's private APIs will repeatedly 135 | // attempt to enforce the corner radius of modal view controllers at various stages of 136 | // presentation, and we want to ensure that the radius is never set. 137 | Swizzle([CALayer class], @selector(setCornerRadius:), @selector(cbc_setCornerRadius:)); 138 | } 139 | 140 | #pragma mark - Public APIs 141 | 142 | void CBCReduceFlakiness(void) { 143 | static dispatch_once_t onceToken; 144 | dispatch_once(&onceToken, ^{ 145 | if (@available(iOS 14, *)) { 146 | // On iOS 14, blur effects result in an almost 50/50 split in rendering behaviors when taking 147 | // snapshots, so we disable them entirely. 148 | DisableBlurEffects(); 149 | 150 | // On iOS 14, rounded corners on alerts cause flakiness about 50% of the time as well. 151 | DisableRoundedAlerts(); 152 | DisableRoundedModalViewControllers(); 153 | } 154 | }); 155 | } 156 | 157 | @protocol AccessibilitySupportOverrides 158 | + (instancetype)shared; 159 | - (instancetype)initWithContentSizeCategory:(UIContentSizeCategory)value; 160 | - (void)setBoldText:(NSNumber *)value; 161 | - (void)overrideSystemWithPreference:(id)value; 162 | @end 163 | 164 | static void SetBoldText(BOOL enabled) { 165 | [[objc_getClass("AccessibilitySupportOverrides") shared] setBoldText:@(enabled)]; 166 | } 167 | 168 | void CBCEnableBoldTextMode(void) { 169 | SetBoldText(YES); 170 | } 171 | 172 | void CBCSetDynamicType(UIContentSizeCategory sizeCategory) { 173 | [objc_getClass("UIContentSizeCategoryPreference") overrideSystemWithPreference:[[objc_getClass("UIContentSizeCategoryPreference") alloc] initWithContentSizeCategory:sizeCategory]]; 174 | } 175 | 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Catalog by Convention 2 | 3 | [![Build Status](https://travis-ci.org/material-foundation/cocoapods-catalog-by-convention.svg?branch=develop)](https://travis-ci.org/material-foundation/cocoapods-catalog-by-convention) 4 | 5 | Catalog by Convention is a set of runtime tools and conventions designed for the development of a 6 | collection of Objective-C and Swift "components". 7 | 8 | ## Overview 9 | 10 | If your team works on many components simultaneously then you may find that building examples and 11 | unit tests involves some non-trivial overhead: 12 | 13 | - Maintenance of each component's Xcode projects. 14 | - Maintenance of each component's examples and testing target. 15 | - Switching between projects/targets/workspaces. 16 | 17 | One solution to this problem is to create what's called a "Catalog" app. A Catalog's purpose is to 18 | demonstrate the usage of a collection of components in one app. A Catalog application reduces the 19 | number of distinct Xcode targets your team has to interact with. Your team manages your Catalog's 20 | Xcode project, adding and updating files as necessary. 21 | 22 | What if — instead of managing an Xcode project — you simply had to create new source files and run 23 | `pod install`? 24 | 25 | Catalog by Convention minimizes the engineering overhead of creating **examples** and **unit tests** 26 | using a combination of conventions and CocoaPods. Simply run `pod install` and your Catalog will 27 | include all examples and unit tests in two easy to access targets. 28 | 29 | The only remaining engineering burden is to actually write the examples and tests. 30 | 31 | ### An example 32 | 33 | An example Catalog is located within the `example/` directory. Run pod install to set it up: 34 | 35 | pod install --project-directory=example/ 36 | open example/Catalog.xcworkspace 37 | 38 | Open the project and you'll find two targets: Catalog and UnitTests. Run the Catalog. 39 | 40 | ![Catalog](docs/assets/catalog.png) 41 | 42 | You can navigate through to the single component's examples in the catalog application. 43 | 44 | Now try running the unit tests. You'll see the one Resistor test appear in the unit test output. 45 | 46 | ![Tests](docs/assets/tests.png) 47 | 48 | **Quick introduction to adding a new example** 49 | 50 | Open the following directory: 51 | 52 | open example/components/Resistor/examples/ 53 | 54 | 1. Make a duplicate of either example. 55 | 2. Open the duplicate and give the class a unique name. 56 | 3. Change the `+catalogBreadcrumbs` path to something unique. 57 | 4. Run `pod install --project-directory=example/` and rebuild the app. 58 | 5. Your example will now be listed under Resistor. 59 | 60 | These five steps describe the end-to-end process for adding new examples to the catalog. 61 | 62 | **Quick introduction to adding a new test** 63 | 64 | Open the following directory: 65 | 66 | open example/components/Resistor/tests/unit/ 67 | 68 | 1. Make a duplicate of either test. 69 | 2. Open the duplicate and give the class a unique name. 70 | 3. Run `pod install --project-directory=example/` and rebuild the app. 71 | 4. Run the unit tests. 72 | 73 | ## Setup guide 74 | 75 | This guide will walk you through how to create a Catalog project that uses the CatalogByConvention 76 | library. 77 | 78 | ### Step 1: Plan out your component conventions 79 | 80 | This is the most important step. What matters most here is that you apply the convention 81 | consistently across each of your components. 82 | 83 | Let's look at the convention followed by the example included in the `example/` directory: 84 | 85 | components/ 86 | ComponentNameCamelCased/ 87 | examples/ 88 | SomeExample.m 89 | src/ 90 | Resistor.h 91 | RESClass.h 92 | RESClass.m 93 | tests/ 94 | unit/ 95 | SomeUnitTest.m 96 | 97 | ### Step 2: Create the necessary files/folders 98 | 99 | Alongside the `components/` directory we'll create the following: 100 | 101 | - A `catalog/` directory. 102 | - A `CatalogExamples.podspec` 103 | - A `CatalogUnitTests.podspec` 104 | - A `Podfile` 105 | 106 | The final result will look like so: 107 | 108 | catalog/ 109 | components/ 110 | CatalogExamples.podspec 111 | CatalogUnitTests.podspec 112 | Podfile 113 | 114 | ### Step 3: Create the convention podspecs 115 | 116 | Let's look at the contents of `CatalogExamples.podspec` and `CatalogUnitTests.podspec`. 117 | 118 | Within `CatalogExamples.podspec`: 119 | 120 | Pod::Spec.new do |s| 121 | s.name = "CatalogExamples" 122 | s.version = "1.0.0" 123 | s.summary = "Convention for catalog examples." 124 | s.homepage = "https://github.com/your/repo" 125 | s.authors = "Catalog" 126 | s.license = 'Apache 2.0' 127 | s.source = { :git => "https://github.com/your/repo.git", :tag => s.version.to_s } 128 | s.requires_arc = true 129 | 130 | # Conventions 131 | s.source_files = 'components/*/examples/*.{h,m,swift}' 132 | s.public_header_files = 'components/*/examples/*.h' 133 | s.resources = ['components/*/examples/resources/*'] 134 | end 135 | 136 | Within `CatalogUnitTests.podspec`: 137 | 138 | Pod::Spec.new do |s| 139 | s.name = "CatalogUnitTests" 140 | s.version = "1.0.0" 141 | s.summary = "Convention for catalog tests." 142 | s.homepage = "https://github.com/your/repo" 143 | s.authors = "Catalog" 144 | s.license = 'Apache 2.0' 145 | s.source = { :git => "https://github.com/your/repo.git", :tag => s.version.to_s } 146 | s.requires_arc = true 147 | s.framework = 'XCTest' 148 | 149 | # Conventions 150 | s.source_files = 'components/*/tests/unit/*.{h,m,swift}' 151 | s.resources = ['components/*/tests/unit/resources/*'] 152 | 153 | # Unit tests require you to specify your components as dependencies. 154 | s.dependency 'Resistor' 155 | end 156 | 157 | ### Step 4: Create a Podfile for your catalog 158 | 159 | Now let's edit the `Podfile`. 160 | 161 | Feel free to use the following as a template, updating the names of targets, paths, and dependencies 162 | where applicable. 163 | 164 | abstract_target 'Catalog' do 165 | workspace 'Catalog.xcworkspace' 166 | use_frameworks! 167 | 168 | pod 'CatalogByConvention' 169 | 170 | # Define where the local pods live. This allows your conventions to depend on them. 171 | pod 'Resistor', :path => 'components/Resistor' 172 | 173 | target "Catalog" do 174 | project 'catalog/Catalog.xcodeproj' 175 | pod 'CatalogExamples', :path => './' 176 | end 177 | 178 | target "UnitTests" do 179 | project 'catalog/Catalog.xcodeproj' 180 | pod 'CatalogUnitTests', :path => './' 181 | end 182 | end 183 | 184 | ### Step 5: Create the Catalog Xcode project 185 | 186 | Create a new Xcode project. We'll assume you're using the "Single View Application" template. Enable 187 | unit tests for the project. 188 | 189 | Ensure that your app and unit test target match those defined in your `Podfile`. 190 | 191 | Delete the default ViewController class. 192 | 193 | Update your app delegate to look like the following: 194 | 195 | import UIKit 196 | import CatalogByConvention 197 | 198 | @UIApplicationMain 199 | class AppDelegate: UIResponder, UIApplicationDelegate { 200 | 201 | var window: UIWindow? 202 | 203 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 204 | self.window = UIWindow(frame: UIScreen.mainScreen().bounds) 205 | 206 | let rootViewController = CBCNodeListViewController(node: CBCCreateNavigationTree()) 207 | rootViewController.title = "Catalog by Convention" 208 | 209 | let navController = UINavigationController(rootViewController: rootViewController) 210 | self.window?.rootViewController = navController 211 | 212 | self.window!.makeKeyAndVisible() 213 | return true 214 | } 215 | } 216 | 217 | ### Step 6: Run pod install 218 | 219 | Run `pod install` for your Catalog. Open your Catalog's workspace. 220 | 221 | pod install 222 | open Catalog.xcworkspace 223 | 224 | All of your examples, unit tests, and component source code will be found within the Pods project 225 | in your workspace. 226 | 227 | ![Pods](docs/assets/pods.png) 228 | 229 | ### Step 7: Build! 230 | 231 | From this point forward you simply need to create new example and unit test source files and they'll 232 | be picked up on a subsequent pod install. 233 | 234 | ### Ongoing steps: Adding examples 235 | 236 | For an example view controller to appear in your project your view controller must implement 237 | `+catalogBreadcrumbs`. For example: 238 | 239 | @implementation ParallelResistorExample (CatalogByConvention) 240 | 241 | + (NSArray *)catalogBreadcrumbs { 242 | return @[ @"Resistor", @"Parallel" ]; 243 | } 244 | 245 | @end 246 | 247 | ## License 248 | 249 | Licensed under the Apache 2.0 license. See LICENSE for details. 250 | -------------------------------------------------------------------------------- /src/private/CBCRuntime.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "CBCRuntime.h" 18 | 19 | #import "CBCCatalogExample.h" 20 | 21 | #import 22 | 23 | #pragma mark Metadata keys 24 | 25 | NSString *const CBCBreadcrumbs = @"breadcrumbs"; 26 | NSString *const CBCIsDebug = @"debug"; 27 | NSString *const CBCDescription = @"description"; 28 | NSString *const CBCIsPresentable = @"presentable"; 29 | NSString *const CBCIsPrimaryDemo = @"primaryDemo"; 30 | NSString *const CBCRelatedInfo = @"relatedInfo"; 31 | NSString *const CBCStoryboardName = @"storyboardName"; 32 | NSString *const CBCMinimumOSVersion = @"minimumOSVersion"; 33 | NSString *const CBCDeprecatedOSVersion = @"deprecatedOSVersion"; 34 | NSString *const CBCKeywords = @"keywords"; 35 | 36 | #pragma mark Class invocations 37 | 38 | static NSArray *CBCCatalogBreadcrumbsFromClass(Class aClass) { 39 | return [aClass performSelector:@selector(catalogBreadcrumbs)]; 40 | } 41 | 42 | static BOOL CBCCatalogIsPrimaryDemoFromClass(Class aClass) { 43 | BOOL isPrimary = NO; 44 | if ([aClass respondsToSelector:@selector(catalogIsPrimaryDemo)]) { 45 | isPrimary = [aClass catalogIsPrimaryDemo]; 46 | } 47 | return isPrimary; 48 | } 49 | 50 | static BOOL CBCCatalogIsPresentableFromClass(Class aClass) { 51 | BOOL isPresentable = YES; 52 | if ([aClass respondsToSelector:@selector(catalogIsPresentable)]) { 53 | isPresentable = [aClass catalogIsPresentable]; 54 | } 55 | return isPresentable; 56 | } 57 | 58 | static BOOL CBCCatalogIsDebugLeaf(Class aClass) { 59 | BOOL isDebugLeaf = NO; 60 | if ([aClass respondsToSelector:@selector(catalogIsDebug)]) { 61 | isDebugLeaf = [aClass catalogIsDebug]; 62 | } 63 | return isDebugLeaf; 64 | } 65 | 66 | static NSURL *CBCRelatedInfoFromClass(Class aClass) { 67 | NSURL *catalogRelatedInfo = nil; 68 | if ([aClass respondsToSelector:@selector(catalogRelatedInfo)]) { 69 | catalogRelatedInfo = [aClass catalogRelatedInfo]; 70 | } 71 | return catalogRelatedInfo; 72 | } 73 | 74 | static NSString *CBCDescriptionFromClass(Class aClass) { 75 | NSString *catalogDescription = nil; 76 | if ([aClass respondsToSelector:@selector(catalogDescription)]) { 77 | catalogDescription = [aClass catalogDescription]; 78 | } 79 | return catalogDescription; 80 | } 81 | 82 | static NSString *CBCStoryboardNameFromClass(Class aClass) { 83 | NSString *catalogStoryboardName = nil; 84 | if ([aClass respondsToSelector:@selector(catalogStoryboardName)]) { 85 | catalogStoryboardName = [aClass catalogStoryboardName]; 86 | } 87 | return catalogStoryboardName; 88 | } 89 | 90 | BOOL CBCCanRunClassOnCurrentOperatingSystem(Class aClass) { 91 | BOOL osVersionAtLeastMinimum = YES; 92 | BOOL osVersionLessThanDeprecated = YES; 93 | if ([aClass respondsToSelector:@selector(minimumOSVersion)]) { 94 | NSOperatingSystemVersion minimumOSVersion = [aClass minimumOSVersion]; 95 | 96 | osVersionAtLeastMinimum = 97 | [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:minimumOSVersion]; 98 | } 99 | 100 | if ([aClass respondsToSelector:@selector(deprecatedOSVersion)]) { 101 | NSOperatingSystemVersion deprecatedOSVersion = [aClass deprecatedOSVersion]; 102 | osVersionLessThanDeprecated = 103 | ![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:deprecatedOSVersion]; 104 | } 105 | 106 | return osVersionAtLeastMinimum && osVersionLessThanDeprecated; 107 | } 108 | 109 | static NSDictionary *CBCConstructMetadataFromMethods(Class aClass) { 110 | NSMutableDictionary *catalogMetadata = [NSMutableDictionary new]; 111 | if ([aClass respondsToSelector:@selector(catalogBreadcrumbs)]) { 112 | [catalogMetadata setObject:CBCCatalogBreadcrumbsFromClass(aClass) forKey:CBCBreadcrumbs]; 113 | [catalogMetadata setObject:[NSNumber numberWithBool:CBCCatalogIsPrimaryDemoFromClass(aClass)] 114 | forKey:CBCIsPrimaryDemo]; 115 | [catalogMetadata setObject:[NSNumber numberWithBool:CBCCatalogIsPresentableFromClass(aClass)] 116 | forKey:CBCIsPresentable]; 117 | [catalogMetadata setObject:[NSNumber numberWithBool:CBCCatalogIsDebugLeaf(aClass)] 118 | forKey:CBCIsDebug]; 119 | NSURL *relatedInfo; 120 | if ((relatedInfo = CBCRelatedInfoFromClass(aClass)) != nil) { 121 | [catalogMetadata setObject:CBCRelatedInfoFromClass(aClass) forKey:CBCRelatedInfo]; 122 | } 123 | NSString *description; 124 | if ((description = CBCDescriptionFromClass(aClass)) != nil) { 125 | [catalogMetadata setObject:CBCDescriptionFromClass(aClass) forKey:CBCDescription]; 126 | } 127 | NSString *storyboardName; 128 | if ((storyboardName = CBCStoryboardNameFromClass(aClass)) != nil) { 129 | [catalogMetadata setObject:CBCStoryboardNameFromClass(aClass) forKey:CBCStoryboardName]; 130 | } 131 | } 132 | return catalogMetadata; 133 | } 134 | 135 | NSDictionary *CBCCatalogMetadataFromClass(Class aClass) { 136 | NSDictionary *catalogMetadata; 137 | if ([aClass respondsToSelector:@selector(catalogMetadata)]) { 138 | catalogMetadata = [aClass catalogMetadata]; 139 | } else { 140 | catalogMetadata = CBCConstructMetadataFromMethods(aClass); 141 | } 142 | return catalogMetadata; 143 | } 144 | 145 | #pragma mark Runtime enumeration 146 | 147 | static BOOL IsSubclassOfClass(Class aClass, Class parentClass) { 148 | Class iterator = class_getSuperclass(aClass); 149 | while (iterator) { 150 | if (iterator == parentClass) { 151 | return YES; 152 | } 153 | iterator = class_getSuperclass(iterator); 154 | } 155 | return NO; 156 | } 157 | 158 | NSArray *CBCGetAllCompatibleClasses(void) { 159 | int numberOfClasses = objc_getClassList(NULL, 0); 160 | Class *classList = (Class *)malloc((size_t)numberOfClasses * sizeof(Class)); 161 | objc_getClassList(classList, numberOfClasses); 162 | 163 | NSMutableArray *classes = [NSMutableArray array]; 164 | 165 | NSSet *ignoredClasses = [NSSet setWithArray:@[ 166 | @"SwiftObject", @"Object", @"FigIrisAutoTrimmerMotionSampleExport", @"NSLeafProxy" 167 | ]]; 168 | NSArray *ignoredPrefixes = @[ @"Swift.", @"_", @"JS", @"WK", @"PF", @"NS" ]; 169 | 170 | Class viewControllerClass = [UIViewController class]; 171 | 172 | for (int ix = 0; ix < numberOfClasses; ++ix) { 173 | Class aClass = classList[ix]; 174 | 175 | if (!IsSubclassOfClass(aClass, viewControllerClass)) { 176 | continue; 177 | } 178 | 179 | NSString *className = NSStringFromClass(aClass); 180 | if ([ignoredClasses containsObject:className]) { 181 | continue; 182 | } 183 | BOOL hasIgnoredPrefix = NO; 184 | for (NSString *prefix in ignoredPrefixes) { 185 | if ([className hasPrefix:prefix]) { 186 | hasIgnoredPrefix = YES; 187 | break; 188 | } 189 | } 190 | if (hasIgnoredPrefix) { 191 | continue; 192 | } 193 | 194 | [classes addObject:aClass]; 195 | } 196 | 197 | free(classList); 198 | 199 | return classes; 200 | } 201 | 202 | NSArray *CBCClassesRespondingToSelector(NSArray *classes, SEL selector) { 203 | NSMutableArray *filteredClasses = [NSMutableArray array]; 204 | for (Class aClass in classes) { 205 | if ([aClass respondsToSelector:selector]) { 206 | [filteredClasses addObject:aClass]; 207 | } 208 | } 209 | return filteredClasses; 210 | } 211 | 212 | #pragma mark UIViewController instantiation 213 | 214 | UIViewController *CBCViewControllerFromClass(Class aClass, NSDictionary *metadata) { 215 | if ([metadata objectForKey:CBCStoryboardName]) { 216 | NSString *storyboardName = [metadata objectForKey:CBCStoryboardName]; 217 | NSBundle *bundle = [NSBundle bundleForClass:aClass]; 218 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle]; 219 | NSCAssert(storyboard, @"expecting a storyboard to exist at %@", storyboardName); 220 | UIViewController *vc = [storyboard instantiateInitialViewController]; 221 | NSCAssert(vc, @"expecting a initialViewController in the storyboard %@", storyboardName); 222 | return vc; 223 | } 224 | return [[aClass alloc] init]; 225 | } 226 | 227 | #pragma mark Fix View Debugging 228 | 229 | void CBCFixViewDebuggingIfNeeded(void) { 230 | static dispatch_once_t onceToken; 231 | dispatch_once(&onceToken, ^{ 232 | Method original = class_getInstanceMethod([UIView class], @selector(viewForBaselineLayout)); 233 | class_addMethod([UIView class], @selector(viewForFirstBaselineLayout), 234 | method_getImplementation(original), method_getTypeEncoding(original)); 235 | class_addMethod([UIView class], @selector(viewForLastBaselineLayout), 236 | method_getImplementation(original), method_getTypeEncoding(original)); 237 | }); 238 | } 239 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.5.2 2 | 3 | This patch release includes performance improvements and support for macCatalyst apps. 4 | 5 | # 2.5.1 6 | 7 | Fix a runtime crash that occurred when the PhotoFoundation framework was included in an app. 8 | 9 | ## Source changes 10 | 11 | * [Ignore PhotoFoundation classes in the runtime lookup (#31)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/2e8866dcd15ed6a3e755c054510333438802e0a6) (featherless) 12 | 13 | # 2.5.0 14 | 15 | There is now a new `NSDictionary` property in `CBCNode` called metadata. It is meant to store all the information regarding an example 16 | rather than using separate methods as previously done. With that said, we offer backwards compatibility and still allow the usage of methods to provide example information. 17 | 18 | Before: 19 | ``` 20 | + (NSArray *)catalogBreadcrumbs { 21 | return @[ @"Activity Indicator", @"Activity Indicator" ]; 22 | } 23 | 24 | + (NSString *)catalogDescription { 25 | return @"Activity Indicator is a visual indication of an app loading content. It can display how " 26 | @"long an operation will take or visualize an unspecified wait time."; 27 | } 28 | 29 | + (BOOL)catalogIsPrimaryDemo { 30 | return YES; 31 | } 32 | 33 | + (BOOL)catalogIsPresentable { 34 | return YES; 35 | } 36 | ``` 37 | 38 | After: 39 | ``` 40 | + (NSDictionary *)catalogMetadata { 41 | return @{@"breadcrumbs": @[ @"Activity Indicator", @"Activity Indicator" ], 42 | @"description": @"Activity Indicator is a visual indication of an app loading content. It can display how " 43 | @"long an operation will take or visualize an unspecified wait time.", 44 | @"primaryDemo": @YES, 45 | @"presentable": @YES}; 46 | } 47 | ``` 48 | 49 | ## Source changes 50 | 51 | * [added a new metadata property that will hold all the key/values for that node. Also code refactoring (#27)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/6c44e443e98bb87c955663c45c1245921338de1e) (Yarden Eitan) 52 | 53 | ## API changes 54 | 55 | #### CBCBreadcrumbs 56 | 57 | *new* constant: `CBCBreadcrumbs` 58 | 59 | #### CBCNode 60 | 61 | *new* property: `metadata` in `CBCNode` 62 | 63 | *removed* property: `nodeDescription` in `CBCNode` 64 | 65 | *modified* method: `-exampleDescription` in `CBCNode` 66 | 67 | | Type of change: | Swift declaration | 68 | |---|---| 69 | | From: | `func exampleDescription() -> String` | 70 | | To: | `func exampleDescription() -> String?` | 71 | 72 | *modified* method: `-exampleDescription` in `CBCNode` 73 | 74 | | Type of change: | Declaration | 75 | |---|---| 76 | | From: | `- (nonnull NSString *)exampleDescription;` | 77 | | To: | `- (nullable NSString *)exampleDescription;` | 78 | 79 | #### CBCRelatedInfo 80 | 81 | *new* constant: `CBCRelatedInfo` 82 | 83 | #### CBCIsDebug 84 | 85 | *new* constant: `CBCIsDebug` 86 | 87 | #### CBCIsPresentable 88 | 89 | *new* constant: `CBCIsPresentable` 90 | 91 | #### CBCIsPrimaryDemo 92 | 93 | *new* constant: `CBCIsPrimaryDemo` 94 | 95 | #### CBCDescription 96 | 97 | *new* constant: `CBCDescription` 98 | 99 | #### CBCStoryboardName 100 | 101 | *new* constant: `CBCStoryboardName` 102 | 103 | # 2.4.1 104 | 105 | Add `exampleRelatedInfo` to the CBCNode header for external invocation. 106 | 107 | ## Source changes 108 | 109 | * [add exampleRelatedInfo to header file (#26)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/57edd7f16ea7ec40d62378faacbc95071310bfbc) (Yarden Eitan) 110 | 111 | # 2.4.0 112 | 113 | - Now you can add the method `catalogRelatedInfo` that returns an NSURL to your example, if you wish to link to related information and resources. 114 | - A performance improvement when fetching all viable classes to build the navigation tree. 115 | 116 | ## Source changes 117 | 118 | * [Add "related info" URLs to examples (#24)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/ee57bf7bb544b105c5d91aaa2ef348d0f663a690) (Adrian Secord) 119 | * [[Runtime] Only select UIViewController subclasses (#22)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/ce864aabf505978a3933a93bfcf048f5d41bc071) (Robert Moore) 120 | 121 | # 2.3.1 122 | 123 | minor bug fix introduced in 2.3.0 that returns wrong boolean values. 124 | 125 | ## Source changes 126 | 127 | * [PR fixes introduced a minor bug, need to update release (#20)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/6be4d710a05dbe981728af00700e20268ca548a9) (Yarden Eitan) 128 | 129 | # 2.3.0 130 | 131 | This minor release adds two new functionalities that support our new Dragons app. 132 | 133 | ## New features 134 | 135 | It's now possible to call `CBCCreatePresentableNavigationTree` to create a navigation tree that only consists of examples that implement the `catalogIsPresentable` method and return `YES` to it. 136 | `CBCNode` now has a new property called `debugLeaf` which is also a `CBCNode`. If an example implements the `catalogIsDebug` method and returns `YES`, then that example will become the 137 | `debugLeaf`. When the `debugLeaf` is set, then in the Dragons app it will become the initial view controller in the navigation. NOTE: If there are multiple examples that return `YES` to 138 | `catalogIsDebug` then the last class that is parsed while going through the hierarchy is the one to be set as the `debugLeaf`. 139 | 140 | ## Source changes 141 | 142 | * [Additional functionality to CbC to support Dragons (#19)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/67c6d97e80c465d5915dc6dff4c6c19f53627bb8) (Yarden Eitan) 143 | 144 | ## Non-source changes 145 | 146 | * [Remove arc support. (#18)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/1fcaf777143b7906958b1cccc3a861320c45ce36) (featherless) 147 | * [Bump the kokoro runner version to v2.1.1.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/a49bf18bc839f86879473329a85f7939e0b115c8) (Jeff Verkoeyen) 148 | * [Replace Carthage support with bazel support (#17)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/dc45a1a6ef5ad92325ee2799b76920375141dacc) (featherless) 149 | 150 | # 2.2.0 151 | 152 | This minor release introduces support for Carthage. 153 | 154 | ## New features 155 | 156 | It's now possible to define multiple paths to a single example. Simply return an array of arrays 157 | of breadcrumbs from the `catalogBreadcrumbs` implementation. 158 | 159 | ## Source changes 160 | 161 | * [added ability to have multiple parallel bread crumbs to get to a view controller.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/cc6a0b16dc41cc044d2ca0a98aa2dcbd35a7c2c5) (randallli) 162 | 163 | ## Non-source changes 164 | 165 | * [Disable code coverage reporting. (#16)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/098188b6353e96f1ffebe2749816e859ba1e8d72) (featherless) 166 | * [Add kokoro continuous build script. (#15)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/ac9cc4b1c67b74c2c03c1d12c1905dfb47a0a141) (featherless) 167 | * [Fix the xcodeproj. (#14)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/a25b7b00664903e90e9be058e5e7826213b6295b) (featherless) 168 | * [Add support for Carthage.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/30dfc96ae85c5e32040304ba584ad6663c9a931f) (Jeff Verkoeyen) 169 | * [fixed method signature](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/9cc0050858eb26dd6bd0c0ecef1f6ffcca6a49e1) (randallli) 170 | * [Add .swift-version file.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/3e38db52bd3d245ade4734394295894e123b1e59) (Jeff Verkoeyen) 171 | 172 | # 2.1.1 173 | 174 | - Fixed a crashing bug on iOS 10.3.1. 175 | 176 | # 2.1.0 177 | 178 | - Add tvOS as a platform. 179 | 180 | # 2.0.1 181 | 182 | - Fixed some warnings. 183 | 184 | ## Source changes 185 | 186 | * [Merge pull request #6 from randallli/fixWarnings](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/8136bf10acab15ebfb12de080e919f6540753dd9) (Randall Li) 187 | 188 | # 2.0.0 189 | 190 | - Upgraded to Swift 3. 191 | - Resolved nullability warning in CBCNodeListViewController. 192 | 193 | ## Source changes 194 | 195 | * [Resolve nullability warning in CBCNodeListViewController.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/aba9ba241b0c93b23aeff2dffbf840308fa1c6a9) (Jeff Verkoeyen) 196 | 197 | ## Non-source changes 198 | 199 | * [Update CHANGELOG.md.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/8749999cea843119c585267211bcebbdd482a5bf) (Jeff Verkoeyen) 200 | * [Automatic changelog preparation for release.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/204bcbf77edae27053e60f9e6c21d36bfb8d48c2) (Jeff Verkoeyen) 201 | * [Upgrade project to Xcode 8 and Swift 3.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/6d7d7e6786ccafe9a267641c48a71859710c5cc0) (Jeff Verkoeyen) 202 | * [Update Podfile.lock.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/d273761f8c452b2cf7fc1f97141320a8f5978ff4) (Jeff Verkoeyen) 203 | * [Add explanation for adding new examples.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/452e5f715adb1c5a1d68f4d642d20ce9ba51b875) (Jeff Verkoeyen) 204 | * [Add notes regarding creating a unit test target and ensuring names match.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/98639ec477c050a8a3d5bb14137fb70a2064bc0f) (Jeff Verkoeyen) 205 | * [Replace tabs with spaces.](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/a26020e6bc55f2a4a19eb45bd218109eea2ddcd1) (Jeff Verkoeyen) 206 | 207 | # 1.0.1 208 | 209 | - Add missing header import to src/CBCCatalogExample.h. 210 | - Add exampleViewControllerName API to CBCNode. 211 | - Use modern nullability annotations. 212 | - API docs now available at https://material-foundation.github.io/cocoapods-catalog-by-convention/. 213 | 214 | # 1.0.0 215 | 216 | - Initial release. 217 | 218 | Includes support for examples and unit tests by convention. 219 | 220 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /example/catalog/UnitTests.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3FAAAB8760AC01023F399C0E /* Pods_Catalog_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C240522789AEE2237903DA6 /* Pods_Catalog_UnitTests.framework */; }; 11 | 666CA7051CAE3628001B1884 /* XcodeCrashFix7.2.1.m in Sources */ = {isa = PBXBuildFile; fileRef = 666CA7031CAE3532001B1884 /* XcodeCrashFix7.2.1.m */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXFileReference section */ 15 | 3390CD8F587A4A3CF563BB0E /* Pods-Catalog-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog-UnitTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Catalog-UnitTests/Pods-Catalog-UnitTests.debug.xcconfig"; sourceTree = ""; }; 16 | 3D464296ABDD2726AA4FDF9F /* Pods-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.debug.xcconfig"; sourceTree = ""; }; 17 | 3FBF65A91EB5B22C45EA3C9B /* Pods_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | 52F8F8BECC5B67A3F5BA0E20 /* Pods-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.release.xcconfig"; sourceTree = ""; }; 19 | 666CA6F81CAE34F0001B1884 /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 666CA6FC1CAE34F0001B1884 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../UnitTests/Info.plist; sourceTree = ""; }; 21 | 666CA7031CAE3532001B1884 /* XcodeCrashFix7.2.1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XcodeCrashFix7.2.1.m; sourceTree = ""; }; 22 | 9C240522789AEE2237903DA6 /* Pods_Catalog_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Catalog_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | C96DDDFCA1936FA012354A56 /* Pods-Catalog-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog-UnitTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Catalog-UnitTests/Pods-Catalog-UnitTests.release.xcconfig"; sourceTree = ""; }; 24 | /* End PBXFileReference section */ 25 | 26 | /* Begin PBXFrameworksBuildPhase section */ 27 | 666CA6F51CAE34F0001B1884 /* Frameworks */ = { 28 | isa = PBXFrameworksBuildPhase; 29 | buildActionMask = 2147483647; 30 | files = ( 31 | 3FAAAB8760AC01023F399C0E /* Pods_Catalog_UnitTests.framework in Frameworks */, 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXFrameworksBuildPhase section */ 36 | 37 | /* Begin PBXGroup section */ 38 | 0B1714C5C78BD56BEB4A23A4 /* Frameworks */ = { 39 | isa = PBXGroup; 40 | children = ( 41 | 3FBF65A91EB5B22C45EA3C9B /* Pods_UnitTests.framework */, 42 | 9C240522789AEE2237903DA6 /* Pods_Catalog_UnitTests.framework */, 43 | ); 44 | name = Frameworks; 45 | sourceTree = ""; 46 | }; 47 | 666CA6DF1CAE34E1001B1884 = { 48 | isa = PBXGroup; 49 | children = ( 50 | 666CA6EA1CAE34E1001B1884 /* UnitTests */, 51 | 666CA6E91CAE34E1001B1884 /* Products */, 52 | 83252B2C0D7147765E8C58A1 /* Pods */, 53 | 0B1714C5C78BD56BEB4A23A4 /* Frameworks */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | 666CA6E91CAE34E1001B1884 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 666CA6F81CAE34F0001B1884 /* UnitTests.xctest */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | 666CA6EA1CAE34E1001B1884 /* UnitTests */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 666CA7031CAE3532001B1884 /* XcodeCrashFix7.2.1.m */, 69 | 666CA6FC1CAE34F0001B1884 /* Info.plist */, 70 | ); 71 | path = UnitTests; 72 | sourceTree = ""; 73 | }; 74 | 83252B2C0D7147765E8C58A1 /* Pods */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 3D464296ABDD2726AA4FDF9F /* Pods-UnitTests.debug.xcconfig */, 78 | 52F8F8BECC5B67A3F5BA0E20 /* Pods-UnitTests.release.xcconfig */, 79 | 3390CD8F587A4A3CF563BB0E /* Pods-Catalog-UnitTests.debug.xcconfig */, 80 | C96DDDFCA1936FA012354A56 /* Pods-Catalog-UnitTests.release.xcconfig */, 81 | ); 82 | name = Pods; 83 | sourceTree = ""; 84 | }; 85 | /* End PBXGroup section */ 86 | 87 | /* Begin PBXNativeTarget section */ 88 | 666CA6F71CAE34F0001B1884 /* UnitTests */ = { 89 | isa = PBXNativeTarget; 90 | buildConfigurationList = 666CA7001CAE34F0001B1884 /* Build configuration list for PBXNativeTarget "UnitTests" */; 91 | buildPhases = ( 92 | 9CA275C50F46028F0EBA13E7 /* [CP] Check Pods Manifest.lock */, 93 | 666CA6F41CAE34F0001B1884 /* Sources */, 94 | 666CA6F51CAE34F0001B1884 /* Frameworks */, 95 | 666CA6F61CAE34F0001B1884 /* Resources */, 96 | 8F5625CD97931E20C8116BFF /* [CP] Embed Pods Frameworks */, 97 | E2A6B4BD8988E7DF3FD232F4 /* [CP] Copy Pods Resources */, 98 | ); 99 | buildRules = ( 100 | ); 101 | dependencies = ( 102 | ); 103 | name = UnitTests; 104 | productName = UnitTests; 105 | productReference = 666CA6F81CAE34F0001B1884 /* UnitTests.xctest */; 106 | productType = "com.apple.product-type.bundle.unit-test"; 107 | }; 108 | /* End PBXNativeTarget section */ 109 | 110 | /* Begin PBXProject section */ 111 | 666CA6E01CAE34E1001B1884 /* Project object */ = { 112 | isa = PBXProject; 113 | attributes = { 114 | LastUpgradeCheck = 0800; 115 | ORGANIZATIONNAME = Google; 116 | TargetAttributes = { 117 | 666CA6F71CAE34F0001B1884 = { 118 | CreatedOnToolsVersion = 7.2.1; 119 | LastSwiftMigration = 0800; 120 | }; 121 | }; 122 | }; 123 | buildConfigurationList = 666CA6E31CAE34E1001B1884 /* Build configuration list for PBXProject "UnitTests" */; 124 | compatibilityVersion = "Xcode 3.2"; 125 | developmentRegion = English; 126 | hasScannedForEncodings = 0; 127 | knownRegions = ( 128 | en, 129 | ); 130 | mainGroup = 666CA6DF1CAE34E1001B1884; 131 | productRefGroup = 666CA6E91CAE34E1001B1884 /* Products */; 132 | projectDirPath = ""; 133 | projectRoot = ""; 134 | targets = ( 135 | 666CA6F71CAE34F0001B1884 /* UnitTests */, 136 | ); 137 | }; 138 | /* End PBXProject section */ 139 | 140 | /* Begin PBXResourcesBuildPhase section */ 141 | 666CA6F61CAE34F0001B1884 /* Resources */ = { 142 | isa = PBXResourcesBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXResourcesBuildPhase section */ 149 | 150 | /* Begin PBXShellScriptBuildPhase section */ 151 | 8F5625CD97931E20C8116BFF /* [CP] Embed Pods Frameworks */ = { 152 | isa = PBXShellScriptBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | ); 156 | inputPaths = ( 157 | "${SRCROOT}/../Pods/Target Support Files/Pods-Catalog-UnitTests/Pods-Catalog-UnitTests-frameworks.sh", 158 | "${BUILT_PRODUCTS_DIR}/CatalogByConvention/CatalogByConvention.framework", 159 | "${BUILT_PRODUCTS_DIR}/Resistor/Resistor.framework", 160 | "${BUILT_PRODUCTS_DIR}/CatalogUnitTests/CatalogUnitTests.framework", 161 | ); 162 | name = "[CP] Embed Pods Frameworks"; 163 | outputPaths = ( 164 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CatalogByConvention.framework", 165 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Resistor.framework", 166 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CatalogUnitTests.framework", 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | shellPath = /bin/sh; 170 | shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-Catalog-UnitTests/Pods-Catalog-UnitTests-frameworks.sh\"\n"; 171 | showEnvVarsInLog = 0; 172 | }; 173 | 9CA275C50F46028F0EBA13E7 /* [CP] Check Pods Manifest.lock */ = { 174 | isa = PBXShellScriptBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | ); 178 | inputPaths = ( 179 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 180 | "${PODS_ROOT}/Manifest.lock", 181 | ); 182 | name = "[CP] Check Pods Manifest.lock"; 183 | outputPaths = ( 184 | "$(DERIVED_FILE_DIR)/Pods-Catalog-UnitTests-checkManifestLockResult.txt", 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | shellPath = /bin/sh; 188 | 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"; 189 | showEnvVarsInLog = 0; 190 | }; 191 | E2A6B4BD8988E7DF3FD232F4 /* [CP] Copy Pods Resources */ = { 192 | isa = PBXShellScriptBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | inputPaths = ( 197 | ); 198 | name = "[CP] Copy Pods Resources"; 199 | outputPaths = ( 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | shellPath = /bin/sh; 203 | shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-Catalog-UnitTests/Pods-Catalog-UnitTests-resources.sh\"\n"; 204 | showEnvVarsInLog = 0; 205 | }; 206 | /* End PBXShellScriptBuildPhase section */ 207 | 208 | /* Begin PBXSourcesBuildPhase section */ 209 | 666CA6F41CAE34F0001B1884 /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 666CA7051CAE3628001B1884 /* XcodeCrashFix7.2.1.m in Sources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXSourcesBuildPhase section */ 218 | 219 | /* Begin XCBuildConfiguration section */ 220 | 666CA6EF1CAE34E1001B1884 /* Debug */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 225 | CLANG_CXX_LIBRARY = "libc++"; 226 | CLANG_ENABLE_MODULES = YES; 227 | CLANG_ENABLE_OBJC_ARC = YES; 228 | CLANG_WARN_BOOL_CONVERSION = YES; 229 | CLANG_WARN_CONSTANT_CONVERSION = YES; 230 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 231 | CLANG_WARN_EMPTY_BODY = YES; 232 | CLANG_WARN_ENUM_CONVERSION = YES; 233 | CLANG_WARN_INFINITE_RECURSION = YES; 234 | CLANG_WARN_INT_CONVERSION = YES; 235 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 236 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 240 | COPY_PHASE_STRIP = NO; 241 | DEBUG_INFORMATION_FORMAT = dwarf; 242 | ENABLE_STRICT_OBJC_MSGSEND = YES; 243 | ENABLE_TESTABILITY = YES; 244 | GCC_C_LANGUAGE_STANDARD = gnu99; 245 | GCC_DYNAMIC_NO_PIC = NO; 246 | GCC_NO_COMMON_BLOCKS = YES; 247 | GCC_OPTIMIZATION_LEVEL = 0; 248 | GCC_PREPROCESSOR_DEFINITIONS = ( 249 | "DEBUG=1", 250 | "$(inherited)", 251 | ); 252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_UNDECLARED_SELECTOR = YES; 255 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 256 | GCC_WARN_UNUSED_FUNCTION = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 259 | MTL_ENABLE_DEBUG_INFO = YES; 260 | ONLY_ACTIVE_ARCH = YES; 261 | SDKROOT = iphoneos; 262 | }; 263 | name = Debug; 264 | }; 265 | 666CA6F01CAE34E1001B1884 /* Release */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ALWAYS_SEARCH_USER_PATHS = NO; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_CONSTANT_CONVERSION = YES; 275 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INFINITE_RECURSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNREACHABLE_CODE = YES; 283 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 284 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 285 | COPY_PHASE_STRIP = NO; 286 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 287 | ENABLE_NS_ASSERTIONS = NO; 288 | ENABLE_STRICT_OBJC_MSGSEND = YES; 289 | GCC_C_LANGUAGE_STANDARD = gnu99; 290 | GCC_NO_COMMON_BLOCKS = YES; 291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 292 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 293 | GCC_WARN_UNDECLARED_SELECTOR = YES; 294 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 295 | GCC_WARN_UNUSED_FUNCTION = YES; 296 | GCC_WARN_UNUSED_VARIABLE = YES; 297 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 298 | MTL_ENABLE_DEBUG_INFO = NO; 299 | SDKROOT = iphoneos; 300 | VALIDATE_PRODUCT = YES; 301 | }; 302 | name = Release; 303 | }; 304 | 666CA7011CAE34F0001B1884 /* Debug */ = { 305 | isa = XCBuildConfiguration; 306 | baseConfigurationReference = 3390CD8F587A4A3CF563BB0E /* Pods-Catalog-UnitTests.debug.xcconfig */; 307 | buildSettings = { 308 | INFOPLIST_FILE = UnitTests/Info.plist; 309 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 310 | PRODUCT_BUNDLE_IDENTIFIER = com.google.UnitTests; 311 | PRODUCT_NAME = "$(TARGET_NAME)"; 312 | SWIFT_VERSION = 3.0; 313 | }; 314 | name = Debug; 315 | }; 316 | 666CA7021CAE34F0001B1884 /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | baseConfigurationReference = C96DDDFCA1936FA012354A56 /* Pods-Catalog-UnitTests.release.xcconfig */; 319 | buildSettings = { 320 | INFOPLIST_FILE = UnitTests/Info.plist; 321 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 322 | PRODUCT_BUNDLE_IDENTIFIER = com.google.UnitTests; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | SWIFT_VERSION = 3.0; 325 | }; 326 | name = Release; 327 | }; 328 | /* End XCBuildConfiguration section */ 329 | 330 | /* Begin XCConfigurationList section */ 331 | 666CA6E31CAE34E1001B1884 /* Build configuration list for PBXProject "UnitTests" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 666CA6EF1CAE34E1001B1884 /* Debug */, 335 | 666CA6F01CAE34E1001B1884 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | 666CA7001CAE34F0001B1884 /* Build configuration list for PBXNativeTarget "UnitTests" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | 666CA7011CAE34F0001B1884 /* Debug */, 344 | 666CA7021CAE34F0001B1884 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | /* End XCConfigurationList section */ 350 | }; 351 | rootObject = 666CA6E01CAE34E1001B1884 /* Project object */; 352 | } 353 | -------------------------------------------------------------------------------- /src/CBCNodeListViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016-present Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "CBCNodeListViewController.h" 18 | 19 | #import "private/CBCRuntime.h" 20 | 21 | @interface CBCNode() 22 | @property(nonatomic, strong, nullable) NSMutableDictionary *map; 23 | @property(nonatomic, strong, nullable) Class exampleClass; 24 | @property(nonatomic, copy, nullable) NSSet *keyWords; 25 | @end 26 | 27 | @implementation CBCNode { 28 | NSMutableArray *_children; 29 | } 30 | 31 | - (instancetype)initWithTitle:(NSString *)title { 32 | self = [super init]; 33 | if (self) { 34 | _title = [title copy]; 35 | self.map = [NSMutableDictionary dictionary]; 36 | _children = [NSMutableArray array]; 37 | CBCFixViewDebuggingIfNeeded(); 38 | } 39 | return self; 40 | } 41 | 42 | - (NSComparisonResult)compare:(CBCNode *)otherObject { 43 | return [self.title compare:otherObject.title]; 44 | } 45 | 46 | - (void)addChild:(CBCNode *)child { 47 | self.map[child.title] = child; 48 | [_children addObject:child]; 49 | } 50 | 51 | - (void)finalizeNode { 52 | _children = [[_children sortedArrayUsingSelector:@selector(compare:)] mutableCopy]; 53 | } 54 | 55 | #pragma mark Public 56 | 57 | - (BOOL)isExample { 58 | return self.exampleClass != nil; 59 | } 60 | 61 | - (NSString *)exampleViewControllerName { 62 | NSAssert(self.exampleClass != nil, @"This node has no associated example."); 63 | return NSStringFromClass(_exampleClass); 64 | } 65 | 66 | - (UIViewController *)createExampleViewController { 67 | NSAssert(self.exampleClass != nil, @"This node has no associated example."); 68 | return CBCViewControllerFromClass(self.exampleClass, self.metadata); 69 | } 70 | 71 | - (NSString *)exampleDescription { 72 | NSString *description = [self.metadata objectForKey:CBCDescription]; 73 | if (description != nil && [description isKindOfClass:[NSString class]]) { 74 | return description; 75 | } 76 | return nil; 77 | } 78 | 79 | - (NSURL *)exampleRelatedInfo { 80 | NSURL *relatedInfo = [self.metadata objectForKey:CBCRelatedInfo]; 81 | if (relatedInfo != nil && [relatedInfo isKindOfClass:[NSURL class]]) { 82 | return relatedInfo; 83 | } 84 | return nil; 85 | } 86 | 87 | - (BOOL)isPrimaryDemo { 88 | id isPrimaryDemo; 89 | if ((isPrimaryDemo = [self.metadata objectForKey:CBCIsPrimaryDemo]) != nil) { 90 | return [isPrimaryDemo boolValue]; 91 | } 92 | return NO; 93 | } 94 | 95 | - (BOOL)isPresentable { 96 | id isPresentable; 97 | if ((isPresentable = [self.metadata objectForKey:CBCIsPresentable]) != nil) { 98 | return [isPresentable boolValue]; 99 | } 100 | return NO; 101 | } 102 | 103 | @end 104 | 105 | static NSArray *CBCGetSearchResultsFromNode(CBCNode *node, NSSet *filters, NSString *searchText) { 106 | NSMutableArray *searchResults = [NSMutableArray array]; 107 | 108 | // Breadth-first search through the navigation tree starting at the given node. 109 | NSMutableArray *queue = [NSMutableArray arrayWithObject:node]; 110 | while ([queue count] > 0) { 111 | CBCNode *current = [queue firstObject]; 112 | [queue removeObjectAtIndex:0]; 113 | if (current.children.count > 0) { 114 | [queue addObjectsFromArray:current.children]; 115 | continue; 116 | } 117 | 118 | // If the node is an example, conduct the filtering. 119 | if (filters.count > 0) { 120 | NSMutableSet *keywordKeys = [current.keyWords mutableCopy]; 121 | [keywordKeys intersectSet:filters]; 122 | // If the keywords do not contain all of the filters, do not add the node. 123 | if (keywordKeys.count != filters.count) { 124 | continue; 125 | } 126 | } 127 | if (searchText.length > 0) { 128 | for (NSString *keyword in current.keyWords) { 129 | if ([keyword containsString:searchText.lowercaseString]) { 130 | [searchResults addObject:current]; 131 | break; 132 | } 133 | } 134 | } else { 135 | // Add the node if there is no search text and it passes the filter check. 136 | [searchResults addObject:current]; 137 | } 138 | } 139 | return searchResults; 140 | } 141 | 142 | static NSDictionary *> *CBCGetSearchResultGroupedNodes( 143 | NSDictionary *> *groupedNodes, NSSet *filters, NSString *searchText) { 144 | NSMutableDictionary *> *searchResultGroupedNodes = 145 | [NSMutableDictionary dictionary]; 146 | for (NSString *group in groupedNodes) { 147 | NSMutableArray *searchResults = [NSMutableArray array]; 148 | for (CBCNode *node in groupedNodes[group]) { 149 | [searchResults addObjectsFromArray:CBCGetSearchResultsFromNode(node, filters, searchText)]; 150 | } 151 | if (searchResults.count > 0) { 152 | searchResultGroupedNodes[group] = searchResults; 153 | } 154 | } 155 | return searchResultGroupedNodes; 156 | } 157 | 158 | @interface CBCNodeListViewController () 159 | @property(nonatomic) NSArray *groups; 160 | @property(nonatomic) NSDictionary *> *groupedNodes; 161 | @property(nonatomic) NSDictionary *> *searchResultGroupedNodes; 162 | @property(nonatomic) NSMutableSet *filters; 163 | @property(nonatomic, copy, nullable) NSString *searchText; 164 | @end 165 | 166 | @implementation CBCNodeListViewController 167 | 168 | - (instancetype)initWithNode:(CBCNode *)node { 169 | NSAssert(!self.node.isExample, @"%@ cannot represent example nodes.", 170 | NSStringFromClass([self class])); 171 | 172 | self = [super initWithNibName:nil bundle:nil]; 173 | if (self) { 174 | _node = node; 175 | 176 | NSMutableSet *groups = [NSMutableSet set]; 177 | NSMutableDictionary *> *groupedNodes = 178 | [NSMutableDictionary dictionary]; 179 | for (CBCNode *child in _node.children) { 180 | NSString *group = child.group; 181 | if (group == nil) { 182 | group = @""; // Ungrouped items get placed in a default group. 183 | } 184 | // Convert to lowercase to ensure case-insensitive grouping since group titles will always be 185 | // capitalized in the UI. 186 | [groups addObject:group]; 187 | 188 | NSMutableArray *nodes = groupedNodes[group]; 189 | if (nodes == nil) { 190 | nodes = [NSMutableArray array]; 191 | groupedNodes[group] = nodes; 192 | } 193 | [nodes addObject:child]; 194 | } 195 | _groups = [groups sortedArrayUsingDescriptors:@[ 196 | [NSSortDescriptor sortDescriptorWithKey:@"self" 197 | ascending:YES 198 | selector:@selector(localizedCaseInsensitiveCompare:)] 199 | ]]; 200 | _groupedNodes = groupedNodes; 201 | _filters = [NSMutableSet set]; 202 | _searchEnabled = NO; 203 | _searchResultGroupedNodes = [NSMutableDictionary dictionary]; 204 | 205 | self.title = self.node.title; 206 | } 207 | return self; 208 | } 209 | 210 | - (void)viewDidLoad { 211 | [super viewDidLoad]; 212 | 213 | UITableViewStyle style = UITableViewStyleGrouped; 214 | #if !TARGET_OS_TV 215 | style = UITableViewStyleInsetGrouped; 216 | if (@available(iOS 13.0, *)) { 217 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { 218 | style = UITableViewStyleInsetGrouped; 219 | } 220 | } 221 | #endif 222 | self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:style]; 223 | self.tableView.autoresizingMask = 224 | (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); 225 | self.tableView.delegate = self; 226 | self.tableView.dataSource = self; 227 | [self.view addSubview:self.tableView]; 228 | } 229 | 230 | - (void)viewWillAppear:(BOOL)animated { 231 | [super viewWillAppear:animated]; 232 | 233 | NSIndexPath *selectedRow = self.tableView.indexPathForSelectedRow; 234 | if (selectedRow) { 235 | [[self transitionCoordinator] animateAlongsideTransition:^(id context) { 236 | [self.tableView deselectRowAtIndexPath:selectedRow animated:YES]; 237 | } 238 | completion:^(id context) { 239 | if ([context isCancelled]) { 240 | [self.tableView selectRowAtIndexPath:selectedRow 241 | animated:NO 242 | scrollPosition:UITableViewScrollPositionNone]; 243 | } 244 | }]; 245 | } 246 | } 247 | 248 | - (void)viewDidAppear:(BOOL)animated { 249 | [super viewDidAppear:animated]; 250 | 251 | [self.tableView flashScrollIndicators]; 252 | } 253 | 254 | #pragma mark - UITableViewDataSource 255 | 256 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 257 | if (_searchEnabled) { 258 | return [_searchResultGroupedNodes count]; 259 | } 260 | return [_groups count]; 261 | } 262 | 263 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 264 | if (_searchEnabled) { 265 | return [_searchResultGroupedNodes.allKeys objectAtIndex:section]; 266 | } 267 | return _groups[section]; 268 | } 269 | 270 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 271 | NSString *group = _groups[section]; 272 | if (_searchEnabled) { 273 | group = [_searchResultGroupedNodes.allKeys objectAtIndex:section]; 274 | return (NSInteger)[_searchResultGroupedNodes[group] count]; 275 | } 276 | return (NSInteger)[self.groupedNodes[group] count]; 277 | } 278 | 279 | - (UITableViewCell *)tableView:(UITableView *)tableView 280 | cellForRowAtIndexPath:(NSIndexPath *)indexPath { 281 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 282 | if (!cell) { 283 | cell = 284 | [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; 285 | } 286 | CBCNode *node = [self nodeForIndexPath:indexPath]; 287 | cell.textLabel.text = node.title; 288 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 289 | return cell; 290 | } 291 | 292 | #pragma mark - UITableViewDelegate 293 | 294 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 295 | CBCNode *node = [self nodeForIndexPath:indexPath]; 296 | UIViewController *viewController = nil; 297 | if ([node isExample]) { 298 | viewController = [node createExampleViewController]; 299 | } else { 300 | viewController = [[[self class] alloc] initWithNode:node]; 301 | } 302 | [self.navigationController pushViewController:viewController animated:YES]; 303 | } 304 | 305 | #pragma mark - Model 306 | 307 | - (CBCNode *)nodeForIndexPath:(NSIndexPath *)indexPath { 308 | NSString *group = _groups[indexPath.section]; 309 | if (_searchEnabled) { 310 | group = [_searchResultGroupedNodes.allKeys objectAtIndex:indexPath.section]; 311 | return [_searchResultGroupedNodes[group] objectAtIndex:indexPath.row]; 312 | } 313 | return self.groupedNodes[group][(NSUInteger)indexPath.row]; 314 | } 315 | 316 | #pragma mark - Filter and Search 317 | 318 | - (void)updateFilters:(NSString *)filter enabled:(BOOL)enabled { 319 | if (enabled) { 320 | [self.filters addObject:filter.lowercaseString]; 321 | } else { 322 | [self.filters removeObject:filter.lowercaseString]; 323 | } 324 | [self search]; 325 | } 326 | 327 | - (void)findSearchText:(NSString *)searchText { 328 | self.searchText = searchText; 329 | [self search]; 330 | } 331 | 332 | - (void)search { 333 | self.searchEnabled = self.searchText.length > 0 || self.filters.count > 0; 334 | if (self.searchEnabled) { 335 | self.searchResultGroupedNodes = 336 | CBCGetSearchResultGroupedNodes(self.groupedNodes, self.filters, self.searchText); 337 | } 338 | [self.tableView reloadData]; 339 | [self.tableView layoutIfNeeded]; 340 | } 341 | 342 | @end 343 | 344 | static void CBCAddNodeFromBreadCrumbs(CBCNode *tree, 345 | NSArray *breadCrumbs, 346 | Class aClass, 347 | NSDictionary *metadata) { 348 | // Walk down the navigation tree one breadcrumb at a time, creating nodes along the way. 349 | 350 | CBCNode *node = tree; 351 | for (NSUInteger ix = 0; ix < [breadCrumbs count]; ++ix) { 352 | NSString *title; 353 | NSString *group; 354 | id breadCrumb = breadCrumbs[ix]; 355 | if ([breadCrumb isKindOfClass:[NSArray class]]) { 356 | NSArray *breadCrumbArray = breadCrumb; 357 | if (breadCrumbArray.count > 1) { 358 | group = breadCrumbArray[0]; 359 | title = breadCrumbArray[1]; 360 | } else { 361 | title = breadCrumbArray[0]; 362 | } 363 | } else { 364 | title = breadCrumb; 365 | } 366 | BOOL isLastCrumb = ix == [breadCrumbs count] - 1; 367 | 368 | // Don't walk the last crumb 369 | if (node.map[title] && !isLastCrumb) { 370 | node = node.map[title]; 371 | node.group = group; 372 | continue; 373 | } 374 | 375 | CBCNode *child = [[CBCNode alloc] initWithTitle:title]; 376 | [node addChild:child]; 377 | node = child; 378 | node.group = group; 379 | } 380 | 381 | // Metadata gets assigned to the leaf node in the tree. 382 | node.metadata = metadata; 383 | 384 | // Gather keywords from the breadcrumbs and keywords metadata for search. 385 | NSMutableSet *keywords = [NSMutableSet set]; 386 | for (id crumb in metadata[CBCBreadcrumbs]) { 387 | if ([crumb isKindOfClass:[NSArray class]]) { 388 | for (NSString *word in crumb) { 389 | [keywords addObject:word.lowercaseString]; 390 | } 391 | } else { 392 | [keywords addObject:[crumb lowercaseString]]; 393 | } 394 | } 395 | for (NSString *keyword in metadata[CBCKeywords]) { 396 | [keywords addObject:keyword.lowercaseString]; 397 | } 398 | node.keyWords = [keywords copy]; 399 | 400 | if ([[metadata objectForKey:CBCIsDebug] boolValue]) { 401 | tree.debugLeaf = node; 402 | } 403 | node.exampleClass = aClass; 404 | } 405 | 406 | static CBCNode *CBCCreateTreeWithOnlyPresentable(BOOL onlyPresentable) { 407 | NSArray *allClasses = CBCGetAllCompatibleClasses(); 408 | NSArray *filteredClasses = [allClasses 409 | filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, 410 | NSDictionary *bindings) { 411 | if (!CBCCanRunClassOnCurrentOperatingSystem(object)) { 412 | return NO; 413 | } 414 | NSDictionary *metadata = CBCCatalogMetadataFromClass(object); 415 | id breadcrumbs = [metadata objectForKey:CBCBreadcrumbs]; 416 | BOOL validObject = breadcrumbs != nil && [breadcrumbs isKindOfClass:[NSArray class]]; 417 | NSNumber *isPresentable = [metadata objectForKey:CBCIsPresentable]; 418 | // If CBCIsPresentable is not explicitly set in a class's metadata, 419 | // this class is presentable by default. 420 | if (onlyPresentable && isPresentable) { 421 | validObject &= isPresentable.boolValue; 422 | } 423 | return validObject; 424 | }]]; 425 | 426 | CBCNode *tree = [[CBCNode alloc] initWithTitle:@"Root"]; 427 | for (Class aClass in filteredClasses) { 428 | // Each example view controller defines its own breadcrumbs (metadata[CBCBreadcrumbs]). 429 | NSDictionary *metadata = CBCCatalogMetadataFromClass(aClass); 430 | NSArray *breadCrumbs = [metadata objectForKey:CBCBreadcrumbs]; 431 | CBCAddNodeFromBreadCrumbs(tree, breadCrumbs, aClass, metadata); 432 | } 433 | 434 | // Perform final post-processing on the nodes. 435 | NSMutableArray *queue = [NSMutableArray arrayWithObject:tree]; 436 | while ([queue count] > 0) { 437 | CBCNode *node = [queue firstObject]; 438 | [queue removeObjectAtIndex:0]; 439 | [queue addObjectsFromArray:node.children]; 440 | 441 | [node finalizeNode]; 442 | } 443 | 444 | return tree; 445 | } 446 | 447 | CBCNode *CBCCreateNavigationTree(void) { 448 | return CBCCreateTreeWithOnlyPresentable(NO); 449 | } 450 | 451 | CBCNode *CBCCreatePresentableNavigationTree(void) { 452 | return CBCCreateTreeWithOnlyPresentable(YES); 453 | } 454 | -------------------------------------------------------------------------------- /example/catalog/Catalog.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 666CA6B51CAE277E001B1884 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 666CA6B41CAE277E001B1884 /* AppDelegate.swift */; }; 11 | 666CA6BC1CAE277E001B1884 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 666CA6BB1CAE277E001B1884 /* Assets.xcassets */; }; 12 | 666CA6BF1CAE277E001B1884 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 666CA6BD1CAE277E001B1884 /* LaunchScreen.storyboard */; }; 13 | 83BA0B8D6103E79970C1227F /* Pods_Catalog_Catalog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24BAEA858EF4F3E92559136E /* Pods_Catalog_Catalog.framework */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXFileReference section */ 17 | 121D8E1E6851B9F818032CF3 /* Pods-Catalog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Catalog/Pods-Catalog.debug.xcconfig"; sourceTree = ""; }; 18 | 24BAEA858EF4F3E92559136E /* Pods_Catalog_Catalog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Catalog_Catalog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 44BC83A9EA60CDCD8C1D2BC4 /* Pods-Catalog-Catalog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog-Catalog.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Catalog-Catalog/Pods-Catalog-Catalog.debug.xcconfig"; sourceTree = ""; }; 20 | 666CA6B11CAE277E001B1884 /* Catalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Catalog.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 666CA6B41CAE277E001B1884 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | 666CA6BB1CAE277E001B1884 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 666CA6BE1CAE277E001B1884 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | 666CA6C01CAE277E001B1884 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | 78C82408C38B7D685BD9ACB5 /* Pods-Catalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog.release.xcconfig"; path = "Pods/Target Support Files/Pods-Catalog/Pods-Catalog.release.xcconfig"; sourceTree = ""; }; 26 | B6E8A428B0228637473AA0C3 /* Pods_Catalog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Catalog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | DA69943629622BF33AA4667D /* Pods-Catalog-Catalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog-Catalog.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Catalog-Catalog/Pods-Catalog-Catalog.release.xcconfig"; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 666CA6AE1CAE277E001B1884 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | 83BA0B8D6103E79970C1227F /* Pods_Catalog_Catalog.framework in Frameworks */, 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 2D36AAC37535529FA7D50C57 /* Pods */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 121D8E1E6851B9F818032CF3 /* Pods-Catalog.debug.xcconfig */, 46 | 78C82408C38B7D685BD9ACB5 /* Pods-Catalog.release.xcconfig */, 47 | 44BC83A9EA60CDCD8C1D2BC4 /* Pods-Catalog-Catalog.debug.xcconfig */, 48 | DA69943629622BF33AA4667D /* Pods-Catalog-Catalog.release.xcconfig */, 49 | ); 50 | name = Pods; 51 | sourceTree = ""; 52 | }; 53 | 666CA6A81CAE277E001B1884 = { 54 | isa = PBXGroup; 55 | children = ( 56 | 666CA6B31CAE277E001B1884 /* Catalog */, 57 | 666CA6B21CAE277E001B1884 /* Products */, 58 | 2D36AAC37535529FA7D50C57 /* Pods */, 59 | 8D3989EE1C77402F56511585 /* Frameworks */, 60 | ); 61 | sourceTree = ""; 62 | }; 63 | 666CA6B21CAE277E001B1884 /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 666CA6B11CAE277E001B1884 /* Catalog.app */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | 666CA6B31CAE277E001B1884 /* Catalog */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 666CA6B41CAE277E001B1884 /* AppDelegate.swift */, 75 | 666CA6BB1CAE277E001B1884 /* Assets.xcassets */, 76 | 666CA6BD1CAE277E001B1884 /* LaunchScreen.storyboard */, 77 | 666CA6C01CAE277E001B1884 /* Info.plist */, 78 | ); 79 | path = Catalog; 80 | sourceTree = ""; 81 | }; 82 | 8D3989EE1C77402F56511585 /* Frameworks */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | B6E8A428B0228637473AA0C3 /* Pods_Catalog.framework */, 86 | 24BAEA858EF4F3E92559136E /* Pods_Catalog_Catalog.framework */, 87 | ); 88 | name = Frameworks; 89 | sourceTree = ""; 90 | }; 91 | /* End PBXGroup section */ 92 | 93 | /* Begin PBXNativeTarget section */ 94 | 666CA6B01CAE277E001B1884 /* Catalog */ = { 95 | isa = PBXNativeTarget; 96 | buildConfigurationList = 666CA6C31CAE277E001B1884 /* Build configuration list for PBXNativeTarget "Catalog" */; 97 | buildPhases = ( 98 | 0B6A648A1B04FF2E118E2247 /* [CP] Check Pods Manifest.lock */, 99 | 666CA6AD1CAE277E001B1884 /* Sources */, 100 | 666CA6AE1CAE277E001B1884 /* Frameworks */, 101 | 666CA6AF1CAE277E001B1884 /* Resources */, 102 | B3B7B96A36CDFE60760F0D51 /* [CP] Embed Pods Frameworks */, 103 | 526A63F3210C836C577F3F5D /* [CP] Copy Pods Resources */, 104 | ); 105 | buildRules = ( 106 | ); 107 | dependencies = ( 108 | ); 109 | name = Catalog; 110 | productName = Catalog; 111 | productReference = 666CA6B11CAE277E001B1884 /* Catalog.app */; 112 | productType = "com.apple.product-type.application"; 113 | }; 114 | /* End PBXNativeTarget section */ 115 | 116 | /* Begin PBXProject section */ 117 | 666CA6A91CAE277E001B1884 /* Project object */ = { 118 | isa = PBXProject; 119 | attributes = { 120 | LastSwiftUpdateCheck = 0720; 121 | LastUpgradeCheck = 0800; 122 | ORGANIZATIONNAME = Google; 123 | TargetAttributes = { 124 | 666CA6B01CAE277E001B1884 = { 125 | CreatedOnToolsVersion = 7.2.1; 126 | LastSwiftMigration = 0800; 127 | }; 128 | }; 129 | }; 130 | buildConfigurationList = 666CA6AC1CAE277E001B1884 /* Build configuration list for PBXProject "Catalog" */; 131 | compatibilityVersion = "Xcode 3.2"; 132 | developmentRegion = English; 133 | hasScannedForEncodings = 0; 134 | knownRegions = ( 135 | en, 136 | Base, 137 | ); 138 | mainGroup = 666CA6A81CAE277E001B1884; 139 | productRefGroup = 666CA6B21CAE277E001B1884 /* Products */; 140 | projectDirPath = ""; 141 | projectRoot = ""; 142 | targets = ( 143 | 666CA6B01CAE277E001B1884 /* Catalog */, 144 | ); 145 | }; 146 | /* End PBXProject section */ 147 | 148 | /* Begin PBXResourcesBuildPhase section */ 149 | 666CA6AF1CAE277E001B1884 /* Resources */ = { 150 | isa = PBXResourcesBuildPhase; 151 | buildActionMask = 2147483647; 152 | files = ( 153 | 666CA6BF1CAE277E001B1884 /* LaunchScreen.storyboard in Resources */, 154 | 666CA6BC1CAE277E001B1884 /* Assets.xcassets in Resources */, 155 | ); 156 | runOnlyForDeploymentPostprocessing = 0; 157 | }; 158 | /* End PBXResourcesBuildPhase section */ 159 | 160 | /* Begin PBXShellScriptBuildPhase section */ 161 | 0B6A648A1B04FF2E118E2247 /* [CP] Check Pods Manifest.lock */ = { 162 | isa = PBXShellScriptBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | ); 166 | inputPaths = ( 167 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 168 | "${PODS_ROOT}/Manifest.lock", 169 | ); 170 | name = "[CP] Check Pods Manifest.lock"; 171 | outputPaths = ( 172 | "$(DERIVED_FILE_DIR)/Pods-Catalog-Catalog-checkManifestLockResult.txt", 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | shellPath = /bin/sh; 176 | 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"; 177 | showEnvVarsInLog = 0; 178 | }; 179 | 526A63F3210C836C577F3F5D /* [CP] Copy Pods Resources */ = { 180 | isa = PBXShellScriptBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | ); 184 | inputPaths = ( 185 | ); 186 | name = "[CP] Copy Pods Resources"; 187 | outputPaths = ( 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | shellPath = /bin/sh; 191 | shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-Catalog-Catalog/Pods-Catalog-Catalog-resources.sh\"\n"; 192 | showEnvVarsInLog = 0; 193 | }; 194 | B3B7B96A36CDFE60760F0D51 /* [CP] Embed Pods Frameworks */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | "${SRCROOT}/../Pods/Target Support Files/Pods-Catalog-Catalog/Pods-Catalog-Catalog-frameworks.sh", 201 | "${BUILT_PRODUCTS_DIR}/CatalogByConvention/CatalogByConvention.framework", 202 | "${BUILT_PRODUCTS_DIR}/CatalogExamples/CatalogExamples.framework", 203 | "${BUILT_PRODUCTS_DIR}/Resistor/Resistor.framework", 204 | ); 205 | name = "[CP] Embed Pods Frameworks"; 206 | outputPaths = ( 207 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CatalogByConvention.framework", 208 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CatalogExamples.framework", 209 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Resistor.framework", 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | shellPath = /bin/sh; 213 | shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-Catalog-Catalog/Pods-Catalog-Catalog-frameworks.sh\"\n"; 214 | showEnvVarsInLog = 0; 215 | }; 216 | /* End PBXShellScriptBuildPhase section */ 217 | 218 | /* Begin PBXSourcesBuildPhase section */ 219 | 666CA6AD1CAE277E001B1884 /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 666CA6B51CAE277E001B1884 /* AppDelegate.swift in Sources */, 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXSourcesBuildPhase section */ 228 | 229 | /* Begin PBXVariantGroup section */ 230 | 666CA6BD1CAE277E001B1884 /* LaunchScreen.storyboard */ = { 231 | isa = PBXVariantGroup; 232 | children = ( 233 | 666CA6BE1CAE277E001B1884 /* Base */, 234 | ); 235 | name = LaunchScreen.storyboard; 236 | sourceTree = ""; 237 | }; 238 | /* End PBXVariantGroup section */ 239 | 240 | /* Begin XCBuildConfiguration section */ 241 | 666CA6C11CAE277E001B1884 /* Debug */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | ALWAYS_SEARCH_USER_PATHS = NO; 245 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 246 | CLANG_CXX_LIBRARY = "libc++"; 247 | CLANG_ENABLE_MODULES = YES; 248 | CLANG_ENABLE_OBJC_ARC = YES; 249 | CLANG_WARN_BOOL_CONVERSION = YES; 250 | CLANG_WARN_CONSTANT_CONVERSION = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 261 | COPY_PHASE_STRIP = NO; 262 | DEBUG_INFORMATION_FORMAT = dwarf; 263 | ENABLE_STRICT_OBJC_MSGSEND = YES; 264 | ENABLE_TESTABILITY = YES; 265 | GCC_C_LANGUAGE_STANDARD = gnu99; 266 | GCC_DYNAMIC_NO_PIC = NO; 267 | GCC_NO_COMMON_BLOCKS = YES; 268 | GCC_OPTIMIZATION_LEVEL = 0; 269 | GCC_PREPROCESSOR_DEFINITIONS = ( 270 | "DEBUG=1", 271 | "$(inherited)", 272 | ); 273 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 274 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 275 | GCC_WARN_UNDECLARED_SELECTOR = YES; 276 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 277 | GCC_WARN_UNUSED_FUNCTION = YES; 278 | GCC_WARN_UNUSED_VARIABLE = YES; 279 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 280 | MTL_ENABLE_DEBUG_INFO = YES; 281 | ONLY_ACTIVE_ARCH = YES; 282 | SDKROOT = iphoneos; 283 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 284 | TARGETED_DEVICE_FAMILY = "1,2"; 285 | }; 286 | name = Debug; 287 | }; 288 | 666CA6C21CAE277E001B1884 /* Release */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ALWAYS_SEARCH_USER_PATHS = NO; 292 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 293 | CLANG_CXX_LIBRARY = "libc++"; 294 | CLANG_ENABLE_MODULES = YES; 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | CLANG_WARN_BOOL_CONVERSION = YES; 297 | CLANG_WARN_CONSTANT_CONVERSION = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_EMPTY_BODY = YES; 300 | CLANG_WARN_ENUM_CONVERSION = YES; 301 | CLANG_WARN_INFINITE_RECURSION = YES; 302 | CLANG_WARN_INT_CONVERSION = YES; 303 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 304 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 305 | CLANG_WARN_UNREACHABLE_CODE = YES; 306 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 307 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 308 | COPY_PHASE_STRIP = NO; 309 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 310 | ENABLE_NS_ASSERTIONS = NO; 311 | ENABLE_STRICT_OBJC_MSGSEND = YES; 312 | GCC_C_LANGUAGE_STANDARD = gnu99; 313 | GCC_NO_COMMON_BLOCKS = YES; 314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 316 | GCC_WARN_UNDECLARED_SELECTOR = YES; 317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 318 | GCC_WARN_UNUSED_FUNCTION = YES; 319 | GCC_WARN_UNUSED_VARIABLE = YES; 320 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 321 | MTL_ENABLE_DEBUG_INFO = NO; 322 | SDKROOT = iphoneos; 323 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 324 | TARGETED_DEVICE_FAMILY = "1,2"; 325 | VALIDATE_PRODUCT = YES; 326 | }; 327 | name = Release; 328 | }; 329 | 666CA6C41CAE277E001B1884 /* Debug */ = { 330 | isa = XCBuildConfiguration; 331 | baseConfigurationReference = 44BC83A9EA60CDCD8C1D2BC4 /* Pods-Catalog-Catalog.debug.xcconfig */; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | INFOPLIST_FILE = Catalog/Info.plist; 335 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 336 | PRODUCT_BUNDLE_IDENTIFIER = com.google.Catalog; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | SWIFT_VERSION = 3.0; 339 | }; 340 | name = Debug; 341 | }; 342 | 666CA6C51CAE277E001B1884 /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | baseConfigurationReference = DA69943629622BF33AA4667D /* Pods-Catalog-Catalog.release.xcconfig */; 345 | buildSettings = { 346 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 347 | INFOPLIST_FILE = Catalog/Info.plist; 348 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 349 | PRODUCT_BUNDLE_IDENTIFIER = com.google.Catalog; 350 | PRODUCT_NAME = "$(TARGET_NAME)"; 351 | SWIFT_VERSION = 3.0; 352 | }; 353 | name = Release; 354 | }; 355 | /* End XCBuildConfiguration section */ 356 | 357 | /* Begin XCConfigurationList section */ 358 | 666CA6AC1CAE277E001B1884 /* Build configuration list for PBXProject "Catalog" */ = { 359 | isa = XCConfigurationList; 360 | buildConfigurations = ( 361 | 666CA6C11CAE277E001B1884 /* Debug */, 362 | 666CA6C21CAE277E001B1884 /* Release */, 363 | ); 364 | defaultConfigurationIsVisible = 0; 365 | defaultConfigurationName = Release; 366 | }; 367 | 666CA6C31CAE277E001B1884 /* Build configuration list for PBXNativeTarget "Catalog" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | 666CA6C41CAE277E001B1884 /* Debug */, 371 | 666CA6C51CAE277E001B1884 /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | /* End XCConfigurationList section */ 377 | }; 378 | rootObject = 666CA6A91CAE277E001B1884 /* Project object */; 379 | } 380 | --------------------------------------------------------------------------------