├── .gitignore ├── .slather.yml ├── .swift-version ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Demo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── SectionScrubber-iOS.xcscheme │ ├── SectionScrubber-tvOS.xcscheme │ ├── Tests.xcscheme │ ├── iOS.xcscheme │ └── tvOS.xcscheme ├── FrameworkInfo.plist ├── GitHub └── demo.gif ├── Info.plist ├── LICENSE.md ├── Library ├── AppDelegate.swift ├── Images.xcassets │ ├── 0.imageset │ │ ├── 0.jpg │ │ └── Contents.json │ ├── 1.imageset │ │ ├── 1.jpg │ │ └── Contents.json │ ├── 2.imageset │ │ ├── 2.jpg │ │ └── Contents.json │ ├── 3.imageset │ │ ├── 3.jpg │ │ └── Contents.json │ ├── 4.imageset │ │ ├── 4.jpg │ │ └── Contents.json │ ├── 5.imageset │ │ ├── 5.png │ │ └── Contents.json │ ├── Contents.json │ └── clear.imageset │ │ ├── Contents.json │ │ └── clear.png ├── Photo.swift ├── PhotoCell.swift ├── PhotosCollectionLayout.swift ├── RemoteCollectionController.swift └── SectionHeader.swift ├── Package.swift ├── README.md ├── Resources └── Resources.xcassets │ ├── Arrows.imageset │ ├── Arrows.png │ ├── Arrows@2x.png │ ├── Arrows@3x.png │ └── Contents.json │ └── Contents.json ├── SectionScrubber.podspec ├── Sources ├── SectionLabel.swift ├── SectionScrubber.h └── SectionScrubber.swift ├── Tests ├── Info.plist └── Tests.swift ├── iOS ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 167.png │ │ ├── 29.png │ │ ├── 29@2x-1.png │ │ ├── 29@2x.png │ │ ├── 29@3x.png │ │ ├── 40.png │ │ ├── 40@2x-1.png │ │ ├── 40@2x.png │ │ ├── 40@3x.png │ │ ├── 60@2x.png │ │ ├── 60@3x.png │ │ ├── 76.png │ │ ├── 76@2x.png │ │ └── Contents.json │ ├── Contents.json │ └── video-indicator.imageset │ │ ├── Contents.json │ │ ├── video-indicator.png │ │ ├── video-indicator@2x.png │ │ └── video-indicator@3x.png ├── Base.lproj │ └── LaunchScreen.storyboard └── Info.plist └── tvOS ├── Assets.xcassets ├── App Icon & Top Shelf Image.brandassets │ ├── App Icon - Large.imagestack │ │ ├── Back.imagestacklayer │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Front.imagestacklayer │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ └── Middle.imagestacklayer │ │ │ ├── Content.imageset │ │ │ └── Contents.json │ │ │ └── Contents.json │ ├── App Icon - Small.imagestack │ │ ├── Back.imagestacklayer │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Front.imagestacklayer │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ └── Middle.imagestacklayer │ │ │ ├── Content.imageset │ │ │ └── Contents.json │ │ │ └── Contents.json │ ├── Contents.json │ ├── Top Shelf Image Wide.imageset │ │ └── Contents.json │ └── Top Shelf Image.imageset │ │ └── Contents.json ├── Contents.json └── LaunchImage.launchimage │ └── Contents.json └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | 10 | # Xcode 11 | # 12 | build/ 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | *.xccheckout 23 | *.moved-aside 24 | DerivedData 25 | *.hmap 26 | *.ipa 27 | *.xcuserstate 28 | *.xcscmblueprint 29 | *.gcno 30 | *.gcda 31 | 32 | # CocoaPods 33 | Pods 34 | 35 | # Carthage 36 | Checkouts 37 | 38 | .idea/.name 39 | 40 | .idea/DateScrubber.iml 41 | 42 | .idea/inspectionProfiles/profiles_settings.xml 43 | 44 | .idea/inspectionProfiles/Project_Default.xml 45 | 46 | .idea/misc.xml 47 | 48 | .idea/modules.xml 49 | 50 | .idea/runConfigurations/iOS.xml 51 | 52 | .idea/runConfigurations/Tests.xml 53 | 54 | .idea/vcs.xml 55 | 56 | .idea/workspace.xml 57 | 58 | .idea/xcode.xml 59 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | ci_service: travis_ci 2 | coverage_service: coveralls 3 | xcodeproj: Demo.xcodeproj 4 | source_directory: Source 5 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Check https://github.com/bakkenbaeck/SectionScrubber/releases for more information. 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | GitHub Issues is for reporting bugs, discussing features and general feedback in **SectionScrubber**. Be sure to check our [documentation](http://cocoadocs.org/docsets/SectionScrubber), [FAQ](https://github.com/bakkenbaeck/SectionScrubber/blob/master/README.md#faq) and [past issues](https://github.com/bakkenbaeck/SectionScrubber/issues?state=closed) before opening any new issues. 2 | 3 | If you are posting about a crash in your application, a stack trace is helpful, but additional context, in the form of code and explanation, is necessary to be of any use. 4 | 5 | 6 | -------------------------------------------------------------------------------- /Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 14036DD01E8BE9A5005FCBB2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14036DCF1E8BE9A5005FCBB2 /* Assets.xcassets */; }; 11 | 14B905021E8BEF1400214247 /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B904FD1E8BEF1400214247 /* Photo.swift */; }; 12 | 14B905031E8BEF1400214247 /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B904FD1E8BEF1400214247 /* Photo.swift */; }; 13 | 14B905051E8BEF1400214247 /* PhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B904FE1E8BEF1400214247 /* PhotoCell.swift */; }; 14 | 14B905061E8BEF1400214247 /* PhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B904FE1E8BEF1400214247 /* PhotoCell.swift */; }; 15 | 14B905081E8BEF1400214247 /* RemoteCollectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B904FF1E8BEF1400214247 /* RemoteCollectionController.swift */; }; 16 | 14B905091E8BEF1400214247 /* RemoteCollectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B904FF1E8BEF1400214247 /* RemoteCollectionController.swift */; }; 17 | 14B9050B1E8BEF1400214247 /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B905001E8BEF1400214247 /* SectionHeader.swift */; }; 18 | 14B9050C1E8BEF1400214247 /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B905001E8BEF1400214247 /* SectionHeader.swift */; }; 19 | 14BDCD4E1E939D5500FD41A0 /* SectionScrubber.h in Headers */ = {isa = PBXBuildFile; fileRef = 14BDCD4D1E939D5500FD41A0 /* SectionScrubber.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | 14BDCD4F1E939D5500FD41A0 /* SectionScrubber.h in Headers */ = {isa = PBXBuildFile; fileRef = 14BDCD4D1E939D5500FD41A0 /* SectionScrubber.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21 | 14BDCD531E939E0E00FD41A0 /* SectionScrubber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140156D41D00733A00424CBB /* SectionScrubber.swift */; }; 22 | 14BDCD541E939E0E00FD41A0 /* SectionLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E61D29A01D05BDB5007C5DF2 /* SectionLabel.swift */; }; 23 | 14BDCD551E939E1100FD41A0 /* SectionScrubber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140156D41D00733A00424CBB /* SectionScrubber.swift */; }; 24 | 14BDCD561E939E1100FD41A0 /* SectionLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E61D29A01D05BDB5007C5DF2 /* SectionLabel.swift */; }; 25 | 14BDCD571E939E3300FD41A0 /* SectionScrubber.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14BDCD381E939CEE00FD41A0 /* SectionScrubber.framework */; }; 26 | 14BDCD5B1E939FC900FD41A0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14BDCD5A1E939FC900FD41A0 /* Images.xcassets */; }; 27 | 14BDCD5C1E939FC900FD41A0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14BDCD5A1E939FC900FD41A0 /* Images.xcassets */; }; 28 | 14BDCD611E93A05800FD41A0 /* Resources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14BDCD601E93A05800FD41A0 /* Resources.xcassets */; }; 29 | 14BDCD621E93A05800FD41A0 /* Resources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14BDCD601E93A05800FD41A0 /* Resources.xcassets */; }; 30 | 14C984021C0DE79200B5E515 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14C984011C0DE79200B5E515 /* Assets.xcassets */; }; 31 | 14C984051C0DE79200B5E515 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 14C984031C0DE79200B5E515 /* LaunchScreen.storyboard */; }; 32 | 14DB48381E8BF620008B6E30 /* PhotosCollectionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14DB48361E8BF620008B6E30 /* PhotosCollectionLayout.swift */; }; 33 | 14DB48391E8BF620008B6E30 /* PhotosCollectionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14DB48361E8BF620008B6E30 /* PhotosCollectionLayout.swift */; }; 34 | 14DB483C1E8BF853008B6E30 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14DB483A1E8BF853008B6E30 /* AppDelegate.swift */; }; 35 | 14DB483D1E8BF853008B6E30 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14DB483A1E8BF853008B6E30 /* AppDelegate.swift */; }; 36 | 14EE765B1E93B586007DE87D /* SectionScrubber.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14BDCD381E939CEE00FD41A0 /* SectionScrubber.framework */; }; 37 | 14EE765C1E93B586007DE87D /* SectionScrubber.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 14BDCD381E939CEE00FD41A0 /* SectionScrubber.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 38 | 14EE76651E93B5D8007DE87D /* SectionScrubber.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14BDCD451E939CFE00FD41A0 /* SectionScrubber.framework */; }; 39 | 14EE76661E93B5D8007DE87D /* SectionScrubber.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 14BDCD451E939CFE00FD41A0 /* SectionScrubber.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 40 | 14EE76671E93B689007DE87D /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A139B31AEFC72B00AD732F /* Tests.swift */; }; 41 | /* End PBXBuildFile section */ 42 | 43 | /* Begin PBXContainerItemProxy section */ 44 | 14EE765D1E93B586007DE87D /* PBXContainerItemProxy */ = { 45 | isa = PBXContainerItemProxy; 46 | containerPortal = 146D728B1AB782920058798C /* Project object */; 47 | proxyType = 1; 48 | remoteGlobalIDString = 14BDCD371E939CEE00FD41A0; 49 | remoteInfo = "SectionScrubber-iOS"; 50 | }; 51 | 14EE76621E93B5A4007DE87D /* PBXContainerItemProxy */ = { 52 | isa = PBXContainerItemProxy; 53 | containerPortal = 146D728B1AB782920058798C /* Project object */; 54 | proxyType = 1; 55 | remoteGlobalIDString = 14BDCD441E939CFE00FD41A0; 56 | remoteInfo = "SectionScrubber-tvOS"; 57 | }; 58 | /* End PBXContainerItemProxy section */ 59 | 60 | /* Begin PBXCopyFilesBuildPhase section */ 61 | 14EE765F1E93B586007DE87D /* Embed Frameworks */ = { 62 | isa = PBXCopyFilesBuildPhase; 63 | buildActionMask = 2147483647; 64 | dstPath = ""; 65 | dstSubfolderSpec = 10; 66 | files = ( 67 | 14EE765C1E93B586007DE87D /* SectionScrubber.framework in Embed Frameworks */, 68 | ); 69 | name = "Embed Frameworks"; 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | 14EE76641E93B5A4007DE87D /* Embed Frameworks */ = { 73 | isa = PBXCopyFilesBuildPhase; 74 | buildActionMask = 2147483647; 75 | dstPath = ""; 76 | dstSubfolderSpec = 10; 77 | files = ( 78 | 14EE76661E93B5D8007DE87D /* SectionScrubber.framework in Embed Frameworks */, 79 | ); 80 | name = "Embed Frameworks"; 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXCopyFilesBuildPhase section */ 84 | 85 | /* Begin PBXFileReference section */ 86 | 140156D41D00733A00424CBB /* SectionScrubber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionScrubber.swift; sourceTree = ""; }; 87 | 14036DC61E8BE9A5005FCBB2 /* tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | 14036DCF1E8BE9A5005FCBB2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 89 | 14036DD11E8BE9A5005FCBB2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 90 | 146D72AC1AB782920058798C /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 91 | 146D72B11AB782920058798C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 92 | 14A139B31AEFC72B00AD732F /* Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 93 | 14B904FD1E8BEF1400214247 /* Photo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = ""; }; 94 | 14B904FE1E8BEF1400214247 /* PhotoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCell.swift; sourceTree = ""; }; 95 | 14B904FF1E8BEF1400214247 /* RemoteCollectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteCollectionController.swift; sourceTree = ""; }; 96 | 14B905001E8BEF1400214247 /* SectionHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionHeader.swift; sourceTree = ""; }; 97 | 14BDCD381E939CEE00FD41A0 /* SectionScrubber.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SectionScrubber.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 98 | 14BDCD451E939CFE00FD41A0 /* SectionScrubber.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SectionScrubber.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | 14BDCD4D1E939D5500FD41A0 /* SectionScrubber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SectionScrubber.h; sourceTree = ""; }; 100 | 14BDCD5A1E939FC900FD41A0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 101 | 14BDCD5D1E93A00D00FD41A0 /* FrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = FrameworkInfo.plist; sourceTree = ""; }; 102 | 14BDCD601E93A05800FD41A0 /* Resources.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Resources.xcassets; sourceTree = ""; }; 103 | 14C0AF7F1BD6D4230009ECBE /* .slather.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .slather.yml; sourceTree = ""; }; 104 | 14C0AF811BD6D4230009ECBE /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 105 | 14C0AF821BD6D4230009ECBE /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; 106 | 14C0AF831BD6D4230009ECBE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 107 | 14C983FD1C0DE79200B5E515 /* iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 108 | 14C984011C0DE79200B5E515 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 109 | 14C984041C0DE79200B5E515 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 110 | 14C984061C0DE79200B5E515 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 111 | 14DB48361E8BF620008B6E30 /* PhotosCollectionLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosCollectionLayout.swift; sourceTree = ""; }; 112 | 14DB483A1E8BF853008B6E30 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 113 | 189BAD888FD0F3E10BD13B5F /* Pods_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 114 | E61D29A01D05BDB5007C5DF2 /* SectionLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionLabel.swift; sourceTree = ""; }; 115 | /* End PBXFileReference section */ 116 | 117 | /* Begin PBXFrameworksBuildPhase section */ 118 | 14036DC31E8BE9A5005FCBB2 /* Frameworks */ = { 119 | isa = PBXFrameworksBuildPhase; 120 | buildActionMask = 2147483647; 121 | files = ( 122 | 14EE76651E93B5D8007DE87D /* SectionScrubber.framework in Frameworks */, 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | 146D72A91AB782920058798C /* Frameworks */ = { 127 | isa = PBXFrameworksBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 14BDCD571E939E3300FD41A0 /* SectionScrubber.framework in Frameworks */, 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | 14BDCD341E939CEE00FD41A0 /* Frameworks */ = { 135 | isa = PBXFrameworksBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | 14BDCD411E939CFE00FD41A0 /* Frameworks */ = { 142 | isa = PBXFrameworksBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | 14C983FA1C0DE79200B5E515 /* Frameworks */ = { 149 | isa = PBXFrameworksBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | 14EE765B1E93B586007DE87D /* SectionScrubber.framework in Frameworks */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXFrameworksBuildPhase section */ 157 | 158 | /* Begin PBXGroup section */ 159 | 140156D31D00733A00424CBB /* Sources */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 14BDCD4D1E939D5500FD41A0 /* SectionScrubber.h */, 163 | 140156D41D00733A00424CBB /* SectionScrubber.swift */, 164 | E61D29A01D05BDB5007C5DF2 /* SectionLabel.swift */, 165 | ); 166 | path = Sources; 167 | sourceTree = ""; 168 | }; 169 | 14036DC71E8BE9A5005FCBB2 /* tvOS */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 14036DCF1E8BE9A5005FCBB2 /* Assets.xcassets */, 173 | 14036DD11E8BE9A5005FCBB2 /* Info.plist */, 174 | ); 175 | path = tvOS; 176 | sourceTree = ""; 177 | }; 178 | 146D728A1AB782920058798C = { 179 | isa = PBXGroup; 180 | children = ( 181 | 14B9050F1E8BEF5B00214247 /* Resources */, 182 | 14B904FC1E8BEF1400214247 /* Library */, 183 | 140156D31D00733A00424CBB /* Sources */, 184 | 14C136501AB7849300B7B07A /* Metadata */, 185 | 146D72AF1AB782920058798C /* Tests */, 186 | 14C983FE1C0DE79200B5E515 /* iOS */, 187 | 14036DC71E8BE9A5005FCBB2 /* tvOS */, 188 | 146D72941AB782920058798C /* Products */, 189 | 3C22AA8E541868C0436D5FC3 /* Frameworks */, 190 | ); 191 | indentWidth = 4; 192 | sourceTree = ""; 193 | tabWidth = 4; 194 | }; 195 | 146D72941AB782920058798C /* Products */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | 146D72AC1AB782920058798C /* Tests.xctest */, 199 | 14C983FD1C0DE79200B5E515 /* iOS.app */, 200 | 14036DC61E8BE9A5005FCBB2 /* tvOS.app */, 201 | 14BDCD381E939CEE00FD41A0 /* SectionScrubber.framework */, 202 | 14BDCD451E939CFE00FD41A0 /* SectionScrubber.framework */, 203 | ); 204 | name = Products; 205 | sourceTree = ""; 206 | }; 207 | 146D72AF1AB782920058798C /* Tests */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 14A139B31AEFC72B00AD732F /* Tests.swift */, 211 | 146D72B01AB782920058798C /* Supporting Files */, 212 | ); 213 | path = Tests; 214 | sourceTree = ""; 215 | }; 216 | 146D72B01AB782920058798C /* Supporting Files */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | 146D72B11AB782920058798C /* Info.plist */, 220 | ); 221 | name = "Supporting Files"; 222 | sourceTree = ""; 223 | }; 224 | 14B904FC1E8BEF1400214247 /* Library */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | 14DB483A1E8BF853008B6E30 /* AppDelegate.swift */, 228 | 14B904FD1E8BEF1400214247 /* Photo.swift */, 229 | 14B904FE1E8BEF1400214247 /* PhotoCell.swift */, 230 | 14B904FF1E8BEF1400214247 /* RemoteCollectionController.swift */, 231 | 14B905001E8BEF1400214247 /* SectionHeader.swift */, 232 | 14DB48361E8BF620008B6E30 /* PhotosCollectionLayout.swift */, 233 | 14BDCD5A1E939FC900FD41A0 /* Images.xcassets */, 234 | ); 235 | path = Library; 236 | sourceTree = ""; 237 | }; 238 | 14B9050F1E8BEF5B00214247 /* Resources */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | 14BDCD601E93A05800FD41A0 /* Resources.xcassets */, 242 | ); 243 | path = Resources; 244 | sourceTree = ""; 245 | }; 246 | 14C136501AB7849300B7B07A /* Metadata */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | 14BDCD5D1E93A00D00FD41A0 /* FrameworkInfo.plist */, 250 | 14C0AF7F1BD6D4230009ECBE /* .slather.yml */, 251 | 14C0AF811BD6D4230009ECBE /* CHANGELOG.md */, 252 | 14C0AF821BD6D4230009ECBE /* CONTRIBUTING.md */, 253 | 14C0AF831BD6D4230009ECBE /* README.md */, 254 | ); 255 | name = Metadata; 256 | sourceTree = ""; 257 | }; 258 | 14C983FE1C0DE79200B5E515 /* iOS */ = { 259 | isa = PBXGroup; 260 | children = ( 261 | 14C984011C0DE79200B5E515 /* Assets.xcassets */, 262 | 14C984031C0DE79200B5E515 /* LaunchScreen.storyboard */, 263 | 14C984061C0DE79200B5E515 /* Info.plist */, 264 | ); 265 | path = iOS; 266 | sourceTree = ""; 267 | }; 268 | 3C22AA8E541868C0436D5FC3 /* Frameworks */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | 189BAD888FD0F3E10BD13B5F /* Pods_iOS.framework */, 272 | ); 273 | name = Frameworks; 274 | sourceTree = ""; 275 | }; 276 | /* End PBXGroup section */ 277 | 278 | /* Begin PBXHeadersBuildPhase section */ 279 | 14BDCD351E939CEE00FD41A0 /* Headers */ = { 280 | isa = PBXHeadersBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | 14BDCD4E1E939D5500FD41A0 /* SectionScrubber.h in Headers */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | 14BDCD421E939CFE00FD41A0 /* Headers */ = { 288 | isa = PBXHeadersBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | 14BDCD4F1E939D5500FD41A0 /* SectionScrubber.h in Headers */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXHeadersBuildPhase section */ 296 | 297 | /* Begin PBXNativeTarget section */ 298 | 14036DC51E8BE9A5005FCBB2 /* tvOS */ = { 299 | isa = PBXNativeTarget; 300 | buildConfigurationList = 14036DD41E8BE9A5005FCBB2 /* Build configuration list for PBXNativeTarget "tvOS" */; 301 | buildPhases = ( 302 | 14036DC21E8BE9A5005FCBB2 /* Sources */, 303 | 14036DC31E8BE9A5005FCBB2 /* Frameworks */, 304 | 14036DC41E8BE9A5005FCBB2 /* Resources */, 305 | 14EE76641E93B5A4007DE87D /* Embed Frameworks */, 306 | ); 307 | buildRules = ( 308 | ); 309 | dependencies = ( 310 | 14EE76631E93B5A4007DE87D /* PBXTargetDependency */, 311 | ); 312 | name = tvOS; 313 | productName = tvOS; 314 | productReference = 14036DC61E8BE9A5005FCBB2 /* tvOS.app */; 315 | productType = "com.apple.product-type.application"; 316 | }; 317 | 146D72AB1AB782920058798C /* Tests */ = { 318 | isa = PBXNativeTarget; 319 | buildConfigurationList = 146D72B91AB782920058798C /* Build configuration list for PBXNativeTarget "Tests" */; 320 | buildPhases = ( 321 | 146D72A81AB782920058798C /* Sources */, 322 | 146D72A91AB782920058798C /* Frameworks */, 323 | 146D72AA1AB782920058798C /* Resources */, 324 | ); 325 | buildRules = ( 326 | ); 327 | dependencies = ( 328 | ); 329 | name = Tests; 330 | productName = PodTests; 331 | productReference = 146D72AC1AB782920058798C /* Tests.xctest */; 332 | productType = "com.apple.product-type.bundle.unit-test"; 333 | }; 334 | 14BDCD371E939CEE00FD41A0 /* SectionScrubber-iOS */ = { 335 | isa = PBXNativeTarget; 336 | buildConfigurationList = 14BDCD3F1E939CEE00FD41A0 /* Build configuration list for PBXNativeTarget "SectionScrubber-iOS" */; 337 | buildPhases = ( 338 | 14BDCD331E939CEE00FD41A0 /* Sources */, 339 | 14BDCD341E939CEE00FD41A0 /* Frameworks */, 340 | 14BDCD351E939CEE00FD41A0 /* Headers */, 341 | 14BDCD361E939CEE00FD41A0 /* Resources */, 342 | ); 343 | buildRules = ( 344 | ); 345 | dependencies = ( 346 | ); 347 | name = "SectionScrubber-iOS"; 348 | productName = "SectionScrubber-iOS"; 349 | productReference = 14BDCD381E939CEE00FD41A0 /* SectionScrubber.framework */; 350 | productType = "com.apple.product-type.framework"; 351 | }; 352 | 14BDCD441E939CFE00FD41A0 /* SectionScrubber-tvOS */ = { 353 | isa = PBXNativeTarget; 354 | buildConfigurationList = 14BDCD4A1E939CFF00FD41A0 /* Build configuration list for PBXNativeTarget "SectionScrubber-tvOS" */; 355 | buildPhases = ( 356 | 14BDCD401E939CFE00FD41A0 /* Sources */, 357 | 14BDCD411E939CFE00FD41A0 /* Frameworks */, 358 | 14BDCD421E939CFE00FD41A0 /* Headers */, 359 | 14BDCD431E939CFE00FD41A0 /* Resources */, 360 | ); 361 | buildRules = ( 362 | ); 363 | dependencies = ( 364 | ); 365 | name = "SectionScrubber-tvOS"; 366 | productName = "SectionScrubber-tvOS"; 367 | productReference = 14BDCD451E939CFE00FD41A0 /* SectionScrubber.framework */; 368 | productType = "com.apple.product-type.framework"; 369 | }; 370 | 14C983FC1C0DE79200B5E515 /* iOS */ = { 371 | isa = PBXNativeTarget; 372 | buildConfigurationList = 14C984091C0DE79200B5E515 /* Build configuration list for PBXNativeTarget "iOS" */; 373 | buildPhases = ( 374 | 14C983F91C0DE79200B5E515 /* Sources */, 375 | 14C983FA1C0DE79200B5E515 /* Frameworks */, 376 | 14C983FB1C0DE79200B5E515 /* Resources */, 377 | 14EE765F1E93B586007DE87D /* Embed Frameworks */, 378 | ); 379 | buildRules = ( 380 | ); 381 | dependencies = ( 382 | 14EE765E1E93B586007DE87D /* PBXTargetDependency */, 383 | ); 384 | name = iOS; 385 | productName = iOS; 386 | productReference = 14C983FD1C0DE79200B5E515 /* iOS.app */; 387 | productType = "com.apple.product-type.application"; 388 | }; 389 | /* End PBXNativeTarget section */ 390 | 391 | /* Begin PBXProject section */ 392 | 146D728B1AB782920058798C /* Project object */ = { 393 | isa = PBXProject; 394 | attributes = { 395 | CLASSPREFIX = ""; 396 | LastSwiftUpdateCheck = 0830; 397 | LastUpgradeCheck = 1200; 398 | ORGANIZATIONNAME = ""; 399 | TargetAttributes = { 400 | 14036DC51E8BE9A5005FCBB2 = { 401 | CreatedOnToolsVersion = 8.3; 402 | DevelopmentTeam = 29X3GG489T; 403 | LastSwiftMigration = 1200; 404 | ProvisioningStyle = Automatic; 405 | }; 406 | 146D72AB1AB782920058798C = { 407 | CreatedOnToolsVersion = 6.2; 408 | DevelopmentTeam = 29X3GG489T; 409 | LastSwiftMigration = 1200; 410 | }; 411 | 14BDCD371E939CEE00FD41A0 = { 412 | CreatedOnToolsVersion = 8.3; 413 | DevelopmentTeam = 29X3GG489T; 414 | LastSwiftMigration = 1200; 415 | ProvisioningStyle = Automatic; 416 | }; 417 | 14BDCD441E939CFE00FD41A0 = { 418 | CreatedOnToolsVersion = 8.3; 419 | DevelopmentTeam = 29X3GG489T; 420 | LastSwiftMigration = 1200; 421 | ProvisioningStyle = Automatic; 422 | }; 423 | 14C983FC1C0DE79200B5E515 = { 424 | CreatedOnToolsVersion = 7.2; 425 | DevelopmentTeam = 29X3GG489T; 426 | LastSwiftMigration = 1200; 427 | ProvisioningStyle = Automatic; 428 | }; 429 | }; 430 | }; 431 | buildConfigurationList = 146D728E1AB782920058798C /* Build configuration list for PBXProject "Demo" */; 432 | compatibilityVersion = "Xcode 3.2"; 433 | developmentRegion = en; 434 | hasScannedForEncodings = 0; 435 | knownRegions = ( 436 | en, 437 | Base, 438 | ); 439 | mainGroup = 146D728A1AB782920058798C; 440 | productRefGroup = 146D72941AB782920058798C /* Products */; 441 | projectDirPath = ""; 442 | projectRoot = ""; 443 | targets = ( 444 | 146D72AB1AB782920058798C /* Tests */, 445 | 14C983FC1C0DE79200B5E515 /* iOS */, 446 | 14036DC51E8BE9A5005FCBB2 /* tvOS */, 447 | 14BDCD371E939CEE00FD41A0 /* SectionScrubber-iOS */, 448 | 14BDCD441E939CFE00FD41A0 /* SectionScrubber-tvOS */, 449 | ); 450 | }; 451 | /* End PBXProject section */ 452 | 453 | /* Begin PBXResourcesBuildPhase section */ 454 | 14036DC41E8BE9A5005FCBB2 /* Resources */ = { 455 | isa = PBXResourcesBuildPhase; 456 | buildActionMask = 2147483647; 457 | files = ( 458 | 14BDCD5C1E939FC900FD41A0 /* Images.xcassets in Resources */, 459 | 14036DD01E8BE9A5005FCBB2 /* Assets.xcassets in Resources */, 460 | ); 461 | runOnlyForDeploymentPostprocessing = 0; 462 | }; 463 | 146D72AA1AB782920058798C /* Resources */ = { 464 | isa = PBXResourcesBuildPhase; 465 | buildActionMask = 2147483647; 466 | files = ( 467 | ); 468 | runOnlyForDeploymentPostprocessing = 0; 469 | }; 470 | 14BDCD361E939CEE00FD41A0 /* Resources */ = { 471 | isa = PBXResourcesBuildPhase; 472 | buildActionMask = 2147483647; 473 | files = ( 474 | 14BDCD611E93A05800FD41A0 /* Resources.xcassets in Resources */, 475 | ); 476 | runOnlyForDeploymentPostprocessing = 0; 477 | }; 478 | 14BDCD431E939CFE00FD41A0 /* Resources */ = { 479 | isa = PBXResourcesBuildPhase; 480 | buildActionMask = 2147483647; 481 | files = ( 482 | 14BDCD621E93A05800FD41A0 /* Resources.xcassets in Resources */, 483 | ); 484 | runOnlyForDeploymentPostprocessing = 0; 485 | }; 486 | 14C983FB1C0DE79200B5E515 /* Resources */ = { 487 | isa = PBXResourcesBuildPhase; 488 | buildActionMask = 2147483647; 489 | files = ( 490 | 14BDCD5B1E939FC900FD41A0 /* Images.xcassets in Resources */, 491 | 14C984021C0DE79200B5E515 /* Assets.xcassets in Resources */, 492 | 14C984051C0DE79200B5E515 /* LaunchScreen.storyboard in Resources */, 493 | ); 494 | runOnlyForDeploymentPostprocessing = 0; 495 | }; 496 | /* End PBXResourcesBuildPhase section */ 497 | 498 | /* Begin PBXSourcesBuildPhase section */ 499 | 14036DC21E8BE9A5005FCBB2 /* Sources */ = { 500 | isa = PBXSourcesBuildPhase; 501 | buildActionMask = 2147483647; 502 | files = ( 503 | 14DB483D1E8BF853008B6E30 /* AppDelegate.swift in Sources */, 504 | 14B905031E8BEF1400214247 /* Photo.swift in Sources */, 505 | 14B905061E8BEF1400214247 /* PhotoCell.swift in Sources */, 506 | 14B9050C1E8BEF1400214247 /* SectionHeader.swift in Sources */, 507 | 14B905091E8BEF1400214247 /* RemoteCollectionController.swift in Sources */, 508 | 14DB48391E8BF620008B6E30 /* PhotosCollectionLayout.swift in Sources */, 509 | ); 510 | runOnlyForDeploymentPostprocessing = 0; 511 | }; 512 | 146D72A81AB782920058798C /* Sources */ = { 513 | isa = PBXSourcesBuildPhase; 514 | buildActionMask = 2147483647; 515 | files = ( 516 | 14EE76671E93B689007DE87D /* Tests.swift in Sources */, 517 | ); 518 | runOnlyForDeploymentPostprocessing = 0; 519 | }; 520 | 14BDCD331E939CEE00FD41A0 /* Sources */ = { 521 | isa = PBXSourcesBuildPhase; 522 | buildActionMask = 2147483647; 523 | files = ( 524 | 14BDCD531E939E0E00FD41A0 /* SectionScrubber.swift in Sources */, 525 | 14BDCD541E939E0E00FD41A0 /* SectionLabel.swift in Sources */, 526 | ); 527 | runOnlyForDeploymentPostprocessing = 0; 528 | }; 529 | 14BDCD401E939CFE00FD41A0 /* Sources */ = { 530 | isa = PBXSourcesBuildPhase; 531 | buildActionMask = 2147483647; 532 | files = ( 533 | 14BDCD551E939E1100FD41A0 /* SectionScrubber.swift in Sources */, 534 | 14BDCD561E939E1100FD41A0 /* SectionLabel.swift in Sources */, 535 | ); 536 | runOnlyForDeploymentPostprocessing = 0; 537 | }; 538 | 14C983F91C0DE79200B5E515 /* Sources */ = { 539 | isa = PBXSourcesBuildPhase; 540 | buildActionMask = 2147483647; 541 | files = ( 542 | 14DB483C1E8BF853008B6E30 /* AppDelegate.swift in Sources */, 543 | 14B905081E8BEF1400214247 /* RemoteCollectionController.swift in Sources */, 544 | 14B905021E8BEF1400214247 /* Photo.swift in Sources */, 545 | 14B905051E8BEF1400214247 /* PhotoCell.swift in Sources */, 546 | 14B9050B1E8BEF1400214247 /* SectionHeader.swift in Sources */, 547 | 14DB48381E8BF620008B6E30 /* PhotosCollectionLayout.swift in Sources */, 548 | ); 549 | runOnlyForDeploymentPostprocessing = 0; 550 | }; 551 | /* End PBXSourcesBuildPhase section */ 552 | 553 | /* Begin PBXTargetDependency section */ 554 | 14EE765E1E93B586007DE87D /* PBXTargetDependency */ = { 555 | isa = PBXTargetDependency; 556 | target = 14BDCD371E939CEE00FD41A0 /* SectionScrubber-iOS */; 557 | targetProxy = 14EE765D1E93B586007DE87D /* PBXContainerItemProxy */; 558 | }; 559 | 14EE76631E93B5A4007DE87D /* PBXTargetDependency */ = { 560 | isa = PBXTargetDependency; 561 | target = 14BDCD441E939CFE00FD41A0 /* SectionScrubber-tvOS */; 562 | targetProxy = 14EE76621E93B5A4007DE87D /* PBXContainerItemProxy */; 563 | }; 564 | /* End PBXTargetDependency section */ 565 | 566 | /* Begin PBXVariantGroup section */ 567 | 14C984031C0DE79200B5E515 /* LaunchScreen.storyboard */ = { 568 | isa = PBXVariantGroup; 569 | children = ( 570 | 14C984041C0DE79200B5E515 /* Base */, 571 | ); 572 | name = LaunchScreen.storyboard; 573 | sourceTree = ""; 574 | }; 575 | /* End PBXVariantGroup section */ 576 | 577 | /* Begin XCBuildConfiguration section */ 578 | 14036DD21E8BE9A5005FCBB2 /* Debug */ = { 579 | isa = XCBuildConfiguration; 580 | buildSettings = { 581 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 582 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 583 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 584 | CLANG_ANALYZER_NONNULL = YES; 585 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 586 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 587 | DEBUG_INFORMATION_FORMAT = dwarf; 588 | DEVELOPMENT_TEAM = 29X3GG489T; 589 | INFOPLIST_FILE = tvOS/Info.plist; 590 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 591 | PRODUCT_BUNDLE_IDENTIFIER = com.bakkenbaeck.tvOS; 592 | PRODUCT_NAME = "$(TARGET_NAME)"; 593 | SDKROOT = appletvos; 594 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 595 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 596 | SWIFT_VERSION = 5.0; 597 | TARGETED_DEVICE_FAMILY = 3; 598 | TVOS_DEPLOYMENT_TARGET = 12.0; 599 | }; 600 | name = Debug; 601 | }; 602 | 14036DD31E8BE9A5005FCBB2 /* Release */ = { 603 | isa = XCBuildConfiguration; 604 | buildSettings = { 605 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 606 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 607 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 608 | CLANG_ANALYZER_NONNULL = YES; 609 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 610 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 611 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 612 | DEVELOPMENT_TEAM = 29X3GG489T; 613 | INFOPLIST_FILE = tvOS/Info.plist; 614 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 615 | PRODUCT_BUNDLE_IDENTIFIER = com.bakkenbaeck.tvOS; 616 | PRODUCT_NAME = "$(TARGET_NAME)"; 617 | SDKROOT = appletvos; 618 | SWIFT_VERSION = 5.0; 619 | TARGETED_DEVICE_FAMILY = 3; 620 | TVOS_DEPLOYMENT_TARGET = 12.0; 621 | }; 622 | name = Release; 623 | }; 624 | 146D72B41AB782920058798C /* Debug */ = { 625 | isa = XCBuildConfiguration; 626 | buildSettings = { 627 | ALWAYS_SEARCH_USER_PATHS = NO; 628 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 629 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 630 | CLANG_CXX_LIBRARY = "libc++"; 631 | CLANG_ENABLE_MODULES = YES; 632 | CLANG_ENABLE_OBJC_ARC = YES; 633 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 634 | CLANG_WARN_BOOL_CONVERSION = YES; 635 | CLANG_WARN_COMMA = YES; 636 | CLANG_WARN_CONSTANT_CONVERSION = YES; 637 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 638 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 639 | CLANG_WARN_EMPTY_BODY = YES; 640 | CLANG_WARN_ENUM_CONVERSION = YES; 641 | CLANG_WARN_INFINITE_RECURSION = YES; 642 | CLANG_WARN_INT_CONVERSION = YES; 643 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 644 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 645 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 646 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 647 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 648 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 649 | CLANG_WARN_STRICT_PROTOTYPES = YES; 650 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 651 | CLANG_WARN_UNREACHABLE_CODE = YES; 652 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 653 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 654 | COPY_PHASE_STRIP = NO; 655 | ENABLE_STRICT_OBJC_MSGSEND = YES; 656 | ENABLE_TESTABILITY = YES; 657 | GCC_C_LANGUAGE_STANDARD = gnu99; 658 | GCC_DYNAMIC_NO_PIC = NO; 659 | GCC_NO_COMMON_BLOCKS = YES; 660 | GCC_OPTIMIZATION_LEVEL = 0; 661 | GCC_PREPROCESSOR_DEFINITIONS = ( 662 | "DEBUG=1", 663 | "$(inherited)", 664 | ); 665 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 666 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 667 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 668 | GCC_WARN_UNDECLARED_SELECTOR = YES; 669 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 670 | GCC_WARN_UNUSED_FUNCTION = YES; 671 | GCC_WARN_UNUSED_VARIABLE = YES; 672 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 673 | MTL_ENABLE_DEBUG_INFO = YES; 674 | ONLY_ACTIVE_ARCH = YES; 675 | SDKROOT = iphoneos; 676 | SWIFT_VERSION = 4.0; 677 | }; 678 | name = Debug; 679 | }; 680 | 146D72B51AB782920058798C /* Release */ = { 681 | isa = XCBuildConfiguration; 682 | buildSettings = { 683 | ALWAYS_SEARCH_USER_PATHS = NO; 684 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 685 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 686 | CLANG_CXX_LIBRARY = "libc++"; 687 | CLANG_ENABLE_MODULES = YES; 688 | CLANG_ENABLE_OBJC_ARC = YES; 689 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 690 | CLANG_WARN_BOOL_CONVERSION = YES; 691 | CLANG_WARN_COMMA = YES; 692 | CLANG_WARN_CONSTANT_CONVERSION = YES; 693 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 694 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 695 | CLANG_WARN_EMPTY_BODY = YES; 696 | CLANG_WARN_ENUM_CONVERSION = YES; 697 | CLANG_WARN_INFINITE_RECURSION = YES; 698 | CLANG_WARN_INT_CONVERSION = YES; 699 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 700 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 701 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 702 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 703 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 704 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 705 | CLANG_WARN_STRICT_PROTOTYPES = YES; 706 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 707 | CLANG_WARN_UNREACHABLE_CODE = YES; 708 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 709 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 710 | COPY_PHASE_STRIP = NO; 711 | ENABLE_NS_ASSERTIONS = NO; 712 | ENABLE_STRICT_OBJC_MSGSEND = YES; 713 | GCC_C_LANGUAGE_STANDARD = gnu99; 714 | GCC_NO_COMMON_BLOCKS = YES; 715 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 716 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 717 | GCC_WARN_UNDECLARED_SELECTOR = YES; 718 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 719 | GCC_WARN_UNUSED_FUNCTION = YES; 720 | GCC_WARN_UNUSED_VARIABLE = YES; 721 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 722 | MTL_ENABLE_DEBUG_INFO = NO; 723 | SDKROOT = iphoneos; 724 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 725 | SWIFT_VERSION = 4.0; 726 | VALIDATE_PRODUCT = YES; 727 | }; 728 | name = Release; 729 | }; 730 | 146D72BA1AB782920058798C /* Debug */ = { 731 | isa = XCBuildConfiguration; 732 | buildSettings = { 733 | CLANG_ENABLE_MODULES = YES; 734 | DEVELOPMENT_TEAM = 29X3GG489T; 735 | FRAMEWORK_SEARCH_PATHS = ""; 736 | GCC_PREPROCESSOR_DEFINITIONS = ( 737 | "DEBUG=1", 738 | "$(inherited)", 739 | ); 740 | INFOPLIST_FILE = Tests/Info.plist; 741 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 742 | PRODUCT_BUNDLE_IDENTIFIER = "com.sample.$(PRODUCT_NAME:rfc1034identifier)"; 743 | PRODUCT_NAME = "$(TARGET_NAME)"; 744 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 745 | SWIFT_VERSION = 5.0; 746 | }; 747 | name = Debug; 748 | }; 749 | 146D72BB1AB782920058798C /* Release */ = { 750 | isa = XCBuildConfiguration; 751 | buildSettings = { 752 | CLANG_ENABLE_MODULES = YES; 753 | DEVELOPMENT_TEAM = 29X3GG489T; 754 | FRAMEWORK_SEARCH_PATHS = ""; 755 | INFOPLIST_FILE = Tests/Info.plist; 756 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 757 | PRODUCT_BUNDLE_IDENTIFIER = "com.sample.$(PRODUCT_NAME:rfc1034identifier)"; 758 | PRODUCT_NAME = "$(TARGET_NAME)"; 759 | SWIFT_VERSION = 5.0; 760 | }; 761 | name = Release; 762 | }; 763 | 14BDCD3D1E939CEE00FD41A0 /* Debug */ = { 764 | isa = XCBuildConfiguration; 765 | buildSettings = { 766 | CLANG_ANALYZER_NONNULL = YES; 767 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 768 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 769 | CODE_SIGN_IDENTITY = ""; 770 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 771 | CURRENT_PROJECT_VERSION = 1; 772 | DEBUG_INFORMATION_FORMAT = dwarf; 773 | DEFINES_MODULE = YES; 774 | DEVELOPMENT_TEAM = 29X3GG489T; 775 | DYLIB_COMPATIBILITY_VERSION = 1; 776 | DYLIB_CURRENT_VERSION = 1; 777 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 778 | INFOPLIST_FILE = "$(SRCROOT)/FrameworkInfo.plist"; 779 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 780 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 781 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 782 | PRODUCT_BUNDLE_IDENTIFIER = com.bakkenbaeck.SectionScrubber; 783 | PRODUCT_NAME = SectionScrubber; 784 | SKIP_INSTALL = YES; 785 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 786 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 787 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 788 | SWIFT_VERSION = 5.0; 789 | TARGETED_DEVICE_FAMILY = "1,2"; 790 | VERSIONING_SYSTEM = "apple-generic"; 791 | VERSION_INFO_PREFIX = ""; 792 | }; 793 | name = Debug; 794 | }; 795 | 14BDCD3E1E939CEE00FD41A0 /* Release */ = { 796 | isa = XCBuildConfiguration; 797 | buildSettings = { 798 | CLANG_ANALYZER_NONNULL = YES; 799 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 800 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 801 | CODE_SIGN_IDENTITY = ""; 802 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 803 | CURRENT_PROJECT_VERSION = 1; 804 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 805 | DEFINES_MODULE = YES; 806 | DEVELOPMENT_TEAM = 29X3GG489T; 807 | DYLIB_COMPATIBILITY_VERSION = 1; 808 | DYLIB_CURRENT_VERSION = 1; 809 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 810 | INFOPLIST_FILE = "$(SRCROOT)/FrameworkInfo.plist"; 811 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 812 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 813 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 814 | PRODUCT_BUNDLE_IDENTIFIER = com.bakkenbaeck.SectionScrubber; 815 | PRODUCT_NAME = SectionScrubber; 816 | SKIP_INSTALL = YES; 817 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 818 | SWIFT_VERSION = 5.0; 819 | TARGETED_DEVICE_FAMILY = "1,2"; 820 | VERSIONING_SYSTEM = "apple-generic"; 821 | VERSION_INFO_PREFIX = ""; 822 | }; 823 | name = Release; 824 | }; 825 | 14BDCD4B1E939CFF00FD41A0 /* Debug */ = { 826 | isa = XCBuildConfiguration; 827 | buildSettings = { 828 | CLANG_ANALYZER_NONNULL = YES; 829 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 830 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 831 | CODE_SIGN_IDENTITY = ""; 832 | CURRENT_PROJECT_VERSION = 1; 833 | DEBUG_INFORMATION_FORMAT = dwarf; 834 | DEFINES_MODULE = YES; 835 | DEVELOPMENT_TEAM = 29X3GG489T; 836 | DYLIB_COMPATIBILITY_VERSION = 1; 837 | DYLIB_CURRENT_VERSION = 1; 838 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 839 | INFOPLIST_FILE = "$(SRCROOT)/FrameworkInfo.plist"; 840 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 841 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 842 | PRODUCT_BUNDLE_IDENTIFIER = com.bakkenbaeck.SectionScrubber; 843 | PRODUCT_NAME = SectionScrubber; 844 | SDKROOT = appletvos; 845 | SKIP_INSTALL = YES; 846 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 847 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 848 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 849 | SWIFT_VERSION = 5.0; 850 | TARGETED_DEVICE_FAMILY = 3; 851 | TVOS_DEPLOYMENT_TARGET = 12.0; 852 | VERSIONING_SYSTEM = "apple-generic"; 853 | VERSION_INFO_PREFIX = ""; 854 | }; 855 | name = Debug; 856 | }; 857 | 14BDCD4C1E939CFF00FD41A0 /* Release */ = { 858 | isa = XCBuildConfiguration; 859 | buildSettings = { 860 | CLANG_ANALYZER_NONNULL = YES; 861 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 862 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 863 | CODE_SIGN_IDENTITY = ""; 864 | CURRENT_PROJECT_VERSION = 1; 865 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 866 | DEFINES_MODULE = YES; 867 | DEVELOPMENT_TEAM = 29X3GG489T; 868 | DYLIB_COMPATIBILITY_VERSION = 1; 869 | DYLIB_CURRENT_VERSION = 1; 870 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 871 | INFOPLIST_FILE = "$(SRCROOT)/FrameworkInfo.plist"; 872 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 873 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 874 | PRODUCT_BUNDLE_IDENTIFIER = com.bakkenbaeck.SectionScrubber; 875 | PRODUCT_NAME = SectionScrubber; 876 | SDKROOT = appletvos; 877 | SKIP_INSTALL = YES; 878 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 879 | SWIFT_VERSION = 5.0; 880 | TARGETED_DEVICE_FAMILY = 3; 881 | TVOS_DEPLOYMENT_TARGET = 12.0; 882 | VERSIONING_SYSTEM = "apple-generic"; 883 | VERSION_INFO_PREFIX = ""; 884 | }; 885 | name = Release; 886 | }; 887 | 14C984071C0DE79200B5E515 /* Debug */ = { 888 | isa = XCBuildConfiguration; 889 | buildSettings = { 890 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 891 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 892 | CODE_SIGN_IDENTITY = "iPhone Developer"; 893 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 894 | DEBUG_INFORMATION_FORMAT = dwarf; 895 | DEVELOPMENT_TEAM = 29X3GG489T; 896 | GCC_NO_COMMON_BLOCKS = YES; 897 | INFOPLIST_FILE = iOS/Info.plist; 898 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 899 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 900 | PRODUCT_BUNDLE_IDENTIFIER = com.demoapp.ios; 901 | PRODUCT_NAME = "$(TARGET_NAME)"; 902 | PROVISIONING_PROFILE = ""; 903 | PROVISIONING_PROFILE_SPECIFIER = ""; 904 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 905 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 906 | SWIFT_VERSION = 5.0; 907 | TARGETED_DEVICE_FAMILY = "1,2"; 908 | }; 909 | name = Debug; 910 | }; 911 | 14C984081C0DE79200B5E515 /* Release */ = { 912 | isa = XCBuildConfiguration; 913 | buildSettings = { 914 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 915 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 916 | CODE_SIGN_IDENTITY = "iPhone Distribution"; 917 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 918 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 919 | DEVELOPMENT_TEAM = 29X3GG489T; 920 | GCC_NO_COMMON_BLOCKS = YES; 921 | INFOPLIST_FILE = iOS/Info.plist; 922 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 923 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 924 | PRODUCT_BUNDLE_IDENTIFIER = com.demoapp.ios; 925 | PRODUCT_NAME = "$(TARGET_NAME)"; 926 | PROVISIONING_PROFILE = ""; 927 | PROVISIONING_PROFILE_SPECIFIER = ""; 928 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 929 | SWIFT_VERSION = 5.0; 930 | TARGETED_DEVICE_FAMILY = "1,2"; 931 | }; 932 | name = Release; 933 | }; 934 | /* End XCBuildConfiguration section */ 935 | 936 | /* Begin XCConfigurationList section */ 937 | 14036DD41E8BE9A5005FCBB2 /* Build configuration list for PBXNativeTarget "tvOS" */ = { 938 | isa = XCConfigurationList; 939 | buildConfigurations = ( 940 | 14036DD21E8BE9A5005FCBB2 /* Debug */, 941 | 14036DD31E8BE9A5005FCBB2 /* Release */, 942 | ); 943 | defaultConfigurationIsVisible = 0; 944 | defaultConfigurationName = Release; 945 | }; 946 | 146D728E1AB782920058798C /* Build configuration list for PBXProject "Demo" */ = { 947 | isa = XCConfigurationList; 948 | buildConfigurations = ( 949 | 146D72B41AB782920058798C /* Debug */, 950 | 146D72B51AB782920058798C /* Release */, 951 | ); 952 | defaultConfigurationIsVisible = 0; 953 | defaultConfigurationName = Release; 954 | }; 955 | 146D72B91AB782920058798C /* Build configuration list for PBXNativeTarget "Tests" */ = { 956 | isa = XCConfigurationList; 957 | buildConfigurations = ( 958 | 146D72BA1AB782920058798C /* Debug */, 959 | 146D72BB1AB782920058798C /* Release */, 960 | ); 961 | defaultConfigurationIsVisible = 0; 962 | defaultConfigurationName = Release; 963 | }; 964 | 14BDCD3F1E939CEE00FD41A0 /* Build configuration list for PBXNativeTarget "SectionScrubber-iOS" */ = { 965 | isa = XCConfigurationList; 966 | buildConfigurations = ( 967 | 14BDCD3D1E939CEE00FD41A0 /* Debug */, 968 | 14BDCD3E1E939CEE00FD41A0 /* Release */, 969 | ); 970 | defaultConfigurationIsVisible = 0; 971 | defaultConfigurationName = Release; 972 | }; 973 | 14BDCD4A1E939CFF00FD41A0 /* Build configuration list for PBXNativeTarget "SectionScrubber-tvOS" */ = { 974 | isa = XCConfigurationList; 975 | buildConfigurations = ( 976 | 14BDCD4B1E939CFF00FD41A0 /* Debug */, 977 | 14BDCD4C1E939CFF00FD41A0 /* Release */, 978 | ); 979 | defaultConfigurationIsVisible = 0; 980 | defaultConfigurationName = Release; 981 | }; 982 | 14C984091C0DE79200B5E515 /* Build configuration list for PBXNativeTarget "iOS" */ = { 983 | isa = XCConfigurationList; 984 | buildConfigurations = ( 985 | 14C984071C0DE79200B5E515 /* Debug */, 986 | 14C984081C0DE79200B5E515 /* Release */, 987 | ); 988 | defaultConfigurationIsVisible = 0; 989 | defaultConfigurationName = Release; 990 | }; 991 | /* End XCConfigurationList section */ 992 | }; 993 | rootObject = 146D728B1AB782920058798C /* Project object */; 994 | } 995 | -------------------------------------------------------------------------------- /Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo.xcodeproj/xcshareddata/xcschemes/SectionScrubber-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Demo.xcodeproj/xcshareddata/xcschemes/SectionScrubber-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Demo.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 76 | 77 | 78 | 79 | 85 | 86 | 92 | 93 | 94 | 95 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Demo.xcodeproj/xcshareddata/xcschemes/iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 67 | 68 | 69 | 70 | 76 | 78 | 84 | 85 | 86 | 87 | 89 | 90 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Demo.xcodeproj/xcshareddata/xcschemes/tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /FrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /GitHub/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/GitHub/demo.gif -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under the **MIT** license 2 | 3 | > Copyright (c) 2016 Bakken & Bæck 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Library/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | static let IsLightStatusBar = false 6 | static let HeaderSize = CGFloat(100) 7 | 8 | var window: UIWindow? 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 11 | self.window = UIWindow(frame: UIScreen.main.bounds) 12 | guard let window = self.window else { return false } 13 | 14 | let remoteController = RemoteCollectionController() 15 | remoteController.title = "Remote" 16 | let remoteNavigationController = UINavigationController(rootViewController: remoteController) 17 | 18 | let tabBarController = UITabBarController() 19 | tabBarController.viewControllers = [remoteNavigationController] 20 | 21 | #if os(iOS) 22 | if AppDelegate.IsLightStatusBar { 23 | UINavigationBar.appearance().barTintColor = UIColor.orange 24 | UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white] 25 | remoteNavigationController.navigationBar.barStyle = .black 26 | } 27 | #endif 28 | 29 | window.rootViewController = tabBarController 30 | window.makeKeyAndVisible() 31 | 32 | return true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Library/Images.xcassets/0.imageset/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Library/Images.xcassets/0.imageset/0.jpg -------------------------------------------------------------------------------- /Library/Images.xcassets/0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "0.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Library/Images.xcassets/1.imageset/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Library/Images.xcassets/1.imageset/1.jpg -------------------------------------------------------------------------------- /Library/Images.xcassets/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Library/Images.xcassets/2.imageset/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Library/Images.xcassets/2.imageset/2.jpg -------------------------------------------------------------------------------- /Library/Images.xcassets/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "2.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Library/Images.xcassets/3.imageset/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Library/Images.xcassets/3.imageset/3.jpg -------------------------------------------------------------------------------- /Library/Images.xcassets/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "3.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Library/Images.xcassets/4.imageset/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Library/Images.xcassets/4.imageset/4.jpg -------------------------------------------------------------------------------- /Library/Images.xcassets/4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "4.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Library/Images.xcassets/5.imageset/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Library/Images.xcassets/5.imageset/5.png -------------------------------------------------------------------------------- /Library/Images.xcassets/5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "5.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Library/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Library/Images.xcassets/clear.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "clear.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Library/Images.xcassets/clear.imageset/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Library/Images.xcassets/clear.imageset/clear.png -------------------------------------------------------------------------------- /Library/Photo.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | struct Photo { 4 | enum Size { 5 | case small, large 6 | } 7 | 8 | var remoteID: String 9 | var placeholder = UIImage(named: "clear.png")! 10 | var url: String? 11 | static let NumberOfSections = 200 12 | 13 | init(remoteID: String) { 14 | self.remoteID = remoteID 15 | } 16 | 17 | func media(_ completion: (_ image: UIImage?, _ error: NSError?) -> ()) { 18 | completion(self.placeholder, nil) 19 | } 20 | 21 | static func constructRemoteElements() -> [String : [Photo]] { 22 | var sections = [String : [Photo]]() 23 | 24 | for section in 0.. String { 60 | return "Section \(index)" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Library/PhotoCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class PhotoCell: UICollectionViewCell { 4 | static let Identifier = "PhotoCellIdentifier" 5 | 6 | lazy var imageView: UIImageView = { 7 | let view = UIImageView() 8 | view.contentMode = .scaleAspectFill 9 | 10 | #if os(iOS) 11 | view.clipsToBounds = true 12 | #else 13 | view.clipsToBounds = false 14 | view.adjustsImageWhenAncestorFocused = true 15 | #endif 16 | 17 | return view 18 | }() 19 | 20 | override init(frame: CGRect) { 21 | super.init(frame: frame) 22 | 23 | self.backgroundColor = UIColor.black 24 | self.addSubview(self.imageView) 25 | } 26 | 27 | required init?(coder aDecoder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | func display(_ photo: Photo) { 32 | self.imageView.image = photo.placeholder 33 | } 34 | 35 | override func layoutSubviews() { 36 | super.layoutSubviews() 37 | 38 | self.imageView.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Library/PhotosCollectionLayout.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class PhotosCollectionLayout: UICollectionViewFlowLayout { 4 | static let headerSize = CGFloat(69) 5 | class var numberOfColumns: Int { 6 | #if os(iOS) 7 | var isPortrait: Bool 8 | switch UIDevice.current.orientation { 9 | case .portrait, .portraitUpsideDown, .unknown, .faceUp, .faceDown: 10 | isPortrait = true 11 | case .landscapeLeft, .landscapeRight: 12 | isPortrait = false 13 | @unknown default: 14 | isPortrait = true 15 | } 16 | 17 | var numberOfColumns = 0 18 | if UIDevice.current.userInterfaceIdiom == .phone { 19 | numberOfColumns = isPortrait ? 3 : 6 20 | } else { 21 | numberOfColumns = isPortrait ? 5 : 8 22 | } 23 | 24 | return numberOfColumns 25 | #else 26 | return 6 27 | #endif 28 | } 29 | 30 | init(isGroupedByDay: Bool = true) { 31 | super.init() 32 | 33 | let bounds = UIScreen.main.bounds 34 | self.itemSize = PhotosCollectionLayout.itemSize() 35 | self.headerReferenceSize = CGSize(width: bounds.size.width, height: PhotosCollectionLayout.headerSize) 36 | 37 | #if os(iOS) 38 | self.minimumLineSpacing = 1 39 | self.minimumInteritemSpacing = 1 40 | #else 41 | let margin = CGFloat(25) 42 | self.minimumLineSpacing = 50 43 | self.minimumInteritemSpacing = margin 44 | self.sectionInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin) 45 | #endif 46 | } 47 | 48 | required init?(coder _: NSCoder) { 49 | fatalError("init(coder:) has not been implemented") 50 | } 51 | 52 | class func itemSize() -> CGSize { 53 | #if os(iOS) 54 | let bounds = UIScreen.main.bounds 55 | let size = (bounds.width - (CGFloat(PhotosCollectionLayout.numberOfColumns) - 1)) / CGFloat(PhotosCollectionLayout.numberOfColumns) 56 | return CGSize(width: size, height: size) 57 | #else 58 | return CGSize(width: 260, height: 260) 59 | #endif 60 | } 61 | 62 | func updateItemSize() { 63 | self.itemSize = PhotosCollectionLayout.itemSize() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Library/RemoteCollectionController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SectionScrubber 3 | 4 | class RemoteCollectionController: UICollectionViewController { 5 | var sections = Photo.constructRemoteElements() 6 | 7 | init() { 8 | super.init(collectionViewLayout: PhotosCollectionLayout()) 9 | } 10 | 11 | required init?(coder aDecoder: NSCoder) { 12 | fatalError("init(coder:) has not been implemented") 13 | } 14 | 15 | lazy var overlayView: UIView = { 16 | let view = UIView() 17 | view.backgroundColor = UIColor.black 18 | view.alpha = 0 19 | 20 | return view 21 | }() 22 | 23 | lazy var sectionScrubber: SectionScrubber = { 24 | let scrubber = SectionScrubber(collectionView: self.collectionView) 25 | scrubber.delegate = self 26 | scrubber.dataSource = self 27 | #if os(iOS) 28 | scrubber.font = UIFont.boldSystemFont(ofSize: 14) 29 | #else 30 | scrubber.font = UIFont.boldSystemFont(ofSize: 30) 31 | #endif 32 | scrubber.textColor = UIColor.white 33 | scrubber.containerColor = UIColor(red: 155.0/255.0, green: 102.0/255.0, blue: 229.0/255.0, alpha: 1) 34 | 35 | return scrubber 36 | }() 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | self.collectionView?.backgroundColor = UIColor.white 42 | self.collectionView?.register(PhotoCell.self, forCellWithReuseIdentifier: PhotoCell.Identifier) 43 | self.collectionView?.register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: SectionHeader.Identifier) 44 | self.collectionView?.showsVerticalScrollIndicator = false 45 | 46 | var count = 0 47 | for i in 0 ..< self.sections.count { 48 | if let photos = self.sections[Photo.title(index: i)] { 49 | count += photos.count 50 | } 51 | } 52 | 53 | self.collectionView?.addSubview(self.overlayView) 54 | self.collectionView?.addSubview(self.sectionScrubber) 55 | } 56 | 57 | override func numberOfSections(in collectionView: UICollectionView) -> Int { 58 | return self.sections.count 59 | } 60 | 61 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 62 | return self.sections[Photo.title(index: section)]?.count ?? 0 63 | } 64 | 65 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 66 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCell.Identifier, for: indexPath) as! PhotoCell 67 | if let photos = self.sections[Photo.title(index: indexPath.section)] { 68 | let photo = photos[indexPath.row] 69 | cell.display(photo) 70 | } 71 | 72 | return cell 73 | } 74 | 75 | override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 76 | let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeader.Identifier, for: indexPath) as! SectionHeader 77 | headerView.titleLabel.text = Photo.title(index: indexPath.section) 78 | 79 | return headerView 80 | } 81 | 82 | override func scrollViewDidScroll(_ scrollView: UIScrollView) { 83 | var overlayFrame = self.collectionView?.frame ?? CGRect.zero 84 | overlayFrame.origin.y = scrollView.contentOffset.y 85 | self.overlayView.frame = overlayFrame 86 | 87 | self.sectionScrubber.updateScrubberPosition() 88 | } 89 | 90 | override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 91 | self.sectionScrubber.updateScrubberPosition() 92 | } 93 | } 94 | 95 | extension RemoteCollectionController: SectionScrubberDelegate { 96 | func sectionScrubberDidStartScrubbing(_ sectionScrubber: SectionScrubber) { 97 | UIView.animate(withDuration: 0.2, delay: 0.0, options: .allowUserInteraction, animations: { 98 | self.overlayView.alpha = 0.4 99 | }, completion: nil) 100 | } 101 | 102 | func sectionScrubberDidStopScrubbing(_ sectionScrubber: SectionScrubber) { 103 | UIView.animate(withDuration: 0.2, delay: 0.0, options: .allowUserInteraction, animations: { 104 | self.overlayView.alpha = 0 105 | }, completion: nil) 106 | } 107 | } 108 | 109 | extension RemoteCollectionController: SectionScrubberDataSource { 110 | func sectionScrubber(_ sectionScrubber: SectionScrubber, titleForSectionAt indexPath: IndexPath) -> String { 111 | return Photo.title(index: indexPath.section) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Library/SectionHeader.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SectionHeader: UICollectionReusableView { 4 | static let Identifier = "SectionHeaderIdentifier" 5 | 6 | lazy var titleLabel: UILabel = { 7 | let label = UILabel() 8 | label.textColor = .black 9 | 10 | return label 11 | }() 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | 16 | self.addSubview(self.titleLabel) 17 | } 18 | 19 | required init?(coder aDecoder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override func layoutSubviews() { 24 | super.layoutSubviews() 25 | 26 | self.titleLabel.frame = CGRect(x: 25, y: 20, width: self.frame.width, height: self.frame.height) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // Licensed under the **MIT** license 2 | // Copyright (c) 2016 Bakken & Bæck 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | import PackageDescription 24 | 25 | let package = Package( 26 | name: "SectionScrubber" 27 | ) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SectionScrubber 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/SectionScrubber.svg?style=flat)](https://cocoapods.org/pods/SectionScrubber) 4 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/bakkenbaeck/SectionScrubber) 5 | ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS%20-lightgrey.svg) 6 | [![License](https://img.shields.io/cocoapods/l/SectionScrubber.svg?style=flat)](https://cocoapods.org/pods/DATAStack) 7 | 8 | * The scrubber will move along when scrolling the `UICollectionView` it has been added to. 9 | * When you pan the scrubber you 'scrub' over the `UICollectionView`. 10 | * While scrubbing you can choose the title that will be shown in the scrubber. 11 | 12 |

