├── .gitignore ├── LICENSE ├── PacmanPageControl-Demo ├── PacmanPageControl-Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── PacmanPageControl.xcscheme ├── PacmanPageControl-Demo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-1024.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-60@2x.png │ │ │ └── icon-60@3x.png │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift └── PacmanPageControl │ ├── Info.plist │ └── PacmanPageControl.h ├── PacmanPageControl.podspec ├── README.md ├── Sources ├── PacmanLayer.swift ├── PacmanPageControl.swift └── RandomColor.swift └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | .DS_Store 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xcuserstate 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | *.dSYM.zip 30 | *.dSYM 31 | 32 | ## Playgrounds 33 | timeline.xctimeline 34 | playground.xcworkspace 35 | 36 | # Swift Package Manager 37 | # 38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 39 | # Packages/ 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 oOatuo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2770C1891E86B35A000FF6E3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2770C1881E86B35A000FF6E3 /* AppDelegate.swift */; }; 11 | 2770C18B1E86B35A000FF6E3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2770C18A1E86B35A000FF6E3 /* ViewController.swift */; }; 12 | 2770C18E1E86B35A000FF6E3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2770C18C1E86B35A000FF6E3 /* Main.storyboard */; }; 13 | 2770C1901E86B35A000FF6E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2770C18F1E86B35A000FF6E3 /* Assets.xcassets */; }; 14 | 2770C1931E86B35A000FF6E3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2770C1911E86B35A000FF6E3 /* LaunchScreen.storyboard */; }; 15 | 2770C19D1E86B456000FF6E3 /* PacmanPageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2770C19C1E86B456000FF6E3 /* PacmanPageControl.swift */; }; 16 | 2770C19F1E86BA12000FF6E3 /* PacmanLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2770C19E1E86BA12000FF6E3 /* PacmanLayer.swift */; }; 17 | 988C6EC2202FF29A00030206 /* PacmanPageControl.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 988C6EC1202FF29A00030206 /* PacmanPageControl.podspec */; }; 18 | 98EF7F9F1E86D639002929B6 /* RandomColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98EF7F9E1E86D639002929B6 /* RandomColor.swift */; }; 19 | 98EF7FC71E8A196B002929B6 /* PacmanPageControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 98EF7FC51E8A196B002929B6 /* PacmanPageControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | 98EF7FCA1E8A196B002929B6 /* PacmanPageControl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98EF7FC31E8A196B002929B6 /* PacmanPageControl.framework */; }; 21 | 98EF7FCB1E8A196B002929B6 /* PacmanPageControl.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 98EF7FC31E8A196B002929B6 /* PacmanPageControl.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 22 | 98EF7FD01E8A1DCA002929B6 /* PacmanPageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2770C19C1E86B456000FF6E3 /* PacmanPageControl.swift */; }; 23 | 98EF7FD11E8A1DCA002929B6 /* PacmanLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2770C19E1E86BA12000FF6E3 /* PacmanLayer.swift */; }; 24 | 98EF7FD21E8A1DCA002929B6 /* RandomColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98EF7F9E1E86D639002929B6 /* RandomColor.swift */; }; 25 | 98F3FBB42031669300B75D5C /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 98F3FBB32031669300B75D5C /* README.md */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 98EF7FC81E8A196B002929B6 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 2770C17D1E86B35A000FF6E3 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 98EF7FC21E8A196B002929B6; 34 | remoteInfo = PacmanPageControl; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXCopyFilesBuildPhase section */ 39 | 98EF7FCF1E8A196B002929B6 /* Embed Frameworks */ = { 40 | isa = PBXCopyFilesBuildPhase; 41 | buildActionMask = 2147483647; 42 | dstPath = ""; 43 | dstSubfolderSpec = 10; 44 | files = ( 45 | 98EF7FCB1E8A196B002929B6 /* PacmanPageControl.framework in Embed Frameworks */, 46 | ); 47 | name = "Embed Frameworks"; 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXCopyFilesBuildPhase section */ 51 | 52 | /* Begin PBXFileReference section */ 53 | 2770C1851E86B35A000FF6E3 /* PacmanPageControl-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PacmanPageControl-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 2770C1881E86B35A000FF6E3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 55 | 2770C18A1E86B35A000FF6E3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 56 | 2770C18D1E86B35A000FF6E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | 2770C18F1E86B35A000FF6E3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 58 | 2770C1921E86B35A000FF6E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 59 | 2770C1941E86B35A000FF6E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | 2770C19C1E86B456000FF6E3 /* PacmanPageControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacmanPageControl.swift; sourceTree = ""; }; 61 | 2770C19E1E86BA12000FF6E3 /* PacmanLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacmanLayer.swift; sourceTree = ""; }; 62 | 988C6EC1202FF29A00030206 /* PacmanPageControl.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = PacmanPageControl.podspec; path = ../PacmanPageControl.podspec; sourceTree = ""; }; 63 | 98EF7F9E1E86D639002929B6 /* RandomColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomColor.swift; sourceTree = ""; }; 64 | 98EF7FC31E8A196B002929B6 /* PacmanPageControl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PacmanPageControl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | 98EF7FC51E8A196B002929B6 /* PacmanPageControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PacmanPageControl.h; sourceTree = ""; }; 66 | 98EF7FC61E8A196B002929B6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | 98F3FBB32031669300B75D5C /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 2770C1821E86B35A000FF6E3 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 98EF7FCA1E8A196B002929B6 /* PacmanPageControl.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 98EF7FBF1E8A196B002929B6 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 2770C17C1E86B35A000FF6E3 = { 90 | isa = PBXGroup; 91 | children = ( 92 | 98F3FBB32031669300B75D5C /* README.md */, 93 | 988C6EC1202FF29A00030206 /* PacmanPageControl.podspec */, 94 | 2770C1871E86B35A000FF6E3 /* PacmanPageControl-Demo */, 95 | 2770C19A1E86B361000FF6E3 /* Supporthings */, 96 | 98EF7FC41E8A196B002929B6 /* PacmanPageControl */, 97 | 2770C1861E86B35A000FF6E3 /* Products */, 98 | ); 99 | sourceTree = ""; 100 | }; 101 | 2770C1861E86B35A000FF6E3 /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 2770C1851E86B35A000FF6E3 /* PacmanPageControl-Demo.app */, 105 | 98EF7FC31E8A196B002929B6 /* PacmanPageControl.framework */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | 2770C1871E86B35A000FF6E3 /* PacmanPageControl-Demo */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 2770C19B1E86B43D000FF6E3 /* Sources */, 114 | 2770C1881E86B35A000FF6E3 /* AppDelegate.swift */, 115 | 2770C18A1E86B35A000FF6E3 /* ViewController.swift */, 116 | 2770C18C1E86B35A000FF6E3 /* Main.storyboard */, 117 | ); 118 | path = "PacmanPageControl-Demo"; 119 | sourceTree = ""; 120 | }; 121 | 2770C19A1E86B361000FF6E3 /* Supporthings */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 2770C1911E86B35A000FF6E3 /* LaunchScreen.storyboard */, 125 | 2770C18F1E86B35A000FF6E3 /* Assets.xcassets */, 126 | 2770C1941E86B35A000FF6E3 /* Info.plist */, 127 | ); 128 | name = Supporthings; 129 | path = "PacmanPageControl-Demo"; 130 | sourceTree = ""; 131 | }; 132 | 2770C19B1E86B43D000FF6E3 /* Sources */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 2770C19C1E86B456000FF6E3 /* PacmanPageControl.swift */, 136 | 2770C19E1E86BA12000FF6E3 /* PacmanLayer.swift */, 137 | 98EF7F9E1E86D639002929B6 /* RandomColor.swift */, 138 | ); 139 | name = Sources; 140 | path = ../../Sources; 141 | sourceTree = ""; 142 | }; 143 | 98EF7FC41E8A196B002929B6 /* PacmanPageControl */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 98EF7FC51E8A196B002929B6 /* PacmanPageControl.h */, 147 | 98EF7FC61E8A196B002929B6 /* Info.plist */, 148 | ); 149 | path = PacmanPageControl; 150 | sourceTree = ""; 151 | }; 152 | /* End PBXGroup section */ 153 | 154 | /* Begin PBXHeadersBuildPhase section */ 155 | 98EF7FC01E8A196B002929B6 /* Headers */ = { 156 | isa = PBXHeadersBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | 98EF7FC71E8A196B002929B6 /* PacmanPageControl.h in Headers */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXHeadersBuildPhase section */ 164 | 165 | /* Begin PBXNativeTarget section */ 166 | 2770C1841E86B35A000FF6E3 /* PacmanPageControl-Demo */ = { 167 | isa = PBXNativeTarget; 168 | buildConfigurationList = 2770C1971E86B35A000FF6E3 /* Build configuration list for PBXNativeTarget "PacmanPageControl-Demo" */; 169 | buildPhases = ( 170 | 2770C1811E86B35A000FF6E3 /* Sources */, 171 | 2770C1821E86B35A000FF6E3 /* Frameworks */, 172 | 2770C1831E86B35A000FF6E3 /* Resources */, 173 | 98EF7FCF1E8A196B002929B6 /* Embed Frameworks */, 174 | ); 175 | buildRules = ( 176 | ); 177 | dependencies = ( 178 | 98EF7FC91E8A196B002929B6 /* PBXTargetDependency */, 179 | ); 180 | name = "PacmanPageControl-Demo"; 181 | productName = "PacmanPageControl-Demo"; 182 | productReference = 2770C1851E86B35A000FF6E3 /* PacmanPageControl-Demo.app */; 183 | productType = "com.apple.product-type.application"; 184 | }; 185 | 98EF7FC21E8A196B002929B6 /* PacmanPageControl */ = { 186 | isa = PBXNativeTarget; 187 | buildConfigurationList = 98EF7FCE1E8A196B002929B6 /* Build configuration list for PBXNativeTarget "PacmanPageControl" */; 188 | buildPhases = ( 189 | 98EF7FBE1E8A196B002929B6 /* Sources */, 190 | 98EF7FBF1E8A196B002929B6 /* Frameworks */, 191 | 98EF7FC01E8A196B002929B6 /* Headers */, 192 | 98EF7FC11E8A196B002929B6 /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = PacmanPageControl; 199 | productName = PacmanPageControl; 200 | productReference = 98EF7FC31E8A196B002929B6 /* PacmanPageControl.framework */; 201 | productType = "com.apple.product-type.framework"; 202 | }; 203 | /* End PBXNativeTarget section */ 204 | 205 | /* Begin PBXProject section */ 206 | 2770C17D1E86B35A000FF6E3 /* Project object */ = { 207 | isa = PBXProject; 208 | attributes = { 209 | LastSwiftUpdateCheck = 0820; 210 | LastUpgradeCheck = 1000; 211 | ORGANIZATIONNAME = ooatuoo; 212 | TargetAttributes = { 213 | 2770C1841E86B35A000FF6E3 = { 214 | CreatedOnToolsVersion = 8.2.1; 215 | ProvisioningStyle = Automatic; 216 | }; 217 | 98EF7FC21E8A196B002929B6 = { 218 | CreatedOnToolsVersion = 8.2.1; 219 | LastSwiftMigration = 0920; 220 | ProvisioningStyle = Automatic; 221 | }; 222 | }; 223 | }; 224 | buildConfigurationList = 2770C1801E86B35A000FF6E3 /* Build configuration list for PBXProject "PacmanPageControl-Demo" */; 225 | compatibilityVersion = "Xcode 3.2"; 226 | developmentRegion = English; 227 | hasScannedForEncodings = 0; 228 | knownRegions = ( 229 | en, 230 | Base, 231 | ); 232 | mainGroup = 2770C17C1E86B35A000FF6E3; 233 | productRefGroup = 2770C1861E86B35A000FF6E3 /* Products */; 234 | projectDirPath = ""; 235 | projectRoot = ""; 236 | targets = ( 237 | 2770C1841E86B35A000FF6E3 /* PacmanPageControl-Demo */, 238 | 98EF7FC21E8A196B002929B6 /* PacmanPageControl */, 239 | ); 240 | }; 241 | /* End PBXProject section */ 242 | 243 | /* Begin PBXResourcesBuildPhase section */ 244 | 2770C1831E86B35A000FF6E3 /* Resources */ = { 245 | isa = PBXResourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | 2770C1931E86B35A000FF6E3 /* LaunchScreen.storyboard in Resources */, 249 | 2770C1901E86B35A000FF6E3 /* Assets.xcassets in Resources */, 250 | 98F3FBB42031669300B75D5C /* README.md in Resources */, 251 | 2770C18E1E86B35A000FF6E3 /* Main.storyboard in Resources */, 252 | 988C6EC2202FF29A00030206 /* PacmanPageControl.podspec in Resources */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | 98EF7FC11E8A196B002929B6 /* Resources */ = { 257 | isa = PBXResourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | /* End PBXResourcesBuildPhase section */ 264 | 265 | /* Begin PBXSourcesBuildPhase section */ 266 | 2770C1811E86B35A000FF6E3 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 2770C18B1E86B35A000FF6E3 /* ViewController.swift in Sources */, 271 | 98EF7F9F1E86D639002929B6 /* RandomColor.swift in Sources */, 272 | 2770C19F1E86BA12000FF6E3 /* PacmanLayer.swift in Sources */, 273 | 2770C1891E86B35A000FF6E3 /* AppDelegate.swift in Sources */, 274 | 2770C19D1E86B456000FF6E3 /* PacmanPageControl.swift in Sources */, 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | }; 278 | 98EF7FBE1E8A196B002929B6 /* Sources */ = { 279 | isa = PBXSourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 98EF7FD01E8A1DCA002929B6 /* PacmanPageControl.swift in Sources */, 283 | 98EF7FD11E8A1DCA002929B6 /* PacmanLayer.swift in Sources */, 284 | 98EF7FD21E8A1DCA002929B6 /* RandomColor.swift in Sources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | /* End PBXSourcesBuildPhase section */ 289 | 290 | /* Begin PBXTargetDependency section */ 291 | 98EF7FC91E8A196B002929B6 /* PBXTargetDependency */ = { 292 | isa = PBXTargetDependency; 293 | target = 98EF7FC21E8A196B002929B6 /* PacmanPageControl */; 294 | targetProxy = 98EF7FC81E8A196B002929B6 /* PBXContainerItemProxy */; 295 | }; 296 | /* End PBXTargetDependency section */ 297 | 298 | /* Begin PBXVariantGroup section */ 299 | 2770C18C1E86B35A000FF6E3 /* Main.storyboard */ = { 300 | isa = PBXVariantGroup; 301 | children = ( 302 | 2770C18D1E86B35A000FF6E3 /* Base */, 303 | ); 304 | name = Main.storyboard; 305 | sourceTree = ""; 306 | }; 307 | 2770C1911E86B35A000FF6E3 /* LaunchScreen.storyboard */ = { 308 | isa = PBXVariantGroup; 309 | children = ( 310 | 2770C1921E86B35A000FF6E3 /* Base */, 311 | ); 312 | name = LaunchScreen.storyboard; 313 | sourceTree = ""; 314 | }; 315 | /* End PBXVariantGroup section */ 316 | 317 | /* Begin XCBuildConfiguration section */ 318 | 2770C1951E86B35A000FF6E3 /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_ANALYZER_NONNULL = YES; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 334 | CLANG_WARN_EMPTY_BODY = YES; 335 | CLANG_WARN_ENUM_CONVERSION = YES; 336 | CLANG_WARN_INFINITE_RECURSION = YES; 337 | CLANG_WARN_INT_CONVERSION = YES; 338 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 340 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 343 | CLANG_WARN_STRICT_PROTOTYPES = YES; 344 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 345 | CLANG_WARN_UNREACHABLE_CODE = YES; 346 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 347 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 348 | COPY_PHASE_STRIP = NO; 349 | DEBUG_INFORMATION_FORMAT = dwarf; 350 | ENABLE_STRICT_OBJC_MSGSEND = YES; 351 | ENABLE_TESTABILITY = YES; 352 | GCC_C_LANGUAGE_STANDARD = gnu99; 353 | GCC_DYNAMIC_NO_PIC = NO; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_OPTIMIZATION_LEVEL = 0; 356 | GCC_PREPROCESSOR_DEFINITIONS = ( 357 | "DEBUG=1", 358 | "$(inherited)", 359 | ); 360 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 361 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 362 | GCC_WARN_UNDECLARED_SELECTOR = YES; 363 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 364 | GCC_WARN_UNUSED_FUNCTION = YES; 365 | GCC_WARN_UNUSED_VARIABLE = YES; 366 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 367 | MTL_ENABLE_DEBUG_INFO = YES; 368 | ONLY_ACTIVE_ARCH = YES; 369 | SDKROOT = iphoneos; 370 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 371 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 372 | }; 373 | name = Debug; 374 | }; 375 | 2770C1961E86B35A000FF6E3 /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | ALWAYS_SEARCH_USER_PATHS = NO; 379 | CLANG_ANALYZER_NONNULL = YES; 380 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 381 | CLANG_CXX_LIBRARY = "libc++"; 382 | CLANG_ENABLE_MODULES = YES; 383 | CLANG_ENABLE_OBJC_ARC = YES; 384 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 385 | CLANG_WARN_BOOL_CONVERSION = YES; 386 | CLANG_WARN_COMMA = YES; 387 | CLANG_WARN_CONSTANT_CONVERSION = YES; 388 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 389 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 390 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 391 | CLANG_WARN_EMPTY_BODY = YES; 392 | CLANG_WARN_ENUM_CONVERSION = YES; 393 | CLANG_WARN_INFINITE_RECURSION = YES; 394 | CLANG_WARN_INT_CONVERSION = YES; 395 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 397 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 399 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 400 | CLANG_WARN_STRICT_PROTOTYPES = YES; 401 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 402 | CLANG_WARN_UNREACHABLE_CODE = YES; 403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 404 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 405 | COPY_PHASE_STRIP = NO; 406 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 407 | ENABLE_NS_ASSERTIONS = NO; 408 | ENABLE_STRICT_OBJC_MSGSEND = YES; 409 | GCC_C_LANGUAGE_STANDARD = gnu99; 410 | GCC_NO_COMMON_BLOCKS = YES; 411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 413 | GCC_WARN_UNDECLARED_SELECTOR = YES; 414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 415 | GCC_WARN_UNUSED_FUNCTION = YES; 416 | GCC_WARN_UNUSED_VARIABLE = YES; 417 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 418 | MTL_ENABLE_DEBUG_INFO = NO; 419 | SDKROOT = iphoneos; 420 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 421 | VALIDATE_PRODUCT = YES; 422 | }; 423 | name = Release; 424 | }; 425 | 2770C1981E86B35A000FF6E3 /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 429 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 430 | DEVELOPMENT_TEAM = ""; 431 | INFOPLIST_FILE = "PacmanPageControl-Demo/Info.plist"; 432 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 433 | PRODUCT_BUNDLE_IDENTIFIER = "ooatuoo.PacmanPageControl-Demo"; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | SWIFT_VERSION = 4.2; 436 | }; 437 | name = Debug; 438 | }; 439 | 2770C1991E86B35A000FF6E3 /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 444 | DEVELOPMENT_TEAM = ""; 445 | INFOPLIST_FILE = "PacmanPageControl-Demo/Info.plist"; 446 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 447 | PRODUCT_BUNDLE_IDENTIFIER = "ooatuoo.PacmanPageControl-Demo"; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | SWIFT_VERSION = 4.2; 450 | }; 451 | name = Release; 452 | }; 453 | 98EF7FCC1E8A196B002929B6 /* Debug */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | CODE_SIGN_IDENTITY = ""; 457 | CURRENT_PROJECT_VERSION = 1; 458 | DEFINES_MODULE = YES; 459 | DYLIB_COMPATIBILITY_VERSION = 1; 460 | DYLIB_CURRENT_VERSION = 1; 461 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 462 | INFOPLIST_FILE = PacmanPageControl/Info.plist; 463 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 464 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 465 | PRODUCT_BUNDLE_IDENTIFIER = ooatuoo.PacmanPageControl; 466 | PRODUCT_NAME = "$(TARGET_NAME)"; 467 | SKIP_INSTALL = YES; 468 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 469 | SWIFT_VERSION = 4.2; 470 | TARGETED_DEVICE_FAMILY = "1,2"; 471 | VERSIONING_SYSTEM = "apple-generic"; 472 | VERSION_INFO_PREFIX = ""; 473 | }; 474 | name = Debug; 475 | }; 476 | 98EF7FCD1E8A196B002929B6 /* Release */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | CODE_SIGN_IDENTITY = ""; 480 | CURRENT_PROJECT_VERSION = 1; 481 | DEFINES_MODULE = YES; 482 | DYLIB_COMPATIBILITY_VERSION = 1; 483 | DYLIB_CURRENT_VERSION = 1; 484 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 485 | INFOPLIST_FILE = PacmanPageControl/Info.plist; 486 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 487 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 488 | PRODUCT_BUNDLE_IDENTIFIER = ooatuoo.PacmanPageControl; 489 | PRODUCT_NAME = "$(TARGET_NAME)"; 490 | SKIP_INSTALL = YES; 491 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 492 | SWIFT_VERSION = 4.2; 493 | TARGETED_DEVICE_FAMILY = "1,2"; 494 | VERSIONING_SYSTEM = "apple-generic"; 495 | VERSION_INFO_PREFIX = ""; 496 | }; 497 | name = Release; 498 | }; 499 | /* End XCBuildConfiguration section */ 500 | 501 | /* Begin XCConfigurationList section */ 502 | 2770C1801E86B35A000FF6E3 /* Build configuration list for PBXProject "PacmanPageControl-Demo" */ = { 503 | isa = XCConfigurationList; 504 | buildConfigurations = ( 505 | 2770C1951E86B35A000FF6E3 /* Debug */, 506 | 2770C1961E86B35A000FF6E3 /* Release */, 507 | ); 508 | defaultConfigurationIsVisible = 0; 509 | defaultConfigurationName = Release; 510 | }; 511 | 2770C1971E86B35A000FF6E3 /* Build configuration list for PBXNativeTarget "PacmanPageControl-Demo" */ = { 512 | isa = XCConfigurationList; 513 | buildConfigurations = ( 514 | 2770C1981E86B35A000FF6E3 /* Debug */, 515 | 2770C1991E86B35A000FF6E3 /* Release */, 516 | ); 517 | defaultConfigurationIsVisible = 0; 518 | defaultConfigurationName = Release; 519 | }; 520 | 98EF7FCE1E8A196B002929B6 /* Build configuration list for PBXNativeTarget "PacmanPageControl" */ = { 521 | isa = XCConfigurationList; 522 | buildConfigurations = ( 523 | 98EF7FCC1E8A196B002929B6 /* Debug */, 524 | 98EF7FCD1E8A196B002929B6 /* Release */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | /* End XCConfigurationList section */ 530 | }; 531 | rootObject = 2770C17D1E86B35A000FF6E3 /* Project object */; 532 | } 533 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo.xcodeproj/xcshareddata/xcschemes/PacmanPageControl.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PacmanPageControl-Demo 4 | // 5 | // Created by ooatuoo on 2017/3/25. 6 | // Copyright © 2017年 ooatuoo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | 21 | return true 22 | } 23 | 24 | func applicationWillResignActive(_ application: UIApplication) { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | func applicationDidEnterBackground(_ application: UIApplication) { 30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | func applicationWillEnterForeground(_ application: UIApplication) { 35 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 36 | } 37 | 38 | func applicationDidBecomeActive(_ application: UIApplication) { 39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | } 41 | 42 | func applicationWillTerminate(_ application: UIApplication) { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "icon-20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "icon-20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "icon-29@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "icon-29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "icon-40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "icon-40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "icon-60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "icon-60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "1024x1024", 53 | "idiom" : "ios-marketing", 54 | "filename" : "icon-1024.png", 55 | "scale" : "1x" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/PacmanPageControl-Demo/PacmanPageControl-Demo/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Pac-Man 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.3.1 21 | CFBundleVersion 22 | 02122018 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIRequiresFullScreen 34 | 35 | UIStatusBarHidden 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UIViewControllerBasedStatusBarAppearance 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl-Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PacmanPageControl-Demo 4 | // 5 | // Created by ooatuoo on 2017/3/25. 6 | // Copyright © 2017年 ooatuoo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let pageCount = 7 12 | 13 | class ViewController: UIViewController { 14 | 15 | @IBOutlet weak var scrollView: UIScrollView! 16 | 17 | lazy var pacman2: PacmanPageControl = { 18 | let pacmanFrame = CGRect(x: 0, y: view.frame.midY + 20, width: view.frame.width, height: 20) 19 | let pacman = PacmanPageControl(frame: pacmanFrame) 20 | pacman.scrollView = scrollView 21 | pacman.dotColorStyle = .same(.cyan) 22 | pacman.pacmanColorStyle = .fixed(.orange) 23 | return pacman 24 | }() 25 | 26 | lazy var pacman3: PacmanPageControl = { 27 | let pacmanFrame = CGRect(x: 0, y: view.frame.midY + 50, width: view.frame.width, height: 20) 28 | let pacman = PacmanPageControl(frame: pacmanFrame) 29 | pacman.scrollView = scrollView 30 | pacman.dotDiameter = 10 31 | pacman.pacmanDiameter = 20 32 | pacman.dotColorStyle = .random(hue: .random, luminosity: .light) 33 | pacman.pacmanColorStyle = .changeWithEatenDot 34 | return pacman 35 | }() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | // set scrollView 41 | do { 42 | scrollView.contentSize = CGSize(width: scrollView.frame.width * CGFloat(pageCount), height: scrollView.frame.height) 43 | 44 | for i in 0 ..< pageCount { 45 | let subview = UIView(frame: CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)) 46 | subview.backgroundColor = randomColor(hue: .blue, luminosity: .bright) 47 | scrollView.addSubview(subview) 48 | } 49 | } 50 | 51 | // add pac-man 52 | view.addSubview(pacman2) 53 | view.addSubview(pacman3) 54 | } 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.3.1 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /PacmanPageControl-Demo/PacmanPageControl/PacmanPageControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // PacmanPageControl.h 3 | // PacmanPageControl 4 | // 5 | // Created by ooatuoo on 2017/3/28. 6 | // Copyright © 2017年 ooatuoo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for PacmanPageControl. 12 | FOUNDATION_EXPORT double PacmanPageControlVersionNumber; 13 | 14 | //! Project version string for PacmanPageControl. 15 | FOUNDATION_EXPORT const unsigned char PacmanPageControlVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /PacmanPageControl.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "PacmanPageControl" 3 | s.version = "0.3.1" 4 | s.summary = <<-DESC 5 | Let's play Pac-Man. 6 | DESC 7 | 8 | s.license = { :type => 'MIT', :file => 'LICENSE' } 9 | s.homepage = "https://github.com/atuooo/PacmanPageControl" 10 | 11 | s.authors = { "oOatuo" => "aaatuooo@gmail.com" } 12 | s.social_media_url = "https://twitter.com/OoAtuo" 13 | 14 | s.ios.deployment_target = "8.0" 15 | 16 | s.source = { 17 | :git => "https://github.com/atuooo/PacmanPageControl.git", 18 | :tag => s.version 19 | } 20 | 21 | s.source_files = "Sources/*.swift" 22 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '4.0' } 23 | s.requires_arc = true 24 | end 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PacmanPageControl 2 | 3 | ### Let's play Pac-Man: 4 | 5 |

