├── .gitignore ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── ExampleTests │ └── Info.plist └── ExampleUITests │ └── Info.plist ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── AIFlatSwitch.h └── AIFlatSwitch.swift └── Tests ├── AIFlatSwitchTests.swift └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | *.xcworkspacedata 39 | xcshareddata/ 40 | Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | # 51 | # Pods/ 52 | 53 | # Carthage 54 | # 55 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 56 | # Carthage/Checkouts 57 | 58 | Carthage/Build 59 | 60 | # fastlane 61 | # 62 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 63 | # screenshots whenever they are needed. 64 | # For more information about the recommended setup visit: 65 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 66 | 67 | fastlane/report.xml 68 | fastlane/Preview.html 69 | fastlane/screenshots/**/*.png 70 | fastlane/test_output 71 | 72 | .DS_Store -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4E0A87D022F8DA450007472B /* AIFlatSwitch in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0A87CF22F8DA450007472B /* AIFlatSwitch */; }; 11 | 4ED8F5651D8DED0500B97B8D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED8F5641D8DED0500B97B8D /* AppDelegate.swift */; }; 12 | 4ED8F5671D8DED0500B97B8D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED8F5661D8DED0500B97B8D /* ViewController.swift */; }; 13 | 4ED8F56A1D8DED0500B97B8D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4ED8F5681D8DED0500B97B8D /* Main.storyboard */; }; 14 | 4ED8F56C1D8DED0500B97B8D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4ED8F56B1D8DED0500B97B8D /* Assets.xcassets */; }; 15 | 4ED8F56F1D8DED0500B97B8D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4ED8F56D1D8DED0500B97B8D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | 4ED8F5761D8DED0500B97B8D /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = 4ED8F5591D8DED0500B97B8D /* Project object */; 22 | proxyType = 1; 23 | remoteGlobalIDString = 4ED8F5601D8DED0500B97B8D; 24 | remoteInfo = Example; 25 | }; 26 | 4ED8F5811D8DED0500B97B8D /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 4ED8F5591D8DED0500B97B8D /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 4ED8F5601D8DED0500B97B8D; 31 | remoteInfo = Example; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 4E0A87CD22F8DA340007472B /* AIFlatSwitch */ = {isa = PBXFileReference; lastKnownFileType = folder; name = AIFlatSwitch; path = ..; sourceTree = ""; }; 37 | 4ED8F5611D8DED0500B97B8D /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 4ED8F5641D8DED0500B97B8D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 4ED8F5661D8DED0500B97B8D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 40 | 4ED8F5691D8DED0500B97B8D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 41 | 4ED8F56B1D8DED0500B97B8D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42 | 4ED8F56E1D8DED0500B97B8D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 43 | 4ED8F5701D8DED0500B97B8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | 4ED8F5751D8DED0500B97B8D /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 4ED8F57B1D8DED0500B97B8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 4ED8F5801D8DED0500B97B8D /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 4ED8F5861D8DED0500B97B8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 4ED8F55E1D8DED0500B97B8D /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | 4E0A87D022F8DA450007472B /* AIFlatSwitch in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | 4ED8F5721D8DED0500B97B8D /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | 4ED8F57D1D8DED0500B97B8D /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 4E0A87CE22F8DA450007472B /* Frameworks */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | ); 80 | name = Frameworks; 81 | sourceTree = ""; 82 | }; 83 | 4ED8F5581D8DED0500B97B8D = { 84 | isa = PBXGroup; 85 | children = ( 86 | 4E0A87CD22F8DA340007472B /* AIFlatSwitch */, 87 | 4ED8F5631D8DED0500B97B8D /* Example */, 88 | 4ED8F5781D8DED0500B97B8D /* ExampleTests */, 89 | 4ED8F5831D8DED0500B97B8D /* ExampleUITests */, 90 | 4ED8F5621D8DED0500B97B8D /* Products */, 91 | 4E0A87CE22F8DA450007472B /* Frameworks */, 92 | ); 93 | sourceTree = ""; 94 | }; 95 | 4ED8F5621D8DED0500B97B8D /* Products */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 4ED8F5611D8DED0500B97B8D /* Example.app */, 99 | 4ED8F5751D8DED0500B97B8D /* ExampleTests.xctest */, 100 | 4ED8F5801D8DED0500B97B8D /* ExampleUITests.xctest */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | 4ED8F5631D8DED0500B97B8D /* Example */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 4ED8F5641D8DED0500B97B8D /* AppDelegate.swift */, 109 | 4ED8F5661D8DED0500B97B8D /* ViewController.swift */, 110 | 4ED8F5681D8DED0500B97B8D /* Main.storyboard */, 111 | 4ED8F56B1D8DED0500B97B8D /* Assets.xcassets */, 112 | 4ED8F56D1D8DED0500B97B8D /* LaunchScreen.storyboard */, 113 | 4ED8F5701D8DED0500B97B8D /* Info.plist */, 114 | ); 115 | path = Example; 116 | sourceTree = ""; 117 | }; 118 | 4ED8F5781D8DED0500B97B8D /* ExampleTests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 4ED8F57B1D8DED0500B97B8D /* Info.plist */, 122 | ); 123 | path = ExampleTests; 124 | sourceTree = ""; 125 | }; 126 | 4ED8F5831D8DED0500B97B8D /* ExampleUITests */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 4ED8F5861D8DED0500B97B8D /* Info.plist */, 130 | ); 131 | path = ExampleUITests; 132 | sourceTree = ""; 133 | }; 134 | /* End PBXGroup section */ 135 | 136 | /* Begin PBXNativeTarget section */ 137 | 4ED8F5601D8DED0500B97B8D /* Example */ = { 138 | isa = PBXNativeTarget; 139 | buildConfigurationList = 4ED8F5891D8DED0500B97B8D /* Build configuration list for PBXNativeTarget "Example" */; 140 | buildPhases = ( 141 | 4ED8F55D1D8DED0500B97B8D /* Sources */, 142 | 4ED8F55E1D8DED0500B97B8D /* Frameworks */, 143 | 4ED8F55F1D8DED0500B97B8D /* Resources */, 144 | ); 145 | buildRules = ( 146 | ); 147 | dependencies = ( 148 | ); 149 | name = Example; 150 | packageProductDependencies = ( 151 | 4E0A87CF22F8DA450007472B /* AIFlatSwitch */, 152 | ); 153 | productName = Example; 154 | productReference = 4ED8F5611D8DED0500B97B8D /* Example.app */; 155 | productType = "com.apple.product-type.application"; 156 | }; 157 | 4ED8F5741D8DED0500B97B8D /* ExampleTests */ = { 158 | isa = PBXNativeTarget; 159 | buildConfigurationList = 4ED8F58C1D8DED0500B97B8D /* Build configuration list for PBXNativeTarget "ExampleTests" */; 160 | buildPhases = ( 161 | 4ED8F5711D8DED0500B97B8D /* Sources */, 162 | 4ED8F5721D8DED0500B97B8D /* Frameworks */, 163 | 4ED8F5731D8DED0500B97B8D /* Resources */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | 4ED8F5771D8DED0500B97B8D /* PBXTargetDependency */, 169 | ); 170 | name = ExampleTests; 171 | productName = ExampleTests; 172 | productReference = 4ED8F5751D8DED0500B97B8D /* ExampleTests.xctest */; 173 | productType = "com.apple.product-type.bundle.unit-test"; 174 | }; 175 | 4ED8F57F1D8DED0500B97B8D /* ExampleUITests */ = { 176 | isa = PBXNativeTarget; 177 | buildConfigurationList = 4ED8F58F1D8DED0500B97B8D /* Build configuration list for PBXNativeTarget "ExampleUITests" */; 178 | buildPhases = ( 179 | 4ED8F57C1D8DED0500B97B8D /* Sources */, 180 | 4ED8F57D1D8DED0500B97B8D /* Frameworks */, 181 | 4ED8F57E1D8DED0500B97B8D /* Resources */, 182 | ); 183 | buildRules = ( 184 | ); 185 | dependencies = ( 186 | 4ED8F5821D8DED0500B97B8D /* PBXTargetDependency */, 187 | ); 188 | name = ExampleUITests; 189 | productName = ExampleUITests; 190 | productReference = 4ED8F5801D8DED0500B97B8D /* ExampleUITests.xctest */; 191 | productType = "com.apple.product-type.bundle.ui-testing"; 192 | }; 193 | /* End PBXNativeTarget section */ 194 | 195 | /* Begin PBXProject section */ 196 | 4ED8F5591D8DED0500B97B8D /* Project object */ = { 197 | isa = PBXProject; 198 | attributes = { 199 | LastSwiftUpdateCheck = 0800; 200 | LastUpgradeCheck = 1320; 201 | ORGANIZATIONNAME = cocoatoucher; 202 | TargetAttributes = { 203 | 4ED8F5601D8DED0500B97B8D = { 204 | CreatedOnToolsVersion = 8.0; 205 | LastSwiftMigration = 1000; 206 | ProvisioningStyle = Automatic; 207 | }; 208 | 4ED8F5741D8DED0500B97B8D = { 209 | CreatedOnToolsVersion = 8.0; 210 | LastSwiftMigration = 0800; 211 | ProvisioningStyle = Automatic; 212 | TestTargetID = 4ED8F5601D8DED0500B97B8D; 213 | }; 214 | 4ED8F57F1D8DED0500B97B8D = { 215 | CreatedOnToolsVersion = 8.0; 216 | LastSwiftMigration = 0800; 217 | ProvisioningStyle = Automatic; 218 | TestTargetID = 4ED8F5601D8DED0500B97B8D; 219 | }; 220 | }; 221 | }; 222 | buildConfigurationList = 4ED8F55C1D8DED0500B97B8D /* Build configuration list for PBXProject "Example" */; 223 | compatibilityVersion = "Xcode 3.2"; 224 | developmentRegion = en; 225 | hasScannedForEncodings = 0; 226 | knownRegions = ( 227 | en, 228 | Base, 229 | ); 230 | mainGroup = 4ED8F5581D8DED0500B97B8D; 231 | productRefGroup = 4ED8F5621D8DED0500B97B8D /* Products */; 232 | projectDirPath = ""; 233 | projectRoot = ""; 234 | targets = ( 235 | 4ED8F5601D8DED0500B97B8D /* Example */, 236 | 4ED8F5741D8DED0500B97B8D /* ExampleTests */, 237 | 4ED8F57F1D8DED0500B97B8D /* ExampleUITests */, 238 | ); 239 | }; 240 | /* End PBXProject section */ 241 | 242 | /* Begin PBXResourcesBuildPhase section */ 243 | 4ED8F55F1D8DED0500B97B8D /* Resources */ = { 244 | isa = PBXResourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | 4ED8F56F1D8DED0500B97B8D /* LaunchScreen.storyboard in Resources */, 248 | 4ED8F56C1D8DED0500B97B8D /* Assets.xcassets in Resources */, 249 | 4ED8F56A1D8DED0500B97B8D /* Main.storyboard in Resources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | 4ED8F5731D8DED0500B97B8D /* Resources */ = { 254 | isa = PBXResourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | }; 260 | 4ED8F57E1D8DED0500B97B8D /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | /* End PBXResourcesBuildPhase section */ 268 | 269 | /* Begin PBXSourcesBuildPhase section */ 270 | 4ED8F55D1D8DED0500B97B8D /* Sources */ = { 271 | isa = PBXSourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 4ED8F5671D8DED0500B97B8D /* ViewController.swift in Sources */, 275 | 4ED8F5651D8DED0500B97B8D /* AppDelegate.swift in Sources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | 4ED8F5711D8DED0500B97B8D /* Sources */ = { 280 | isa = PBXSourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | 4ED8F57C1D8DED0500B97B8D /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | /* End PBXSourcesBuildPhase section */ 294 | 295 | /* Begin PBXTargetDependency section */ 296 | 4ED8F5771D8DED0500B97B8D /* PBXTargetDependency */ = { 297 | isa = PBXTargetDependency; 298 | target = 4ED8F5601D8DED0500B97B8D /* Example */; 299 | targetProxy = 4ED8F5761D8DED0500B97B8D /* PBXContainerItemProxy */; 300 | }; 301 | 4ED8F5821D8DED0500B97B8D /* PBXTargetDependency */ = { 302 | isa = PBXTargetDependency; 303 | target = 4ED8F5601D8DED0500B97B8D /* Example */; 304 | targetProxy = 4ED8F5811D8DED0500B97B8D /* PBXContainerItemProxy */; 305 | }; 306 | /* End PBXTargetDependency section */ 307 | 308 | /* Begin PBXVariantGroup section */ 309 | 4ED8F5681D8DED0500B97B8D /* Main.storyboard */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | 4ED8F5691D8DED0500B97B8D /* Base */, 313 | ); 314 | name = Main.storyboard; 315 | sourceTree = ""; 316 | }; 317 | 4ED8F56D1D8DED0500B97B8D /* LaunchScreen.storyboard */ = { 318 | isa = PBXVariantGroup; 319 | children = ( 320 | 4ED8F56E1D8DED0500B97B8D /* Base */, 321 | ); 322 | name = LaunchScreen.storyboard; 323 | sourceTree = ""; 324 | }; 325 | /* End PBXVariantGroup section */ 326 | 327 | /* Begin XCBuildConfiguration section */ 328 | 4ED8F5871D8DED0500B97B8D /* Debug */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 333 | CLANG_ANALYZER_NONNULL = YES; 334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 335 | CLANG_CXX_LIBRARY = "libc++"; 336 | CLANG_ENABLE_MODULES = YES; 337 | CLANG_ENABLE_OBJC_ARC = YES; 338 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 339 | CLANG_WARN_BOOL_CONVERSION = YES; 340 | CLANG_WARN_COMMA = YES; 341 | CLANG_WARN_CONSTANT_CONVERSION = YES; 342 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 345 | CLANG_WARN_EMPTY_BODY = YES; 346 | CLANG_WARN_ENUM_CONVERSION = YES; 347 | CLANG_WARN_INFINITE_RECURSION = YES; 348 | CLANG_WARN_INT_CONVERSION = YES; 349 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 351 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 353 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 355 | CLANG_WARN_STRICT_PROTOTYPES = YES; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = dwarf; 363 | ENABLE_STRICT_OBJC_MSGSEND = YES; 364 | ENABLE_TESTABILITY = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_DYNAMIC_NO_PIC = NO; 367 | GCC_NO_COMMON_BLOCKS = YES; 368 | GCC_OPTIMIZATION_LEVEL = 0; 369 | GCC_PREPROCESSOR_DEFINITIONS = ( 370 | "DEBUG=1", 371 | "$(inherited)", 372 | ); 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 380 | MTL_ENABLE_DEBUG_INFO = YES; 381 | ONLY_ACTIVE_ARCH = YES; 382 | SDKROOT = iphoneos; 383 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 384 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 385 | }; 386 | name = Debug; 387 | }; 388 | 4ED8F5881D8DED0500B97B8D /* Release */ = { 389 | isa = XCBuildConfiguration; 390 | buildSettings = { 391 | ALWAYS_SEARCH_USER_PATHS = NO; 392 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 393 | CLANG_ANALYZER_NONNULL = YES; 394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 395 | CLANG_CXX_LIBRARY = "libc++"; 396 | CLANG_ENABLE_MODULES = YES; 397 | CLANG_ENABLE_OBJC_ARC = YES; 398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_COMMA = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 404 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 405 | CLANG_WARN_EMPTY_BODY = YES; 406 | CLANG_WARN_ENUM_CONVERSION = YES; 407 | CLANG_WARN_INFINITE_RECURSION = YES; 408 | CLANG_WARN_INT_CONVERSION = YES; 409 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 410 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 411 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 412 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 413 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 414 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 415 | CLANG_WARN_STRICT_PROTOTYPES = YES; 416 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 417 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 418 | CLANG_WARN_UNREACHABLE_CODE = YES; 419 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 420 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 421 | COPY_PHASE_STRIP = NO; 422 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 423 | ENABLE_NS_ASSERTIONS = NO; 424 | ENABLE_STRICT_OBJC_MSGSEND = YES; 425 | GCC_C_LANGUAGE_STANDARD = gnu99; 426 | GCC_NO_COMMON_BLOCKS = YES; 427 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 428 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 429 | GCC_WARN_UNDECLARED_SELECTOR = YES; 430 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 431 | GCC_WARN_UNUSED_FUNCTION = YES; 432 | GCC_WARN_UNUSED_VARIABLE = YES; 433 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 434 | MTL_ENABLE_DEBUG_INFO = NO; 435 | SDKROOT = iphoneos; 436 | SWIFT_COMPILATION_MODE = wholemodule; 437 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 438 | VALIDATE_PRODUCT = YES; 439 | }; 440 | name = Release; 441 | }; 442 | 4ED8F58A1D8DED0500B97B8D /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 446 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 447 | INFOPLIST_FILE = Example/Info.plist; 448 | LD_RUNPATH_SEARCH_PATHS = ( 449 | "$(inherited)", 450 | "@executable_path/Frameworks", 451 | ); 452 | PRODUCT_BUNDLE_IDENTIFIER = com.cocoatoucher.Example; 453 | PRODUCT_NAME = "$(TARGET_NAME)"; 454 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 455 | SWIFT_VERSION = 5.0; 456 | }; 457 | name = Debug; 458 | }; 459 | 4ED8F58B1D8DED0500B97B8D /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 463 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 464 | INFOPLIST_FILE = Example/Info.plist; 465 | LD_RUNPATH_SEARCH_PATHS = ( 466 | "$(inherited)", 467 | "@executable_path/Frameworks", 468 | ); 469 | PRODUCT_BUNDLE_IDENTIFIER = com.cocoatoucher.Example; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 472 | SWIFT_VERSION = 5.0; 473 | }; 474 | name = Release; 475 | }; 476 | 4ED8F58D1D8DED0500B97B8D /* Debug */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 480 | BUNDLE_LOADER = "$(TEST_HOST)"; 481 | INFOPLIST_FILE = ExampleTests/Info.plist; 482 | LD_RUNPATH_SEARCH_PATHS = ( 483 | "$(inherited)", 484 | "@executable_path/Frameworks", 485 | "@loader_path/Frameworks", 486 | ); 487 | PRODUCT_BUNDLE_IDENTIFIER = com.cocoatoucher.ExampleTests; 488 | PRODUCT_NAME = "$(TARGET_NAME)"; 489 | SWIFT_VERSION = 3.0; 490 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 491 | }; 492 | name = Debug; 493 | }; 494 | 4ED8F58E1D8DED0500B97B8D /* Release */ = { 495 | isa = XCBuildConfiguration; 496 | buildSettings = { 497 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 498 | BUNDLE_LOADER = "$(TEST_HOST)"; 499 | INFOPLIST_FILE = ExampleTests/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = ( 501 | "$(inherited)", 502 | "@executable_path/Frameworks", 503 | "@loader_path/Frameworks", 504 | ); 505 | PRODUCT_BUNDLE_IDENTIFIER = com.cocoatoucher.ExampleTests; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SWIFT_VERSION = 3.0; 508 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 509 | }; 510 | name = Release; 511 | }; 512 | 4ED8F5901D8DED0500B97B8D /* Debug */ = { 513 | isa = XCBuildConfiguration; 514 | buildSettings = { 515 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 516 | INFOPLIST_FILE = ExampleUITests/Info.plist; 517 | LD_RUNPATH_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "@executable_path/Frameworks", 520 | "@loader_path/Frameworks", 521 | ); 522 | PRODUCT_BUNDLE_IDENTIFIER = com.cocoatoucher.ExampleUITests; 523 | PRODUCT_NAME = "$(TARGET_NAME)"; 524 | SWIFT_VERSION = 3.0; 525 | TEST_TARGET_NAME = Example; 526 | }; 527 | name = Debug; 528 | }; 529 | 4ED8F5911D8DED0500B97B8D /* Release */ = { 530 | isa = XCBuildConfiguration; 531 | buildSettings = { 532 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 533 | INFOPLIST_FILE = ExampleUITests/Info.plist; 534 | LD_RUNPATH_SEARCH_PATHS = ( 535 | "$(inherited)", 536 | "@executable_path/Frameworks", 537 | "@loader_path/Frameworks", 538 | ); 539 | PRODUCT_BUNDLE_IDENTIFIER = com.cocoatoucher.ExampleUITests; 540 | PRODUCT_NAME = "$(TARGET_NAME)"; 541 | SWIFT_VERSION = 3.0; 542 | TEST_TARGET_NAME = Example; 543 | }; 544 | name = Release; 545 | }; 546 | /* End XCBuildConfiguration section */ 547 | 548 | /* Begin XCConfigurationList section */ 549 | 4ED8F55C1D8DED0500B97B8D /* Build configuration list for PBXProject "Example" */ = { 550 | isa = XCConfigurationList; 551 | buildConfigurations = ( 552 | 4ED8F5871D8DED0500B97B8D /* Debug */, 553 | 4ED8F5881D8DED0500B97B8D /* Release */, 554 | ); 555 | defaultConfigurationIsVisible = 0; 556 | defaultConfigurationName = Release; 557 | }; 558 | 4ED8F5891D8DED0500B97B8D /* Build configuration list for PBXNativeTarget "Example" */ = { 559 | isa = XCConfigurationList; 560 | buildConfigurations = ( 561 | 4ED8F58A1D8DED0500B97B8D /* Debug */, 562 | 4ED8F58B1D8DED0500B97B8D /* Release */, 563 | ); 564 | defaultConfigurationIsVisible = 0; 565 | defaultConfigurationName = Release; 566 | }; 567 | 4ED8F58C1D8DED0500B97B8D /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { 568 | isa = XCConfigurationList; 569 | buildConfigurations = ( 570 | 4ED8F58D1D8DED0500B97B8D /* Debug */, 571 | 4ED8F58E1D8DED0500B97B8D /* Release */, 572 | ); 573 | defaultConfigurationIsVisible = 0; 574 | defaultConfigurationName = Release; 575 | }; 576 | 4ED8F58F1D8DED0500B97B8D /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = { 577 | isa = XCConfigurationList; 578 | buildConfigurations = ( 579 | 4ED8F5901D8DED0500B97B8D /* Debug */, 580 | 4ED8F5911D8DED0500B97B8D /* Release */, 581 | ); 582 | defaultConfigurationIsVisible = 0; 583 | defaultConfigurationName = Release; 584 | }; 585 | /* End XCConfigurationList section */ 586 | 587 | /* Begin XCSwiftPackageProductDependency section */ 588 | 4E0A87CF22F8DA450007472B /* AIFlatSwitch */ = { 589 | isa = XCSwiftPackageProductDependency; 590 | productName = AIFlatSwitch; 591 | }; 592 | /* End XCSwiftPackageProductDependency section */ 593 | }; 594 | rootObject = 4ED8F5591D8DED0500B97B8D /* Project object */; 595 | } 596 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AIFlatSwitchExample 4 | // 5 | // Created by cocoatoucher on 03/04/15. 6 | // Copyright (c) 2015 cocoatoucher. 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 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Example/Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/Example/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 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 105 | 111 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // iOS Example 4 | // 5 | // Created by cocoatoucher on 07/03/15. 6 | // Copyright (c) 2015 cocoatoucher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AIFlatSwitch 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var flatSwitch: AIFlatSwitch! 15 | @IBOutlet weak var label: UILabel! 16 | @IBOutlet weak var bigFlatSwitch: AIFlatSwitch! 17 | @IBOutlet weak var smallFlatSwitch: AIFlatSwitch! 18 | @IBOutlet weak var programmaticSwitchContainer: UIView! 19 | var programmaticSwitch: AIFlatSwitch! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | self.programmaticSwitch = AIFlatSwitch(frame: CGRect(x: 0, y: 0, width: programmaticSwitchContainer.frame.width, height: programmaticSwitchContainer.frame.height)) 25 | self.programmaticSwitch.lineWidth = 2.0 26 | self.programmaticSwitch.strokeColor = UIColor.blue 27 | self.programmaticSwitch.backgroundLayerColor = UIColor.white 28 | self.programmaticSwitch.trailStrokeColor = UIColor.red.withAlphaComponent(0.2) 29 | self.programmaticSwitch.autoresizingMask = [.flexibleWidth, .flexibleHeight] 30 | programmaticSwitchContainer.addSubview(programmaticSwitch) 31 | 32 | updateSwitchValue() 33 | } 34 | 35 | @IBAction func onSwitchValueChange(_ sender: AnyObject) { 36 | if sender as? AIFlatSwitch == flatSwitch { 37 | self.updateSwitchValue() 38 | 39 | bigFlatSwitch.setSelected(!bigFlatSwitch.isSelected, animated: true) 40 | smallFlatSwitch.isSelected = !smallFlatSwitch.isSelected 41 | } 42 | } 43 | 44 | func updateSwitchValue() { 45 | label.text = (flatSwitch.isSelected) ? NSLocalizedString("On", comment: "") : NSLocalizedString("Off", comment: "") 46 | } 47 | 48 | override func didReceiveMemoryWarning() { 49 | super.didReceiveMemoryWarning() 50 | // Dispose of any resources that can be recreated. 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Example/ExampleTests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/ExampleUITests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 cocoatoucher (http://github.com/cocoatoucher/AIFlatSwitch) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "AIFlatSwitch", 6 | platforms: [ 7 | .iOS(.v9), .tvOS(.v12) 8 | ], 9 | products: [ 10 | .library(name: "AIFlatSwitch", targets: ["AIFlatSwitch"]) 11 | ], 12 | dependencies: [], 13 | targets: [ 14 | .target(name: "AIFlatSwitch", path: "Sources"), 15 | .testTarget(name: "AIFlatSwitch-Tests", dependencies: ["AIFlatSwitch"], path: "Tests") 16 | ], 17 | swiftLanguageVersions: [.v5] 18 | ) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AIFlatSwitch 2 | 3 | A smooth, nice looking and IBDesignable flat design switch for iOS. Can be used instead of UISwitch. 4 | 5 | Inspired by Creativedash's Dribbble post [here](http://dribbble.com/shots/1631598-On-Off) 6 | 7 |