13 | 14 |

15 | 16 | ## Usage 17 | 18 | From your UICollectionViewController: 19 | 20 | ```swift 21 | lazy var sectionScrubber: SectionScrubber = { 22 | let scrubber = SectionScrubber(collectionView: self.collectionView) 23 | scrubber.sectionlabelTextColor = UIColor(red: 69/255, green: 67/255, blue: 76/255, alpha: 0.8) 24 | scrubber.dataSource = self 25 | 26 | return scrubber 27 | }() 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | self.collectionView?.addSubview(sectionScrubber) 32 | } 33 | 34 | override func scrollViewDidScroll(scrollView: UIScrollView) { 35 | self.sectionScrubber.updateScrubberPosition() 36 | } 37 | 38 | override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { 39 | self.sectionScrubber.updateScrubberPosition() 40 | } 41 | 42 | extension RemoteCollectionController: SectionScrubberDataSource { 43 | func sectionScrubber(sectionScrubber: SectionScrubber, titleForSectionAt indexPath: NSIndexPath) -> String { 44 | return Photo.title(index: indexPath.section) 45 | } 46 | } 47 | ``` 48 | 49 | ## Installation 50 | 51 | **SectionScrubber** is available through [CocoaPods](http://cocoapods.org). To install 52 | it, simply add the following line to your Podfile: 53 | 54 | ```ruby 55 | pod 'SectionScrubber' 56 | ``` 57 | 58 | **SectionScrubber** is also available through [Carthage](https://github.com/Carthage/Carthage). To install 59 | it, simply add the following line to your Cartfile: 60 | 61 | ```ruby 62 | github "bakkenbaeck/SectionScrubber" 63 | ``` 64 | 65 | ## License 66 | 67 | **SectionScrubber** is available under the MIT license. See the LICENSE file for more info. 68 | 69 | ## Author 70 | 71 | Bakken & Bæck, [@bakkenbaeck](https://twitter.com/bakkenbaeck) 72 | -------------------------------------------------------------------------------- /Resources/Resources.xcassets/Arrows.imageset/Arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Resources/Resources.xcassets/Arrows.imageset/Arrows.png -------------------------------------------------------------------------------- /Resources/Resources.xcassets/Arrows.imageset/Arrows@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Resources/Resources.xcassets/Arrows.imageset/Arrows@2x.png -------------------------------------------------------------------------------- /Resources/Resources.xcassets/Arrows.imageset/Arrows@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/Resources/Resources.xcassets/Arrows.imageset/Arrows@3x.png -------------------------------------------------------------------------------- /Resources/Resources.xcassets/Arrows.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Arrows.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Arrows@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Arrows@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Resources/Resources.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SectionScrubber.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SectionScrubber" 3 | s.summary = "A component to quickly scroll between collection view sections" 4 | s.version = "2.0.0" 5 | s.homepage = "https://github.com/bakkenbaeck/SectionScrubber" 6 | s.license = 'MIT' 7 | s.author = { "Bakken & Bæck" => "post@bakkenbaeck.no" } 8 | s.source = { :git => "https://github.com/bakkenbaeck/SectionScrubber.git", :tag => s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/bakkenbaeck' 10 | s.ios.deployment_target = '9.0' 11 | s.tvos.deployment_target = '9.0' 12 | s.requires_arc = true 13 | s.source_files = 'Sources/**/*' 14 | s.resources = 'Resources/**/*' 15 | s.resource_bundles = { 'SectionScrubberResources' => ['Resources/**/*'] } 16 | end 17 | -------------------------------------------------------------------------------- /Sources/SectionLabel.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SectionLabel: UIView { 4 | private lazy var sectionLabelImageView: UIImageView = { 5 | let view = UIImageView() 6 | view.translatesAutoresizingMaskIntoConstraints = false 7 | 8 | return view 9 | }() 10 | 11 | private lazy var textLabel: UILabel = { 12 | let view = UILabel() 13 | view.translatesAutoresizingMaskIntoConstraints = false 14 | view.textAlignment = .center 15 | 16 | return view 17 | }() 18 | 19 | var labelImage: UIImage? { 20 | didSet { 21 | if let labelImage = self.labelImage { 22 | self.sectionLabelImageView.image = labelImage 23 | } 24 | } 25 | } 26 | 27 | override init(frame: CGRect) { 28 | super.init(frame: frame) 29 | 30 | self.hide() 31 | 32 | self.addSubview(self.sectionLabelImageView) 33 | self.addSubview(self.textLabel) 34 | 35 | self.sectionLabelImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true 36 | self.sectionLabelImageView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1).isActive = true 37 | self.sectionLabelImageView.widthAnchor.constraint(equalTo: self.textLabel.widthAnchor, constant: 48).isActive = true 38 | self.sectionLabelImageView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true 39 | 40 | self.widthAnchor.constraint(equalTo: self.textLabel.widthAnchor).isActive = true 41 | self.textLabel.centerYAnchor.constraint(equalTo: self.sectionLabelImageView.centerYAnchor, constant: 1).isActive = true 42 | self.textLabel.centerXAnchor.constraint(equalTo: self.sectionLabelImageView.centerXAnchor, constant: -5).isActive = true 43 | self.textLabel.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1).isActive = true 44 | self.textLabel.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) 45 | self.textLabel.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) 46 | } 47 | 48 | required init?(coder aDecoder: NSCoder) { 49 | fatalError("init(coder:) has not been implemented") 50 | } 51 | 52 | func setFont(_ font : UIFont){ 53 | self.textLabel.font = font 54 | } 55 | 56 | func setTextColor(_ color : UIColor){ 57 | self.textLabel.textColor = color 58 | } 59 | 60 | func setText(_ text: String){ 61 | self.textLabel.text = text 62 | // Source: https://github.com/bakkenbaeck/SweetUIKit/blob/master/Sources/UILabel%2BSweetness.swift 63 | let rect = (self.textLabel.attributedText ?? NSAttributedString()).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil) 64 | self.widthAnchor.constraint(equalToConstant: rect.width).isActive = true 65 | } 66 | 67 | func hide() { 68 | UIView.animate(withDuration: 0.2, delay: 0.0, options: [.allowUserInteraction, .beginFromCurrentState], animations: { 69 | self.alpha = 0 70 | }, completion: nil) 71 | } 72 | 73 | func show() { 74 | UIView.animate(withDuration: 0.2, delay: 0.0, options: [.allowUserInteraction, .beginFromCurrentState], animations: { 75 | self.alpha = 1 76 | }, completion: nil) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/SectionScrubber.h: -------------------------------------------------------------------------------- 1 | @import UIKit; 2 | 3 | FOUNDATION_EXPORT double SectionScrubber_VersionNumber; 4 | 5 | FOUNDATION_EXPORT const unsigned char SectionScrubber_VersionString[]; 6 | -------------------------------------------------------------------------------- /Sources/SectionScrubber.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol SectionScrubberDelegate: class { 4 | func sectionScrubberDidStartScrubbing(_ sectionScrubber: SectionScrubber) 5 | 6 | func sectionScrubberDidStopScrubbing(_ sectionScrubber: SectionScrubber) 7 | } 8 | 9 | public protocol SectionScrubberDataSource: class { 10 | func sectionScrubber(_ sectionScrubber: SectionScrubber, titleForSectionAt indexPath: IndexPath) -> String 11 | } 12 | 13 | public class SectionScrubber: UIView { 14 | public enum State { 15 | case hidden 16 | case scrolling 17 | case scrubbing 18 | } 19 | 20 | private let widthHiding: CGFloat = 4 21 | private let widthScrubbing: CGFloat = 200 22 | private let rightMarginHidden: CGFloat = 1 23 | 24 | #if os(iOS) 25 | private let leftMargin: CGFloat = 10 26 | private let scrubHeight: CGFloat = 42 27 | private let widthScrolling: CGFloat = 140 28 | private let rightMarginScrolling: CGFloat = 1 29 | #else 30 | private let leftMargin: CGFloat = 20 31 | private let scrubHeight: CGFloat = 100 32 | private let widthScrolling: CGFloat = 280 33 | private let rightMarginScrolling: CGFloat = -120 34 | #endif 35 | 36 | private let animationDuration: TimeInterval = 0.4 37 | private let animationDamping: CGFloat = 0.8 38 | private let animationSpringVelocity: CGFloat = 10 39 | 40 | public weak var delegate: SectionScrubberDelegate? 41 | public weak var dataSource: SectionScrubberDataSource? 42 | 43 | private var adjustedContainerBoundsHeight: CGFloat { 44 | guard let collectionView = self.collectionView else { return 0 } 45 | return collectionView.bounds.height - (collectionView.adjustedContentInset.top + collectionView.adjustedContentInset.bottom + self.frame.height) 46 | } 47 | 48 | private var adjustedContainerOrigin: CGFloat { 49 | guard let collectionView = self.collectionView else { return 0 } 50 | guard let window = collectionView.window else { return 0 } 51 | 52 | /* 53 | We check against the `UICollectionViewControllerWrapperView`, because this indicates we're working with 54 | a collection view that is inside a collection view controller. When that is the case, we have to deal with its 55 | superview instead of with it directly, otherwise we have a offsetting problem. 56 | */ 57 | if collectionView.superview?.isKind(of: NSClassFromString(String.init(format: "U%@ectionViewCont%@w", "IColl", "rollerWrapperVie"))!) != nil { 58 | return (collectionView.superview?.convert(collectionView.frame.origin, to: window).y)! 59 | } else { 60 | return collectionView.convert(collectionView.frame.origin, to: window).y 61 | } 62 | } 63 | 64 | private var adjustedContainerHeight: CGFloat { 65 | guard let collectionView = self.collectionView else { return 0 } 66 | return collectionView.contentSize.height - collectionView.bounds.height + (collectionView.adjustedContentInset.top + collectionView.adjustedContentInset.bottom) 67 | } 68 | 69 | private var adjustedContainerOffset: CGFloat { 70 | guard let collectionView = self.collectionView else { return 0 } 71 | return collectionView.contentOffset.y + collectionView.adjustedContentInset.top 72 | } 73 | 74 | private var containingViewFrame: CGRect { 75 | return self.superview?.frame ?? CGRect.zero 76 | } 77 | 78 | fileprivate lazy var panGestureRecognizer: UIPanGestureRecognizer = { 79 | UIPanGestureRecognizer() 80 | }() 81 | 82 | fileprivate lazy var longPressGestureRecognizer: UILongPressGestureRecognizer = { 83 | UILongPressGestureRecognizer() 84 | }() 85 | 86 | private weak var collectionView: UICollectionView? 87 | 88 | private var topConstraint: NSLayoutConstraint? 89 | 90 | private lazy var sectionScrubberImageRightConstraint: NSLayoutConstraint = { 91 | self.sectionScrubberImageView.rightAnchor.constraint(equalTo: self.rightAnchor) 92 | }() 93 | 94 | private lazy var sectionScrubberWidthConstraint: NSLayoutConstraint = { 95 | self.sectionScrubberContainer.widthAnchor.constraint(equalToConstant: 4) 96 | }() 97 | 98 | private lazy var sectionScrubberRightConstraint: NSLayoutConstraint = { 99 | self.sectionScrubberContainer.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 1) 100 | }() 101 | 102 | fileprivate lazy var sectionScrubberContainer: UIView = { 103 | let container = UIView() 104 | container.translatesAutoresizingMaskIntoConstraints = false 105 | container.isUserInteractionEnabled = true 106 | container.autoresizingMask = [.flexibleWidth, .flexibleHeight] 107 | container.backgroundColor = self.containerColor 108 | #if os(iOS) 109 | container.layer.cornerRadius = 4 110 | #else 111 | container.layer.cornerRadius = 12 112 | #endif 113 | container.layer.masksToBounds = true 114 | 115 | container.heightAnchor.constraint(equalToConstant: self.scrubHeight).isActive = true 116 | 117 | return container 118 | }() 119 | 120 | fileprivate lazy var sectionScrubberImageView: UIImageView = { 121 | let imageView = UIImageView() 122 | imageView.translatesAutoresizingMaskIntoConstraints = false 123 | imageView.isUserInteractionEnabled = true 124 | imageView.image = UIImage(named: "Arrows", in: Bundle(for: self.classForCoder), compatibleWith: nil) 125 | imageView.heightAnchor.constraint(equalToConstant: 18).isActive = true 126 | imageView.contentMode = .scaleAspectFit 127 | imageView.clipsToBounds = true 128 | 129 | return imageView 130 | }() 131 | 132 | public var state = State.hidden { 133 | didSet { 134 | if self.state != oldValue { 135 | self.updateSectionTitle() 136 | self.animateState(self.state, animated: true) 137 | } 138 | } 139 | } 140 | 141 | public var font: UIFont? { 142 | didSet { 143 | if let font = self.font { 144 | self.titleLabel.font = font 145 | } 146 | } 147 | } 148 | 149 | public var textColor: UIColor? { 150 | didSet { 151 | if let textColor = self.textColor { 152 | self.titleLabel.textColor = textColor 153 | } 154 | } 155 | } 156 | 157 | public var containerColor: UIColor? { 158 | didSet { 159 | if let containerColor = self.containerColor { 160 | self.sectionScrubberContainer.backgroundColor = containerColor 161 | } 162 | } 163 | } 164 | 165 | fileprivate lazy var titleLabel: UILabel = { 166 | let label = UILabel() 167 | label.translatesAutoresizingMaskIntoConstraints = false 168 | label.textColor = self.textColor 169 | label.font = self.font 170 | label.heightAnchor.constraint(equalToConstant: self.scrubHeight).isActive = true 171 | 172 | return label 173 | }() 174 | 175 | public init(collectionView: UICollectionView?) { 176 | self.collectionView = collectionView 177 | 178 | super.init(frame: CGRect.zero) 179 | self.translatesAutoresizingMaskIntoConstraints = false 180 | 181 | self.heightAnchor.constraint(equalToConstant: self.scrubHeight).isActive = true 182 | 183 | self.panGestureRecognizer.addTarget(self, action: #selector(self.handleScrub)) 184 | self.panGestureRecognizer.delegate = self 185 | self.addGestureRecognizer(self.panGestureRecognizer) 186 | 187 | self.longPressGestureRecognizer.addTarget(self, action: #selector(self.handleScrub)) 188 | self.longPressGestureRecognizer.minimumPressDuration = 0.001 189 | self.longPressGestureRecognizer.delegate = self 190 | self.addGestureRecognizer(self.longPressGestureRecognizer) 191 | 192 | self.addSubview(self.sectionScrubberContainer) 193 | self.sectionScrubberRightConstraint.isActive = true 194 | self.sectionScrubberContainer.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 195 | self.sectionScrubberWidthConstraint.isActive = true 196 | 197 | #if os(iOS) 198 | self.sectionScrubberContainer.addSubview(self.sectionScrubberImageView) 199 | self.sectionScrubberImageView.centerYAnchor.constraint(equalTo: self.sectionScrubberContainer.centerYAnchor).isActive = true 200 | self.sectionScrubberImageView.trailingAnchor.constraint(equalTo: self.sectionScrubberContainer.trailingAnchor, constant: -3).isActive = true 201 | #endif 202 | 203 | self.sectionScrubberContainer.addSubview(self.titleLabel) 204 | 205 | self.titleLabel.rightAnchor.constraint(equalTo: self.sectionScrubberContainer.rightAnchor).isActive = true 206 | self.titleLabel.leftAnchor.constraint(lessThanOrEqualTo: self.sectionScrubberContainer.leftAnchor, constant: self.leftMargin).isActive = true 207 | self.titleLabel.centerYAnchor.constraint(equalTo: self.sectionScrubberContainer.centerYAnchor).isActive = true 208 | } 209 | 210 | public required init?(coder _: NSCoder) { 211 | fatalError("init(coder:) has not been implemented") 212 | } 213 | 214 | public override func didMoveToSuperview() { 215 | super.didMoveToSuperview() 216 | self.animateState(self.state, animated: false) 217 | 218 | if let superview = self.superview { 219 | self.leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true 220 | self.rightAnchor.constraint(equalTo: superview.rightAnchor).isActive = true 221 | self.centerXAnchor.constraint(equalTo: superview.centerXAnchor).isActive = true 222 | 223 | self.topConstraint = self.topAnchor.constraint(equalTo: superview.topAnchor) 224 | self.topConstraint?.isActive = true 225 | } 226 | } 227 | 228 | private func hideSectionScrubberAfterDelay() { 229 | NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.hideSectionScrubber), object: nil) 230 | self.perform(#selector(self.hideSectionScrubber), with: nil, afterDelay: 2) 231 | } 232 | 233 | public func updateScrubberPosition() { 234 | guard let collectionView = self.collectionView else { return } 235 | guard collectionView.contentSize.height != 0 else { return } 236 | 237 | if self.state == .hidden { 238 | self.state = .scrolling 239 | } 240 | self.hideSectionScrubberAfterDelay() 241 | 242 | let percentage = roundedPercentage(collectionView.contentOffset.y / self.adjustedContainerHeight) 243 | let newY = self.adjustedContainerOffset + (self.adjustedContainerBoundsHeight * percentage) 244 | self.topConstraint?.constant = newY 245 | 246 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 247 | self.updateSectionTitle() 248 | } 249 | } 250 | 251 | /* 252 | * Only process touch events if we're hitting the actual sectionScrubber image. 253 | * Every other touch is ignored. 254 | . 255 | */ 256 | public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 257 | 258 | let hitWidth: CGFloat = 60 259 | let hitFrame = CGRect(x: frame.width - hitWidth, y: 0, width: hitWidth, height: frame.height) 260 | 261 | if hitFrame.contains(point) { 262 | return super.hitTest(point, with: event) 263 | } 264 | 265 | return nil 266 | } 267 | 268 | /** 269 | Initial dragging doesn't take in account collection view headers, just cells, so before the sectionScrubber reaches 270 | a cell, this is not going to return an index path. 271 | **/ 272 | private func indexPath(at point: CGPoint) -> IndexPath? { 273 | guard let collectionView = self.collectionView else { return nil } 274 | if let indexPath = collectionView.indexPathForItem(at: point) { 275 | return indexPath 276 | } 277 | for indexPath in collectionView.indexPathsForVisibleSupplementaryElements(ofKind: UICollectionView.elementKindSectionHeader) { 278 | guard let view = collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: indexPath) else { continue } 279 | if view.frame.contains(point) { 280 | return indexPath 281 | } 282 | } 283 | return nil 284 | } 285 | 286 | private func updateSectionTitle() { 287 | var currentIndexPath: IndexPath? 288 | 289 | let centerIsAboveContentInset = self.center.y < self.collectionView?.adjustedContentInset.top ?? 0 290 | if centerIsAboveContentInset { 291 | currentIndexPath = IndexPath(item: 0, section: 0) 292 | } else { 293 | // Only will work on the Apple TV since iOS doesn't have a focused item. 294 | if let focusedCell = UIScreen.main.focusedView as? UICollectionViewCell, let indexPath = self.collectionView?.indexPath(for: focusedCell) { 295 | currentIndexPath = indexPath 296 | } else { 297 | // This makes too many assumptions about the collection view layout. It just uses CGPoint x: 0, 298 | // because it works for now, but we might need to come up with a better method for this. 299 | let centerPoint = CGPoint(x: 0, y: self.center.y) 300 | if let indexPath = self.indexPath(at: centerPoint) { 301 | currentIndexPath = indexPath 302 | } else { 303 | let elements = self.collectionView?.collectionViewLayout.layoutAttributesForElements(in: self.frame)?.compactMap { $0.indexPath } ?? [IndexPath]() 304 | if let indexPath = elements.last { 305 | currentIndexPath = indexPath 306 | } 307 | } 308 | } 309 | } 310 | 311 | if let currentIndexPath = currentIndexPath { 312 | if let title = self.dataSource?.sectionScrubber(self, titleForSectionAt: currentIndexPath) { 313 | self.titleLabel.text = title.uppercased() 314 | } 315 | } 316 | } 317 | 318 | private var previousLocation: CGFloat = 0 319 | 320 | @objc func handleScrub(_ gesture: UIPanGestureRecognizer) { 321 | guard let collectionView = self.collectionView else { return } 322 | guard let window = collectionView.window else { return } 323 | guard self.containingViewFrame.height != 0 else { return } 324 | 325 | if gesture.state == .began { 326 | self.startScrubbing() 327 | } 328 | 329 | if gesture.state == .began || gesture.state == .changed || gesture.state == .ended { 330 | let locationInCollectionView = gesture.location(in: collectionView) 331 | let locationInWindow = collectionView.convert(locationInCollectionView, to: window) 332 | let location = locationInWindow.y - (self.adjustedContainerOrigin + collectionView.adjustedContentInset.top + collectionView.adjustedContentInset.bottom) 333 | 334 | if gesture.state != .began && location != self.previousLocation { 335 | let gesturePercentage = self.roundedPercentage(location / self.adjustedContainerBoundsHeight) 336 | let y = (self.adjustedContainerHeight * gesturePercentage) - collectionView.adjustedContentInset.top 337 | collectionView.setContentOffset(CGPoint(x: collectionView.contentOffset.x, y: y), animated: false) 338 | } 339 | 340 | self.previousLocation = location 341 | self.hideSectionScrubberAfterDelay() 342 | } 343 | 344 | if gesture.state == .ended || gesture.state == .cancelled { 345 | self.stopScrubbing() 346 | } 347 | } 348 | 349 | private func roundedPercentage(_ percentage: CGFloat) -> CGFloat { 350 | var newPercentage = percentage 351 | 352 | newPercentage = max(newPercentage, 0.0) 353 | newPercentage = min(newPercentage, 1.0) 354 | 355 | return newPercentage 356 | } 357 | 358 | private func animateState(_ state: State, animated: Bool) { 359 | let duration = animated ? self.animationDuration : 0.0 360 | var titleAlpha: CGFloat = 1 361 | 362 | switch state { 363 | case .hidden: 364 | self.sectionScrubberRightConstraint.constant = self.rightMarginHidden 365 | self.sectionScrubberWidthConstraint.constant = self.widthHiding 366 | titleAlpha = 0 367 | case .scrolling: 368 | self.sectionScrubberRightConstraint.constant = self.rightMarginScrolling 369 | self.sectionScrubberWidthConstraint.constant = self.widthScrolling 370 | case .scrubbing: 371 | self.sectionScrubberRightConstraint.constant = self.rightMarginHidden 372 | self.sectionScrubberWidthConstraint.constant = self.widthScrubbing 373 | } 374 | 375 | UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: self.animationDamping, initialSpringVelocity: self.animationSpringVelocity, options: [.allowUserInteraction, .beginFromCurrentState, .curveEaseOut], animations: { 376 | self.titleLabel.alpha = titleAlpha 377 | let isIPhone5OrBelow = max(UIScreen.main.bounds.width, UIScreen.main.bounds.height) <= 568.0 378 | if isIPhone5OrBelow { 379 | self.sectionScrubberContainer.layoutIfNeeded() 380 | } else { 381 | self.layoutIfNeeded() 382 | } 383 | }, completion: { _ in }) 384 | } 385 | 386 | private func startScrubbing() { 387 | self.delegate?.sectionScrubberDidStartScrubbing(self) 388 | self.state = .scrubbing 389 | } 390 | 391 | private func stopScrubbing() { 392 | self.delegate?.sectionScrubberDidStopScrubbing(self) 393 | 394 | guard self.state == .scrubbing else { 395 | return 396 | } 397 | 398 | self.state = .scrolling 399 | } 400 | 401 | @objc func hideSectionScrubber() { 402 | self.state = .hidden 403 | } 404 | } 405 | 406 | extension SectionScrubber: UIGestureRecognizerDelegate { 407 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 408 | 409 | if gestureRecognizer.view != self || otherGestureRecognizer.view == self { 410 | return false 411 | } 412 | 413 | return true 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import XCTest 3 | 4 | class Tests: XCTestCase { 5 | } -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/29@2x-1.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/29@2x.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/29@3x.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/40@2x-1.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/40@2x.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/40@3x.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/60@2x.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/60@3x.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/AppIcon.appiconset/76@2x.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "60@3x.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "size" : "29x29", 61 | "idiom" : "ipad", 62 | "filename" : "29.png", 63 | "scale" : "1x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "29@2x-1.png", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "size" : "40x40", 73 | "idiom" : "ipad", 74 | "filename" : "40.png", 75 | "scale" : "1x" 76 | }, 77 | { 78 | "size" : "40x40", 79 | "idiom" : "ipad", 80 | "filename" : "40@2x-1.png", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "size" : "76x76", 85 | "idiom" : "ipad", 86 | "filename" : "76.png", 87 | "scale" : "1x" 88 | }, 89 | { 90 | "size" : "76x76", 91 | "idiom" : "ipad", 92 | "filename" : "76@2x.png", 93 | "scale" : "2x" 94 | }, 95 | { 96 | "size" : "83.5x83.5", 97 | "idiom" : "ipad", 98 | "filename" : "167.png", 99 | "scale" : "2x" 100 | } 101 | ], 102 | "info" : { 103 | "version" : 1, 104 | "author" : "xcode" 105 | } 106 | } -------------------------------------------------------------------------------- /iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /iOS/Assets.xcassets/video-indicator.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "video-indicator.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "video-indicator@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "video-indicator@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /iOS/Assets.xcassets/video-indicator.imageset/video-indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/video-indicator.imageset/video-indicator.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/video-indicator.imageset/video-indicator@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/video-indicator.imageset/video-indicator@2x.png -------------------------------------------------------------------------------- /iOS/Assets.xcassets/video-indicator.imageset/video-indicator@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3lvis/SectionScrubber/a5ebf83c3d46ffe6add742320fc02969cef469be/iOS/Assets.xcassets/video-indicator.imageset/video-indicator@3x.png -------------------------------------------------------------------------------- /iOS/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 | -------------------------------------------------------------------------------- /iOS/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 | SectionScrubber 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIStatusBarHidden 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | UIInterfaceOrientationPortraitUpsideDown 44 | 45 | UIViewControllerBasedStatusBarAppearance 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "2320x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image Wide.imageset", 19 | "role" : "top-shelf-image-wide" 20 | }, 21 | { 22 | "size" : "1920x720", 23 | "idiom" : "tv", 24 | "filename" : "Top Shelf Image.imageset", 25 | "role" : "top-shelf-image" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /tvOS/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 | SectionScrubber 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIRequiredDeviceCapabilities 24 | 25 | arm64 26 | 27 | UIUserInterfaceStyle 28 | Automatic 29 | 30 | 31 | --------------------------------------------------------------------------------