6 | demo 7 |

8 | 9 | ## Install 10 | 11 | ### [CocoaPods](https://cocoapods.org/) 12 | 13 | To integrate PacmanPageControl into your Xcode project using CocoaPods, specify it to a target in your Podfile: 14 | 15 | ```ruby 16 | pod 'PacmanPageControl' 17 | ``` 18 | 19 | ### [Carthage](https://github.com/Carthage/Carthage) 20 | 21 | ```ogdl 22 | github "atuooo/PacmanPageControl" 23 | ``` 24 | 25 | ### Manually 26 | 27 | Clone this repo and throw the source files under `Sources` folder into your project to use it. 28 | 29 | ## Example 30 | 31 | ```swift 32 | // init 33 | let pacman = PacmanPageControl(frame: pacmanFrame) 34 | pacman.scrollView = scrollView 35 | // pacman.dotColorStyle = .random(hue: .orange, luminosity: .light) 36 | // pacman.pacmanColorStyle = .changeWithEatenDot 37 | 38 | view.addSubview(pacman) 39 | ``` 40 | 41 | **You can also set it in Interface Builder.** 42 | 43 | ## Acknowledgements 44 | 45 | Thanks for onevcat's [RandomColorSwift](https://github.com/onevcat/RandomColorSwift). 46 | 47 | ## License 48 | 49 | PacmanPageControl is released under the terms of the MIT license. See [LICENSE](LICENSE) for details. 50 | -------------------------------------------------------------------------------- /Sources/PacmanLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PacmanLayer.swift 3 | // PacmanPageControl 4 | // 5 | // Copyright (c) 2017 oOatuo (aaatuooo@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | import UIKit 26 | 27 | class PacmanLayer: CALayer { 28 | public var color: UIColor! 29 | public var direction: ScrollDirection = .right 30 | 31 | public var factor: CGFloat = 0 { 32 | didSet { 33 | setNeedsDisplay() 34 | } 35 | } 36 | 37 | fileprivate var diameter: CGFloat! 38 | fileprivate let lineWidth: CGFloat = 1.0 39 | fileprivate let lineColor: UIColor = .white 40 | 41 | enum ScrollDirection { 42 | case right 43 | case left 44 | } 45 | 46 | override func draw(in ctx: CGContext) { 47 | let center = CGPoint(x: frame.width / 2, y: frame.height / 2) 48 | let theAngle = calculateAngle(with: direction == .left ? 1-factor : factor) 49 | 50 | let currentAngle = direction == .right ? theAngle : theAngle - CGFloat.pi 51 | 52 | ctx.setShouldAntialias(true) 53 | ctx.setAllowsAntialiasing(true) 54 | 55 | // draw body 56 | 57 | ctx.beginPath() 58 | ctx.addArc(center: center, radius: (frame.width / 2 - lineWidth), startAngle: currentAngle, endAngle: -currentAngle, clockwise: false) 59 | ctx.addLine(to: center) 60 | ctx.closePath() 61 | 62 | ctx.setLineWidth(lineWidth) 63 | ctx.setStrokeColor(lineColor.cgColor) 64 | ctx.setFillColor(color.cgColor) 65 | ctx.drawPath(using: .fillStroke) 66 | ctx.fillPath() 67 | 68 | // draw eye 69 | 70 | let eyeDiameter = 0.15 * frame.width 71 | let eyeRect = CGRect(x: (frame.width - eyeDiameter) / 2, y: frame.height / 4 - eyeDiameter / 2, width: eyeDiameter, height: eyeDiameter) 72 | 73 | ctx.beginPath() 74 | ctx.addEllipse(in: eyeRect) 75 | ctx.setFillColor(lineColor.cgColor) 76 | ctx.fillPath() 77 | } 78 | 79 | fileprivate func calculateAngle(with x: CGFloat) -> CGFloat { 80 | let minAngle = CGFloat(27 / 180 * CGFloat.pi) 81 | let maxAngle = CGFloat(49 / 180 * CGFloat.pi) 82 | let zeroAngle = CGFloat(3 / 180 * CGFloat.pi) 83 | let diffAngle = maxAngle - minAngle 84 | 85 | if x <= 0.5 { 86 | return -4 * diffAngle * x * x + 4 * diffAngle * x + minAngle 87 | 88 | } else if x <= 0.75 { 89 | return 4 * (zeroAngle - maxAngle) * x + 3 * maxAngle - 2 * zeroAngle 90 | 91 | } else { 92 | return 4 * (minAngle - zeroAngle) * x + 4 * zeroAngle - 3 * minAngle 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/PacmanPageControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PacmanPageControl.swift 3 | // PacmanPageControl 4 | // 5 | // Copyright (c) 2017 oOatuo (aaatuooo@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | import UIKit 26 | 27 | @IBDesignable 28 | open class PacmanPageControl: UIView { 29 | public enum DotColorStyle { 30 | case same(UIColor) 31 | case different([UIColor]) 32 | case random(hue: Hue, luminosity: Luminosity) 33 | } 34 | 35 | public enum PacmanColorStyle { 36 | case fixed(UIColor) 37 | case changeWithEatenDot 38 | } 39 | 40 | open var dotColorStyle: DotColorStyle = .random(hue: .random, luminosity: .light) 41 | open var pacmanColorStyle: PacmanColorStyle = .changeWithEatenDot 42 | 43 | @IBInspectable open var pacmanDiameter: CGFloat = 12 44 | @IBInspectable open var dotDiameter: CGFloat = 5 45 | @IBInspectable open var dotInterval: CGFloat = 0 46 | 47 | @IBOutlet open var scrollView: UIScrollView? { 48 | didSet { 49 | guard oldValue !== scrollView else { return } 50 | 51 | [#keyPath(UIScrollView.contentOffset)].forEach { 52 | oldValue?.removeObserver(self, forKeyPath: $0) 53 | scrollView?.addObserver(self, forKeyPath: $0, options: [.new, .old, .initial], context: nil) 54 | } 55 | 56 | if let newSV = scrollView { 57 | pageCount = lroundf(Float(newSV.contentSize.width / newSV.frame.width)) 58 | } 59 | } 60 | } 61 | 62 | open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 63 | guard keyPath == #keyPath(UIScrollView.contentOffset), let scrlView = scrollView else { return } 64 | scroll(with: scrlView) 65 | } 66 | 67 | open override func didMoveToSuperview() { 68 | super.didMoveToSuperview() 69 | isUserInteractionEnabled = false 70 | setDotColors() 71 | setSubLayers() 72 | } 73 | 74 | deinit { 75 | scrollView?.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset)) 76 | } 77 | 78 | #if TARGET_INTERFACE_BUILDER 79 | open override func draw(_ rect: CGRect) { 80 | setSubLayers() 81 | } 82 | 83 | open override func prepareForInterfaceBuilder() { 84 | pageCount = 6 85 | } 86 | #endif 87 | 88 | // MARK: - Private 89 | 90 | private var pageCount: Int = 0 { 91 | didSet { 92 | guard oldValue != pageCount, superview != nil else { return } 93 | setDotColors() 94 | setSubLayers() 95 | } 96 | } 97 | 98 | private var progress: CGFloat = 0 99 | private var pacmanOriginX: CGFloat = 0 100 | private var lastContentOffsetX: CGFloat = 0 101 | 102 | private var dotColors: [UIColor] = [] 103 | private var dotLayers: [CAShapeLayer] = [] 104 | 105 | private lazy var pacmanLayer: PacmanLayer = { 106 | return PacmanLayer() 107 | }() 108 | 109 | private func scroll(with scrollView: UIScrollView) { 110 | let svOffsetX = scrollView.contentOffset.x 111 | let svWidth = scrollView.frame.width 112 | progress = svOffsetX / svWidth 113 | 114 | let checkCount = lroundf(Float(scrollView.contentSize.width / svWidth)) 115 | guard checkCount == pageCount, checkCount > 0 else { return pageCount = checkCount } 116 | 117 | if lastContentOffsetX < svOffsetX { 118 | pacmanLayer.direction = .right 119 | } else if lastContentOffsetX > svOffsetX { 120 | pacmanLayer.direction = .left 121 | } 122 | 123 | let offset = abs(fmodf(Float(svOffsetX), Float(svWidth))) 124 | let factor = max(0, CGFloat(offset)/svWidth) 125 | 126 | if factor == 0 { 127 | lastContentOffsetX = svOffsetX 128 | 129 | if case .changeWithEatenDot = pacmanColorStyle { 130 | pacmanLayer.color = dotColors[Int(progress)] 131 | } 132 | } 133 | 134 | CATransaction.begin() 135 | CATransaction.setDisableActions(true) 136 | pacmanLayer.position.x = pacmanOriginX + pacmanDiameter / 2 + progress * (dotDiameter + dotInterval) 137 | CATransaction.commit() 138 | 139 | pacmanLayer.factor = CGFloat(factor) 140 | 141 | for (index, layer) in dotLayers.enumerated() { 142 | setDotLayer(layer, at: index) 143 | } 144 | } 145 | 146 | private func setDotColors() { 147 | guard pageCount != 0 else { return dotColors.removeAll() } 148 | 149 | switch dotColorStyle { 150 | case .same(let color): 151 | dotColors = Array(repeating: color, count: pageCount) 152 | 153 | case .different(let colors): 154 | if colors.isEmpty { 155 | let defaultColor = UIColor(red: 0.94, green: 0.72, blue: 0.3, alpha: 1) 156 | dotColors = Array.init(repeating: defaultColor, count: pageCount) 157 | 158 | } else { 159 | (0 ..< (pageCount / colors.count)).forEach { _ in 160 | dotColors.append(contentsOf: colors) 161 | } 162 | 163 | dotColors.append(contentsOf: colors.prefix(pageCount % colors.count)) 164 | } 165 | 166 | case .random(let hue, let luminosity): 167 | dotColors = randomColors(count: pageCount, hue: hue, luminosity: luminosity) 168 | } 169 | } 170 | 171 | private func setSubLayers() { 172 | dotLayers.forEach { $0.removeFromSuperlayer() } 173 | pacmanLayer.removeFromSuperlayer() 174 | 175 | guard pageCount != 0 else { return } 176 | 177 | if dotInterval <= 0 { 178 | dotInterval = dotDiameter * 1.2 179 | } 180 | 181 | let dotTotalWidth = dotDiameter * CGFloat(pageCount) + dotInterval * CGFloat(pageCount - 1) 182 | let dotOriginY = (frame.height - dotDiameter) / 2 183 | let dotOriginX = (frame.width - dotTotalWidth) / 2 184 | pacmanOriginX = dotOriginX + dotDiameter / 2 - pacmanDiameter / 2 185 | 186 | var dotFrame = CGRect(x: dotOriginX, y: dotOriginY, width: dotDiameter, height: dotDiameter) 187 | 188 | dotLayers = (0..= 0 && progress <= CGFloat(pageCount - 1) else { return } 215 | 216 | let originRect = CGRect(x: 0, y: 0, width: dotDiameter, height: dotDiameter) 217 | 218 | let offset = abs(progress - CGFloat(index)) 219 | let x = min(1, offset) 220 | let insetDis = dotDiameter / 2 * (x * x - 2 * x + 1) 221 | 222 | let scaleRect = originRect.insetBy(dx: insetDis, dy: insetDis) 223 | dotLayer.path = UIBezierPath(ovalIn: scaleRect).cgPath 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /Sources/RandomColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomColor.swift 3 | // RandomColorSwift 4 | // 5 | // Copyright (c) 2016 Wei Wang (http://onevcat.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | #if os(iOS) 26 | import UIKit 27 | public typealias Color = UIColor 28 | #else 29 | import Cocoa 30 | public typealias Color = NSColor 31 | #endif 32 | 33 | private var colorDictionary: [Hue: ColorDefinition] = [ 34 | .monochrome: ColorDefinition(hueRange: nil, lowerBounds: [(0,0), (100,0)]), 35 | .red: ColorDefinition(hueRange: (-26,18), lowerBounds: [(20,100), (30,92), (40,89), (50,85), (60,78), (70,70), (80,60), (90,55), (100,50)]), 36 | .orange: ColorDefinition(hueRange: (19,46), lowerBounds: [(20,100), (30,93), (40,88), (50,86), (60,85), (70,70), (100,70)]), 37 | .yellow: ColorDefinition(hueRange: (47,62), lowerBounds: [(25,100), (40,94), (50,89), (60,86), (70,84), (80,82), (90,80), (100,75)]), 38 | .green: ColorDefinition(hueRange: (63,178), lowerBounds: [(30,100), (40,90), (50,85), (60,81), (70,74), (80,64), (90,50), (100,40)]), 39 | .blue: ColorDefinition(hueRange: (179,257), lowerBounds: [(20,100), (30,86), (40,80), (50,74), (60,60), (70,52), (80,44), (90,39), (100,35)]), 40 | .purple: ColorDefinition(hueRange: (258, 282), lowerBounds: [(20,100), (30,87), (40,79), (50,70), (60,65), (70,59), (80,52), (90,45), (100,42)]), 41 | .pink: ColorDefinition(hueRange: (283, 334), lowerBounds: [(20,100), (30,90), (40,86), (60,84), (80,80), (90,75), (100,73)]) 42 | ] 43 | 44 | extension Hue { 45 | var range: Range { 46 | switch self { 47 | case .value(let value): return (value, value) 48 | case .random: return (0, 360) 49 | default: 50 | if let colorDefinition = colorDictionary[self] { 51 | return colorDefinition.hueRange ?? (0, 360) 52 | } else { 53 | assert(false, "Unrecgonized Hue enum: \(self).") 54 | return (0, 360) 55 | } 56 | } 57 | } 58 | } 59 | 60 | /** 61 | Generate a single random color with some conditions. 62 | 63 | - parameter hue: Hue of target color. It will be the main property of the generated color. Default is .Random. 64 | - parameter luminosity: Luminosity of target color. It will decide the brightness of generated color. Default is .Random. 65 | 66 | - returns: A random color following input conditions. It will be a `UIColor` object for iOS target, and an `NSColor` object for OSX target. 67 | */ 68 | public func randomColor(hue: Hue = .random, luminosity: Luminosity = .random) -> Color { 69 | 70 | func random(in range: Range) -> Int { 71 | assert(range.max >= range.min, "Max in range should be greater than min") 72 | return Int(arc4random_uniform(UInt32(range.max - range.min))) + range.min 73 | } 74 | 75 | func getColorDefinition(hueValue: Int) -> ColorDefinition { 76 | var hueValue = hueValue 77 | 78 | if hueValue >= 334 && hueValue <= 360 { 79 | hueValue -= 360 80 | } 81 | 82 | let color = colorDictionary.values.filter({ (definition: ColorDefinition) -> Bool in 83 | if let hueRange = definition.hueRange { 84 | return hueValue >= hueRange.min && hueValue <= hueRange.max 85 | } else { 86 | return false 87 | } 88 | }) 89 | 90 | assert(color.count == 1, "There should one and only one color satisfied the filter") 91 | return color.first! 92 | } 93 | 94 | func pickHue(_ hue: Hue) -> Int { 95 | var hueValue = random(in: hue.range) 96 | 97 | // Instead of storing red as two seperate ranges, 98 | // we group them, using negative numbers 99 | if hueValue < 0 { 100 | hueValue = hueValue + 360 101 | } 102 | return hueValue 103 | } 104 | 105 | func pickSaturation(color: ColorDefinition, hue: Hue, luminosity: Luminosity) -> Int { 106 | var color = color 107 | 108 | if luminosity == .random { 109 | return random(in: (0, 100)) 110 | } 111 | 112 | if hue == .monochrome { 113 | return 0 114 | } 115 | 116 | let saturationRange = color.saturationRange 117 | var sMin = saturationRange.min 118 | var sMax = saturationRange.max 119 | 120 | switch luminosity { 121 | case .bright: 122 | sMin = 55 123 | case .dark: 124 | sMin = sMax - 10 125 | case .light: 126 | sMax = 55 127 | default: () 128 | } 129 | 130 | return random(in: (sMin, sMax)) 131 | } 132 | 133 | func pickBrightness(color: ColorDefinition, saturationValue: Int, luminosity: Luminosity) -> Int { 134 | var color = color 135 | 136 | func getMinimumBrightness(saturationValue: Int) -> Int { 137 | var lowerBounds = color.lowerBounds; 138 | for i in 0 ..< lowerBounds.count - 1 { 139 | 140 | let s1 = Float(lowerBounds[i].0) 141 | let v1 = Float(lowerBounds[i].1) 142 | 143 | let s2 = Float(lowerBounds[i+1].0) 144 | let v2 = Float(lowerBounds[i+1].1) 145 | 146 | if Float(saturationValue) >= s1 && Float(saturationValue) <= s2 { 147 | let m = (v2 - v1) / (s2 - s1) 148 | let b = v1 - m * s1 149 | return lroundf(m * Float(saturationValue) + b) 150 | } 151 | } 152 | return 0 153 | } 154 | 155 | var bMin = getMinimumBrightness(saturationValue: saturationValue) 156 | var bMax = 100 157 | 158 | switch luminosity { 159 | case .dark: 160 | bMax = bMin + 20 161 | case .light: 162 | bMin = (bMax + bMin) / 2 163 | case .random: 164 | bMin = 0 165 | bMax = 100 166 | default: () 167 | } 168 | 169 | return random(in: (bMin, bMax)) 170 | } 171 | 172 | 173 | let hueValue = pickHue(hue) 174 | 175 | let color = getColorDefinition(hueValue: hueValue) 176 | 177 | let saturationValue = pickSaturation(color: color, hue: hue, luminosity: luminosity) 178 | let brightnessValue = pickBrightness(color: color, saturationValue: saturationValue, luminosity: luminosity) 179 | 180 | #if os(iOS) 181 | return Color(hue: CGFloat(hueValue) / 360.0, 182 | saturation: CGFloat(saturationValue) / 100.0, 183 | brightness: CGFloat(brightnessValue) / 100.0, 184 | alpha: 1.0) 185 | #else 186 | return Color(deviceHue: CGFloat(hueValue) / 360.0, 187 | saturation: CGFloat(saturationValue) / 100.0, 188 | brightness: CGFloat(brightnessValue) / 100.0, 189 | alpha: 1.0) 190 | #endif 191 | } 192 | 193 | /** 194 | Generate a set of random colors with some conditions. 195 | 196 | - parameter count: The count of how many colors will be generated. 197 | - parameter hue: Hue of target color. It will be the main property of the generated color. Default is .Random. 198 | - parameter luminosity: Luminosity of target color. It will decide the brightness of generated color. Default is .Random. 199 | 200 | - returns: An array of random colors following input conditions. The elements will be `UIColor` objects for iOS target, and `NSColor` objects for OSX target. 201 | */ 202 | public func randomColors(count: Int, hue: Hue = .random, luminosity: Luminosity = .random) -> [Color] { 203 | var colors: [Color] = [] 204 | while (colors.count < count) { 205 | colors.append(randomColor(hue: hue, luminosity: luminosity)) 206 | } 207 | return colors 208 | } 209 | 210 | public enum Luminosity: Int { 211 | case bright, light, dark 212 | case random 213 | } 214 | 215 | typealias Range = (min: Int, max: Int) 216 | 217 | struct ColorDefinition { 218 | let hueRange: Range? 219 | let lowerBounds: [Range] 220 | 221 | lazy var saturationRange: Range = { 222 | let sMin = self.lowerBounds[0].0 223 | let sMax = self.lowerBounds[self.lowerBounds.count - 1].0 224 | return (sMin, sMax) 225 | }() 226 | 227 | lazy var brightnessRange: Range = { 228 | let bMin = self.lowerBounds[self.lowerBounds.count - 1].1 229 | let bMax = self.lowerBounds[0].1 230 | return (bMin, bMax) 231 | }() 232 | 233 | init(hueRange: Range?, lowerBounds: [Range]) { 234 | self.hueRange = hueRange 235 | self.lowerBounds = lowerBounds 236 | } 237 | } 238 | 239 | public enum Hue { 240 | 241 | case monochrome, red, orange, yellow, green, blue, purple, pink 242 | case value(Int) 243 | case random 244 | 245 | public func toInt() -> Int { 246 | switch self { 247 | case .monochrome: return 1 248 | case .red: return 2 249 | case .orange: return 3 250 | case .yellow: return 4 251 | case .green: return 5 252 | case .blue: return 6 253 | case .purple: return 7 254 | case .pink: return 8 255 | case .value(_): return -1 256 | case .random: return 0 257 | } 258 | } 259 | } 260 | 261 | public func == (lhs: Hue, rhs: Hue) -> Bool { 262 | return lhs.toInt() == rhs.toInt() 263 | } 264 | 265 | extension Hue: Hashable { 266 | public var hashValue: Int { 267 | get { 268 | return self.toInt() 269 | } 270 | } 271 | } 272 | 273 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atuooo/PacmanPageControl/c2168f980be3d9690b785614641202e715ac4bca/demo.gif --------------------------------------------------------------------------------