8 | 9 | Flat switch animation 10 | 11 |

12 | 13 |

14 | 15 | 16 | cocoapods 17 | 18 |

19 |

20 | 21 |

22 | 23 | ## Requirements 24 | - iOS 8.0+, tvOS 12.0+ 25 | - Xcode 11.0+ 26 | - Swift 5 27 | 28 | ## Usage 29 | 30 | ### Creating the flat switch 31 | 32 | - Either programmatically 33 | 34 | ```swift 35 | var flatSwitch = AIFlatSwitch(frame: CGRectMake(0, 0, 50, 50)) 36 | ``` 37 | 38 | - Or in Interface Builder 39 | 40 | ### Methods 41 | 42 | > To change its selected state: 43 | 44 | ```swift 45 | flatSwitch.isSelected = true 46 | ``` 47 | - [x] IBInspectable 48 | 49 | > or: 50 | 51 | ```swift 52 | flatSwitch.setSelected(true, animated: true) 53 | ``` 54 | 55 | > To listen to its state changes: 56 | 57 | ```swift 58 | @IBAction func handleSwitchValueChange(sender: AnyObject) { 59 | if let flatSwitch = sender as? AIFlatSwitch { 60 | print(flatSwitch.isSelected) 61 | } 62 | } 63 | ``` 64 | 65 | > Animation observer callbacks: 66 | 67 | ```swift 68 | flatSwitch.selectionAnimationDidStart = { isSelected in 69 | print("New state: \(isSelected)") 70 | } 71 | 72 | flatSwitch.selectionAnimationDidStop = { isSelected in 73 | print("State when animation stopped: \(isSelected)") 74 | } 75 | ``` 76 | 77 | > Styling the switch: 78 | 79 | ```swift 80 | flatSwitch.lineWidth = 2.0 81 | flatSwitch.strokeColor = UIColor.blue 82 | flatSwitch.trailStrokeColor = UIColor.red 83 | flatSwitch.backgroundLayerColor = UIColor.red 84 | flatSwitch.animatesOnTouch = false 85 | ``` 86 | - [x] IBInspectable 87 | 88 | ## Contribution guidelines 89 | 90 | - Make your changes in your branch 91 | - Bump the pod version in AIFlatSwitch.podspec file (e.g. 1.0.1 to 1.0.2) 92 | - Make sure the Example project compiles and works fine in the Simulator 93 | - Find references to your source code changes in README.md and update them (e.g. method names, changed features) 94 | - Create a pull request 95 | 96 | ## License 97 | 98 | AIFlatSwitch is released under the MIT license. See LICENSE for details. 99 | 100 | animated check button, checkmark 101 | -------------------------------------------------------------------------------- /Sources/AIFlatSwitch.h: -------------------------------------------------------------------------------- 1 | // 2 | // AIFlatSwitch.h 3 | // AIFlatSwitch 4 | // 5 | // Created by cocoatoucher on 07/03/15. 6 | // Copyright (c) 2015 cocoatoucher. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for AIFlatSwitch. 12 | FOUNDATION_EXPORT double AIFlatSwitchVersionNumber; 13 | 14 | //! Project version string for AIFlatSwitch. 15 | FOUNDATION_EXPORT const unsigned char AIFlatSwitchVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/AIFlatSwitch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AIFlatSwitch.swift 3 | // AIFlatSwitch 4 | // 5 | // Created by cocoatoucher on 11/02/15. 6 | // Copyright (c) 2015 cocoatoucher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | A flat design switch alternative to UISwitch 13 | */ 14 | @IBDesignable open class AIFlatSwitch: UIControl { 15 | 16 | // MARK: - Public 17 | 18 | /** 19 | Line width for the circle, trail and checkmark parts of the switch. 20 | */ 21 | @IBInspectable open var lineWidth: CGFloat = 2.0 { 22 | didSet { 23 | self.circle.lineWidth = lineWidth 24 | self.checkmark.lineWidth = lineWidth 25 | self.trailCircle.lineWidth = lineWidth 26 | } 27 | } 28 | 29 | /** 30 | Set to false if the selection should not be animated with touch up inside events. 31 | */ 32 | @IBInspectable open var animatesOnTouch: Bool = true 33 | 34 | /** 35 | Stroke color for circle and checkmark. 36 | Circle disappears and trail becomes visible when the switch is selected. 37 | */ 38 | @IBInspectable open var strokeColor: UIColor = UIColor.black { 39 | didSet { 40 | self.circle.strokeColor = strokeColor.cgColor 41 | self.checkmark.strokeColor = strokeColor.cgColor 42 | } 43 | } 44 | 45 | /** 46 | Stroke color for trail. 47 | Trail disappears and circle becomes visible when the switch is deselected. 48 | */ 49 | @IBInspectable open var trailStrokeColor: UIColor = UIColor.gray { 50 | didSet { 51 | self.trailCircle.strokeColor = trailStrokeColor.cgColor 52 | } 53 | } 54 | 55 | /** 56 | Color for the inner circle. 57 | */ 58 | @IBInspectable open var backgroundLayerColor: UIColor = UIColor.clear { 59 | didSet { 60 | self.backgroundLayer.fillColor = backgroundLayerColor.cgColor 61 | } 62 | } 63 | 64 | /** 65 | Overrides isSelected from UIControl using internal state flag. 66 | Default value is false. 67 | */ 68 | @IBInspectable open override var isSelected: Bool { 69 | get { 70 | return isSelectedInternal 71 | } 72 | set { 73 | super.isSelected = newValue 74 | self.setSelected(newValue, animated: false) 75 | } 76 | } 77 | 78 | /** 79 | Called when selection animation started 80 | Either when selecting or deselecting 81 | */ 82 | public var selectionAnimationDidStart: ((_ isSelected: Bool) -> Void)? 83 | /** 84 | Called when selection animation stopped 85 | Either when selecting or deselecting 86 | */ 87 | public var selectionAnimationDidStop: ((_ isSelected: Bool) -> Void)? 88 | 89 | public override init(frame: CGRect) { 90 | super.init(frame: frame) 91 | // Configure switch when created with frame 92 | self.configure() 93 | } 94 | 95 | public required init?(coder aDecoder: NSCoder) { 96 | super.init(coder: aDecoder) 97 | // Configure switch when created from xib 98 | self.configure() 99 | } 100 | 101 | /** 102 | Switches between selected and deselected state. Use this method to programmatically change the value of selected state. 103 | - Parameter isSelected: Whether the switch should be selected or not 104 | - Parameter animated: Whether the transition should be animated or not 105 | */ 106 | open func setSelected(_ isSelected: Bool, animated: Bool) { 107 | self.isSelectedInternal = isSelected 108 | 109 | // Remove all animations before switching to new state 110 | checkmark.removeAllAnimations() 111 | circle.removeAllAnimations() 112 | trailCircle.removeAllAnimations() 113 | 114 | // Reset sublayer values 115 | self.resetLayerValues(self.isSelectedInternal, stateWillBeAnimated: animated) 116 | 117 | // Animate to new state 118 | if animated { 119 | self.addAnimations(desiredSelectedState: isSelectedInternal) 120 | self.accessibilityValue = isSelected ? "checked" : "unchecked" 121 | } 122 | } 123 | 124 | open override func layoutSublayers(of layer: CALayer) { 125 | super.layoutSublayers(of: layer) 126 | 127 | guard layer == self.layer else { 128 | return 129 | } 130 | 131 | var offset: CGPoint = CGPoint.zero 132 | let radius = fmin(self.bounds.width, self.bounds.height) / 2 - (lineWidth / 2) 133 | offset.x = (self.bounds.width - radius * 2) / 2.0 134 | offset.y = (self.bounds.height - radius * 2) / 2.0 135 | 136 | CATransaction.begin() 137 | CATransaction.setDisableActions(true) 138 | 139 | // Calculate frame for circle and trail circle 140 | let circleAndTrailFrame = CGRect(x: offset.x, y: offset.y, width: radius * 2, height: radius * 2) 141 | 142 | let circlePath = UIBezierPath(ovalIn: circleAndTrailFrame) 143 | trailCircle.path = circlePath.cgPath 144 | 145 | circle.transform = CATransform3DIdentity 146 | circle.frame = self.bounds 147 | circle.path = UIBezierPath(ovalIn: circleAndTrailFrame).cgPath 148 | // Rotating circle by 212 degrees to be able to manipulate stroke end location. 149 | circle.transform = CATransform3DMakeRotation(CGFloat(212 * Double.pi / 180), 0, 0, 1) 150 | 151 | let origin = CGPoint(x: offset.x + radius, y: offset.y + radius) 152 | 153 | // Calculate checkmark path 154 | let checkmarkPath = UIBezierPath() 155 | 156 | var checkmarkStartPoint = CGPoint.zero 157 | // Checkmark will start from circle's stroke end calculated above. 158 | checkmarkStartPoint.x = origin.x + radius * CGFloat(cos(212 * Double.pi / 180)) 159 | checkmarkStartPoint.y = origin.y + radius * CGFloat(sin(212 * Double.pi / 180)) 160 | checkmarkPath.move(to: checkmarkStartPoint) 161 | 162 | self.checkmarkSplitPoint = CGPoint(x: offset.x + radius * 0.9, y: offset.y + radius * 1.4) 163 | checkmarkPath.addLine(to: self.checkmarkSplitPoint) 164 | 165 | var checkmarkEndPoint = CGPoint.zero 166 | // Checkmark will end 320 degrees location of the circle layer. 167 | checkmarkEndPoint.x = origin.x + radius * CGFloat(cos(320 * Double.pi / 180)) 168 | checkmarkEndPoint.y = origin.y + radius * CGFloat(sin(320 * Double.pi / 180)) 169 | checkmarkPath.addLine(to: checkmarkEndPoint) 170 | 171 | checkmark.frame = self.bounds 172 | checkmark.path = checkmarkPath.cgPath 173 | 174 | let innerCircleRadius = fmin(self.bounds.width, self.bounds.height) / 2.1 - (lineWidth / 2.1) 175 | 176 | offset.x = (self.bounds.width - innerCircleRadius * 2) / 2.0 177 | offset.y = (self.bounds.height - innerCircleRadius * 2) / 2.0 178 | 179 | backgroundLayer.path = UIBezierPath(ovalIn: CGRect(x: offset.x, y: offset.y, width: innerCircleRadius * 2, height: innerCircleRadius * 2)).cgPath 180 | 181 | CATransaction.commit() 182 | } 183 | 184 | // MARK: - Private 185 | 186 | /** 187 | Animation duration for the whole selection transition 188 | */ 189 | private let animationDuration: CFTimeInterval = 0.3 190 | /** 191 | Percentage where the checkmark tail ends 192 | */ 193 | private let finalStrokeEndForCheckmark: CGFloat = 0.85 194 | /** 195 | Percentage where the checkmark head begins 196 | */ 197 | private let finalStrokeStartForCheckmark: CGFloat = 0.3 198 | /** 199 | Percentage of the bounce amount of checkmark near animation completion 200 | */ 201 | private let checkmarkBounceAmount: CGFloat = 0.1 202 | /** 203 | Internal flag to keep track of selected state. 204 | */ 205 | private var isSelectedInternal: Bool = false 206 | /** 207 | Trail layer. Trail is the circle which appears when the switch is in deselected state. 208 | */ 209 | private var trailCircle: CAShapeLayer = CAShapeLayer() 210 | /** 211 | Circle layer. Circle appears when the switch is in selected state. 212 | */ 213 | private var circle: CAShapeLayer = CAShapeLayer() 214 | /** 215 | Checkmark layer. Checkmark appears when the switch is in selected state. 216 | */ 217 | private var checkmark: CAShapeLayer = CAShapeLayer() 218 | /** 219 | circleLayer, is the layer which appears inside the circle. 220 | */ 221 | private var backgroundLayer: CAShapeLayer = CAShapeLayer() 222 | /** 223 | Middle point of the checkmark layer. Calculated each time the sublayers are layout. 224 | */ 225 | private var checkmarkSplitPoint: CGPoint = CGPoint.zero 226 | 227 | 228 | /** 229 | Configures circle, trail and checkmark layers after initialization. 230 | Setups switch with the default selection state. 231 | Configures target for tocuh up inside event for triggering selection. 232 | */ 233 | private func configure() { 234 | 235 | func configureShapeLayer(_ shapeLayer: CAShapeLayer) { 236 | shapeLayer.lineJoin = CAShapeLayerLineJoin.round 237 | shapeLayer.lineCap = CAShapeLayerLineCap.round 238 | shapeLayer.lineWidth = self.lineWidth 239 | shapeLayer.fillColor = UIColor.clear.cgColor 240 | self.layer.addSublayer(shapeLayer) 241 | } 242 | 243 | // Setup layers 244 | self.layer.addSublayer(backgroundLayer) 245 | backgroundLayer.fillColor = backgroundLayerColor.cgColor 246 | 247 | configureShapeLayer(trailCircle) 248 | trailCircle.strokeColor = trailStrokeColor.cgColor 249 | 250 | configureShapeLayer(circle) 251 | circle.strokeColor = strokeColor.cgColor 252 | 253 | configureShapeLayer(checkmark) 254 | checkmark.strokeColor = strokeColor.cgColor 255 | 256 | // Setup initial state 257 | self.setSelected(false, animated: false) 258 | 259 | // Add target for handling touch up inside event as a default manner 260 | self.addTarget(self, action: #selector(AIFlatSwitch.handleTouchUpInside), for: UIControl.Event.touchUpInside) 261 | } 262 | 263 | /** 264 | Switches between selected and deselected state with touch up inside events. Set animatesOnTouch to false to disable animation on touch. 265 | Send valueChanged event as a result. 266 | */ 267 | @objc private func handleTouchUpInside() { 268 | self.setSelected(!self.isSelected, animated: self.animatesOnTouch) 269 | self.sendActions(for: UIControl.Event.valueChanged) 270 | } 271 | 272 | /** 273 | Switches layer values to selected or deselected state without any animation. 274 | If the there is going to be an animation(stateWillBeAnimated parameter is true), then the layer values are reset to reverse of the desired state value to provide the transition for animation. 275 | - Parameter desiredSelectedState: Desired selection state for the reset to handle 276 | - Parameter stateWillBeAnimated: If the reset should prepare the layers for animation 277 | */ 278 | private func resetLayerValues(_ desiredSelectedState: Bool, stateWillBeAnimated: Bool) { 279 | CATransaction.begin() 280 | CATransaction.setDisableActions(true) 281 | 282 | if (desiredSelectedState && stateWillBeAnimated) || (desiredSelectedState == false && stateWillBeAnimated == false) { 283 | // Switch to deselected state 284 | checkmark.strokeEnd = 0.0 285 | checkmark.strokeStart = 0.0 286 | trailCircle.opacity = 0.0 287 | circle.strokeStart = 0.0 288 | circle.strokeEnd = 1.0 289 | } else { 290 | // Switch to selected state 291 | checkmark.strokeEnd = finalStrokeEndForCheckmark 292 | checkmark.strokeStart = finalStrokeStartForCheckmark 293 | trailCircle.opacity = 1.0 294 | circle.strokeStart = 0.0 295 | circle.strokeEnd = 0.0 296 | } 297 | 298 | CATransaction.commit() 299 | } 300 | 301 | /** 302 | Animates the selected state transition. 303 | - Parameter desiredSelectedState: Desired selection state for the animation to handle 304 | */ 305 | private func addAnimations(desiredSelectedState selected: Bool) { 306 | let circleAnimationDuration = animationDuration * 0.5 307 | 308 | let checkmarkEndDuration = animationDuration * 0.8 309 | let checkmarkStartDuration = checkmarkEndDuration - circleAnimationDuration 310 | let checkmarkBounceDuration = animationDuration - checkmarkEndDuration 311 | 312 | let checkmarkAnimationGroup = CAAnimationGroup() 313 | checkmarkAnimationGroup.isRemovedOnCompletion = false 314 | checkmarkAnimationGroup.fillMode = CAMediaTimingFillMode.forwards 315 | checkmarkAnimationGroup.duration = animationDuration 316 | checkmarkAnimationGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) 317 | 318 | let checkmarkStrokeEndAnimation = CAKeyframeAnimation(keyPath: "strokeEnd") 319 | checkmarkStrokeEndAnimation.duration = checkmarkEndDuration + checkmarkBounceDuration 320 | checkmarkStrokeEndAnimation.isRemovedOnCompletion = false 321 | checkmarkStrokeEndAnimation.fillMode = CAMediaTimingFillMode.forwards 322 | checkmarkStrokeEndAnimation.calculationMode = CAAnimationCalculationMode.paced 323 | 324 | if selected { 325 | checkmarkStrokeEndAnimation.values = [NSNumber(value: 0.0 as Float), NSNumber(value: Float(finalStrokeEndForCheckmark + checkmarkBounceAmount) as Float), NSNumber(value: Float(finalStrokeEndForCheckmark) as Float)] 326 | checkmarkStrokeEndAnimation.keyTimes = [NSNumber(value: 0.0 as Double), NSNumber(value: checkmarkEndDuration as Double), NSNumber(value: checkmarkEndDuration + checkmarkBounceDuration as Double)] 327 | } else { 328 | checkmarkStrokeEndAnimation.values = [NSNumber(value: Float(finalStrokeEndForCheckmark) as Float), NSNumber(value: Float(finalStrokeEndForCheckmark + checkmarkBounceAmount) as Float), NSNumber(value: -0.1 as Float)] 329 | checkmarkStrokeEndAnimation.keyTimes = [NSNumber(value: 0.0 as Double), NSNumber(value: checkmarkBounceDuration as Double), NSNumber(value: checkmarkEndDuration + checkmarkBounceDuration as Double)] 330 | } 331 | 332 | let checkmarkStrokeStartAnimation = CAKeyframeAnimation(keyPath: "strokeStart") 333 | checkmarkStrokeStartAnimation.duration = checkmarkStartDuration + checkmarkBounceDuration 334 | checkmarkStrokeStartAnimation.isRemovedOnCompletion = false 335 | checkmarkStrokeStartAnimation.fillMode = CAMediaTimingFillMode.forwards 336 | checkmarkStrokeStartAnimation.calculationMode = CAAnimationCalculationMode.paced 337 | 338 | if selected { 339 | checkmarkStrokeStartAnimation.values = [NSNumber(value: 0.0 as Float), NSNumber(value: Float(finalStrokeStartForCheckmark + checkmarkBounceAmount) as Float), NSNumber(value: Float(finalStrokeStartForCheckmark) as Float)] 340 | checkmarkStrokeStartAnimation.keyTimes = [NSNumber(value: 0.0 as Double), NSNumber(value: checkmarkStartDuration as Double), NSNumber(value: checkmarkStartDuration + checkmarkBounceDuration as Double)] 341 | } else { 342 | checkmarkStrokeStartAnimation.values = [NSNumber(value: Float(finalStrokeStartForCheckmark) as Float), NSNumber(value: Float(finalStrokeStartForCheckmark + checkmarkBounceAmount) as Float), NSNumber(value: 0.0 as Float)] 343 | checkmarkStrokeStartAnimation.keyTimes = [NSNumber(value: 0.0 as Double), NSNumber(value: checkmarkBounceDuration as Double), NSNumber(value: checkmarkStartDuration + checkmarkBounceDuration as Double)] 344 | } 345 | 346 | if selected { 347 | checkmarkStrokeStartAnimation.beginTime = circleAnimationDuration 348 | } 349 | 350 | checkmarkAnimationGroup.animations = [checkmarkStrokeEndAnimation, checkmarkStrokeStartAnimation] 351 | checkmark.add(checkmarkAnimationGroup, forKey: "checkmarkAnimation") 352 | 353 | let circleAnimationGroup = CAAnimationGroup() 354 | circleAnimationGroup.duration = animationDuration 355 | circleAnimationGroup.isRemovedOnCompletion = false 356 | circleAnimationGroup.fillMode = CAMediaTimingFillMode.forwards 357 | circleAnimationGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) 358 | 359 | let circleStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") 360 | circleStrokeEnd.duration = circleAnimationDuration 361 | if selected { 362 | circleStrokeEnd.beginTime = 0.0 363 | 364 | circleStrokeEnd.fromValue = NSNumber(value: 1.0 as Float) 365 | circleStrokeEnd.toValue = NSNumber(value: -0.1 as Float) 366 | } else { 367 | circleStrokeEnd.beginTime = animationDuration - circleAnimationDuration 368 | 369 | circleStrokeEnd.fromValue = NSNumber(value: 0.0 as Float) 370 | circleStrokeEnd.toValue = NSNumber(value: 1.0 as Float) 371 | } 372 | circleStrokeEnd.isRemovedOnCompletion = false 373 | circleStrokeEnd.fillMode = CAMediaTimingFillMode.forwards 374 | 375 | circleAnimationGroup.animations = [circleStrokeEnd] 376 | circleAnimationGroup.delegate = self 377 | circle.add(circleAnimationGroup, forKey: "circleStrokeEnd") 378 | 379 | let trailCircleColor = CABasicAnimation(keyPath: "opacity") 380 | trailCircleColor.duration = animationDuration 381 | if selected { 382 | trailCircleColor.fromValue = NSNumber(value: 0.0 as Float) 383 | trailCircleColor.toValue = NSNumber(value: 1.0 as Float) 384 | } else { 385 | trailCircleColor.fromValue = NSNumber(value: 1.0 as Float) 386 | trailCircleColor.toValue = NSNumber(value: 0.0 as Float) 387 | } 388 | trailCircleColor.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) 389 | 390 | trailCircleColor.fillMode = CAMediaTimingFillMode.forwards 391 | trailCircleColor.isRemovedOnCompletion = false 392 | trailCircle.add(trailCircleColor, forKey: "trailCircleColor") 393 | } 394 | 395 | } 396 | 397 | extension AIFlatSwitch: CAAnimationDelegate { 398 | 399 | public func animationDidStart(_ anim: CAAnimation) { 400 | selectionAnimationDidStart?(isSelectedInternal) 401 | } 402 | 403 | public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 404 | selectionAnimationDidStop?(isSelectedInternal) 405 | } 406 | 407 | } 408 | -------------------------------------------------------------------------------- /Tests/AIFlatSwitchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AIFlatSwitchTests.swift 3 | // AIFlatSwitchTests 4 | // 5 | // Created by cocoatoucher on 07/03/15. 6 | // Copyright (c) 2015 cocoatoucher. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import AIFlatSwitch 11 | 12 | class AIFlatSwitchTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /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 | CFBundleVersion 20 | 1 21 | 22 | 23 | --------------------------------------------------------------------------------