├── .gitignore ├── Assets ├── snapchat.gif └── snapchat_checkbox.gif ├── LICENSE ├── README.md └── SnapchatCheckbox ├── SnapchatCheckbox.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── SnapchatCheckbox ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── SnapchatCheckbox.swift └── ViewController.swift /.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 | *.xcuserstate 23 | *.DS_Store 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 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | -------------------------------------------------------------------------------- /Assets/snapchat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielinoa/SnapchatCheckbox/7430e40bf46769a2d0fbbe125fc68a25a7ffac22/Assets/snapchat.gif -------------------------------------------------------------------------------- /Assets/snapchat_checkbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielinoa/SnapchatCheckbox/7430e40bf46769a2d0fbbe125fc68a25a7ffac22/Assets/snapchat_checkbox.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Daniel Inoa 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SnapchatCheckbox 2 | 3 | In Snapchat, after receiving and opening a snap, there is the option to replay a snap by holding down on the specified snap, which results in a small box being filled in a circular manner. 4 | 5 | 6 | 7 | This checkbox is inspired by that control/view. 8 | 9 | 10 | 11 | ## Compatibility 12 | 13 | * This project is written in Swift 3.0 and requires Xcode 8.0+ to build and run. 14 | * SnapchatCheckbox runs on iOS 10+. 15 | 16 | ## Contributors 17 | 18 | * [Daniel Inoa](https://twitter.com/danielinoa_) 19 | 20 | ## License 21 | 22 | SnapchatCheckbox is under the MIT License. 23 | -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 01CF112C1DEA911D0020155D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CF112B1DEA911D0020155D /* AppDelegate.swift */; }; 11 | 01CF112E1DEA911D0020155D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CF112D1DEA911D0020155D /* ViewController.swift */; }; 12 | 01CF11311DEA911D0020155D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01CF112F1DEA911D0020155D /* Main.storyboard */; }; 13 | 01CF11331DEA911D0020155D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01CF11321DEA911D0020155D /* Assets.xcassets */; }; 14 | 01CF11361DEA911D0020155D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01CF11341DEA911D0020155D /* LaunchScreen.storyboard */; }; 15 | 01CF113E1DEA915B0020155D /* SnapchatCheckbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CF113D1DEA915B0020155D /* SnapchatCheckbox.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 01CF11281DEA911D0020155D /* SnapchatCheckbox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SnapchatCheckbox.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 01CF112B1DEA911D0020155D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 01CF112D1DEA911D0020155D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | 01CF11301DEA911D0020155D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 23 | 01CF11321DEA911D0020155D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 01CF11351DEA911D0020155D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 25 | 01CF11371DEA911D0020155D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 01CF113D1DEA915B0020155D /* SnapchatCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapchatCheckbox.swift; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 01CF11251DEA911D0020155D /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 01CF111F1DEA911D0020155D = { 41 | isa = PBXGroup; 42 | children = ( 43 | 01CF112A1DEA911D0020155D /* SnapchatCheckbox */, 44 | 01CF11291DEA911D0020155D /* Products */, 45 | ); 46 | sourceTree = ""; 47 | }; 48 | 01CF11291DEA911D0020155D /* Products */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 01CF11281DEA911D0020155D /* SnapchatCheckbox.app */, 52 | ); 53 | name = Products; 54 | sourceTree = ""; 55 | }; 56 | 01CF112A1DEA911D0020155D /* SnapchatCheckbox */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 01CF112B1DEA911D0020155D /* AppDelegate.swift */, 60 | 01CF113D1DEA915B0020155D /* SnapchatCheckbox.swift */, 61 | 01CF112D1DEA911D0020155D /* ViewController.swift */, 62 | 01CF112F1DEA911D0020155D /* Main.storyboard */, 63 | 01CF11321DEA911D0020155D /* Assets.xcassets */, 64 | 01CF11341DEA911D0020155D /* LaunchScreen.storyboard */, 65 | 01CF11371DEA911D0020155D /* Info.plist */, 66 | ); 67 | path = SnapchatCheckbox; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | 01CF11271DEA911D0020155D /* SnapchatCheckbox */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = 01CF113A1DEA911D0020155D /* Build configuration list for PBXNativeTarget "SnapchatCheckbox" */; 76 | buildPhases = ( 77 | 01CF11241DEA911D0020155D /* Sources */, 78 | 01CF11251DEA911D0020155D /* Frameworks */, 79 | 01CF11261DEA911D0020155D /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = SnapchatCheckbox; 86 | productName = SnapchatCheckbox; 87 | productReference = 01CF11281DEA911D0020155D /* SnapchatCheckbox.app */; 88 | productType = "com.apple.product-type.application"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | 01CF11201DEA911D0020155D /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastSwiftUpdateCheck = 0810; 97 | LastUpgradeCheck = 0810; 98 | ORGANIZATIONNAME = "Daniel Inoa"; 99 | TargetAttributes = { 100 | 01CF11271DEA911D0020155D = { 101 | CreatedOnToolsVersion = 8.1; 102 | DevelopmentTeam = A8SJS26GGQ; 103 | ProvisioningStyle = Automatic; 104 | }; 105 | }; 106 | }; 107 | buildConfigurationList = 01CF11231DEA911D0020155D /* Build configuration list for PBXProject "SnapchatCheckbox" */; 108 | compatibilityVersion = "Xcode 3.2"; 109 | developmentRegion = English; 110 | hasScannedForEncodings = 0; 111 | knownRegions = ( 112 | en, 113 | Base, 114 | ); 115 | mainGroup = 01CF111F1DEA911D0020155D; 116 | productRefGroup = 01CF11291DEA911D0020155D /* Products */; 117 | projectDirPath = ""; 118 | projectRoot = ""; 119 | targets = ( 120 | 01CF11271DEA911D0020155D /* SnapchatCheckbox */, 121 | ); 122 | }; 123 | /* End PBXProject section */ 124 | 125 | /* Begin PBXResourcesBuildPhase section */ 126 | 01CF11261DEA911D0020155D /* Resources */ = { 127 | isa = PBXResourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 01CF11361DEA911D0020155D /* LaunchScreen.storyboard in Resources */, 131 | 01CF11331DEA911D0020155D /* Assets.xcassets in Resources */, 132 | 01CF11311DEA911D0020155D /* Main.storyboard in Resources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | 01CF11241DEA911D0020155D /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 01CF112E1DEA911D0020155D /* ViewController.swift in Sources */, 144 | 01CF112C1DEA911D0020155D /* AppDelegate.swift in Sources */, 145 | 01CF113E1DEA915B0020155D /* SnapchatCheckbox.swift in Sources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXSourcesBuildPhase section */ 150 | 151 | /* Begin PBXVariantGroup section */ 152 | 01CF112F1DEA911D0020155D /* Main.storyboard */ = { 153 | isa = PBXVariantGroup; 154 | children = ( 155 | 01CF11301DEA911D0020155D /* Base */, 156 | ); 157 | name = Main.storyboard; 158 | sourceTree = ""; 159 | }; 160 | 01CF11341DEA911D0020155D /* LaunchScreen.storyboard */ = { 161 | isa = PBXVariantGroup; 162 | children = ( 163 | 01CF11351DEA911D0020155D /* Base */, 164 | ); 165 | name = LaunchScreen.storyboard; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXVariantGroup section */ 169 | 170 | /* Begin XCBuildConfiguration section */ 171 | 01CF11381DEA911D0020155D /* Debug */ = { 172 | isa = XCBuildConfiguration; 173 | buildSettings = { 174 | ALWAYS_SEARCH_USER_PATHS = NO; 175 | CLANG_ANALYZER_NONNULL = YES; 176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 177 | CLANG_CXX_LIBRARY = "libc++"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_CONSTANT_CONVERSION = YES; 182 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 183 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 184 | CLANG_WARN_EMPTY_BODY = YES; 185 | CLANG_WARN_ENUM_CONVERSION = YES; 186 | CLANG_WARN_INFINITE_RECURSION = YES; 187 | CLANG_WARN_INT_CONVERSION = YES; 188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 189 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 193 | COPY_PHASE_STRIP = NO; 194 | DEBUG_INFORMATION_FORMAT = dwarf; 195 | ENABLE_STRICT_OBJC_MSGSEND = YES; 196 | ENABLE_TESTABILITY = YES; 197 | GCC_C_LANGUAGE_STANDARD = gnu99; 198 | GCC_DYNAMIC_NO_PIC = NO; 199 | GCC_NO_COMMON_BLOCKS = YES; 200 | GCC_OPTIMIZATION_LEVEL = 0; 201 | GCC_PREPROCESSOR_DEFINITIONS = ( 202 | "DEBUG=1", 203 | "$(inherited)", 204 | ); 205 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 206 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 207 | GCC_WARN_UNDECLARED_SELECTOR = YES; 208 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 209 | GCC_WARN_UNUSED_FUNCTION = YES; 210 | GCC_WARN_UNUSED_VARIABLE = YES; 211 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 212 | MTL_ENABLE_DEBUG_INFO = YES; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = iphoneos; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | }; 218 | name = Debug; 219 | }; 220 | 01CF11391DEA911D0020155D /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 226 | CLANG_CXX_LIBRARY = "libc++"; 227 | CLANG_ENABLE_MODULES = YES; 228 | CLANG_ENABLE_OBJC_ARC = YES; 229 | CLANG_WARN_BOOL_CONVERSION = YES; 230 | CLANG_WARN_CONSTANT_CONVERSION = YES; 231 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 232 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INFINITE_RECURSION = YES; 236 | CLANG_WARN_INT_CONVERSION = YES; 237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 238 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 239 | CLANG_WARN_UNREACHABLE_CODE = YES; 240 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 241 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 242 | COPY_PHASE_STRIP = NO; 243 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 244 | ENABLE_NS_ASSERTIONS = NO; 245 | ENABLE_STRICT_OBJC_MSGSEND = YES; 246 | GCC_C_LANGUAGE_STANDARD = gnu99; 247 | GCC_NO_COMMON_BLOCKS = YES; 248 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 249 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 250 | GCC_WARN_UNDECLARED_SELECTOR = YES; 251 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 252 | GCC_WARN_UNUSED_FUNCTION = YES; 253 | GCC_WARN_UNUSED_VARIABLE = YES; 254 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 255 | MTL_ENABLE_DEBUG_INFO = NO; 256 | SDKROOT = iphoneos; 257 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 258 | VALIDATE_PRODUCT = YES; 259 | }; 260 | name = Release; 261 | }; 262 | 01CF113B1DEA911D0020155D /* Debug */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 266 | DEVELOPMENT_TEAM = A8SJS26GGQ; 267 | INFOPLIST_FILE = SnapchatCheckbox/Info.plist; 268 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 269 | PRODUCT_BUNDLE_IDENTIFIER = com.danielinoa.SnapchatCheckbox; 270 | PRODUCT_NAME = "$(TARGET_NAME)"; 271 | SWIFT_VERSION = 3.0; 272 | }; 273 | name = Debug; 274 | }; 275 | 01CF113C1DEA911D0020155D /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 279 | DEVELOPMENT_TEAM = A8SJS26GGQ; 280 | INFOPLIST_FILE = SnapchatCheckbox/Info.plist; 281 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 282 | PRODUCT_BUNDLE_IDENTIFIER = com.danielinoa.SnapchatCheckbox; 283 | PRODUCT_NAME = "$(TARGET_NAME)"; 284 | SWIFT_VERSION = 3.0; 285 | }; 286 | name = Release; 287 | }; 288 | /* End XCBuildConfiguration section */ 289 | 290 | /* Begin XCConfigurationList section */ 291 | 01CF11231DEA911D0020155D /* Build configuration list for PBXProject "SnapchatCheckbox" */ = { 292 | isa = XCConfigurationList; 293 | buildConfigurations = ( 294 | 01CF11381DEA911D0020155D /* Debug */, 295 | 01CF11391DEA911D0020155D /* Release */, 296 | ); 297 | defaultConfigurationIsVisible = 0; 298 | defaultConfigurationName = Release; 299 | }; 300 | 01CF113A1DEA911D0020155D /* Build configuration list for PBXNativeTarget "SnapchatCheckbox" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | 01CF113B1DEA911D0020155D /* Debug */, 304 | 01CF113C1DEA911D0020155D /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | }; 308 | /* End XCConfigurationList section */ 309 | }; 310 | rootObject = 01CF11201DEA911D0020155D /* Project object */; 311 | } 312 | -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SnapchatCheckbox 4 | // 5 | // Created by Daniel Inoa on 11/26/16. 6 | // Copyright © 2016 Daniel Inoa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox/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 | } -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox/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 | -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox/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 | 62 | 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 | -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox/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 | -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox/SnapchatCheckbox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Daniel Inoa on 11/19/16. 3 | // Copyright © 2016 Daniel Inoa. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | @IBDesignable final class SnapchatCheckbox: UIControl { 9 | 10 | private(set) var isChecked: Bool = false 11 | 12 | func setChecked(_ checked: Bool, animated: Bool = false) { 13 | let fromValue: Float = isChecked ? 1 : 0 14 | let toValue: Float = isChecked ? 0 : 1 15 | if animated { 16 | let animation = CAKeyframeAnimation(keyPath: "path") 17 | let byValue: Float = 0.01 * ( !isChecked ? 1 : -1) 18 | let pathValues = stride(from: fromValue, to: toValue, by: byValue).map({ piePath(withFraction: $0).cgPath }) 19 | animation.values = pathValues 20 | animation.duration = 0.5 21 | shapeLayer.add(animation, forKey: "pathAnimation") 22 | } 23 | shapeLayer.path = piePath(withFraction: toValue).cgPath 24 | isChecked = checked 25 | } 26 | 27 | // MARK: - Lifecycle 28 | 29 | override init(frame: CGRect) { 30 | super.init(frame: frame) 31 | configure() 32 | } 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | super.init(coder: aDecoder) 36 | configure() 37 | } 38 | 39 | private func configure() { 40 | clipsToBounds = true 41 | layer.cornerRadius = 5 42 | layer.borderColor = tintColor.cgColor 43 | layer.borderWidth = 4 44 | layer.addSublayer(shapeLayer) 45 | } 46 | 47 | // MARK: - Appearance 48 | 49 | override var isHighlighted: Bool { 50 | didSet { 51 | backgroundColor = isHighlighted ? tintColor.withAlphaComponent(0.2) : .clear 52 | } 53 | } 54 | 55 | override var clipsToBounds: Bool { 56 | didSet { 57 | // workaround 58 | if !clipsToBounds { 59 | clipsToBounds = true 60 | } 61 | } 62 | } 63 | 64 | private lazy var shapeLayer: CAShapeLayer = { 65 | let layer = CAShapeLayer() 66 | layer.fillColor = self.tintColor.cgColor 67 | let fraction: Float = self.isChecked ? 1 : 0 68 | layer.path = self.piePath(withFraction: fraction).cgPath 69 | return layer 70 | }() 71 | 72 | private func piePath(withFraction fraction: Float) -> UIBezierPath { 73 | let fraction = min(max(0, fraction), 1) 74 | let center = CGPoint(x: bounds.width/2, y: bounds.height/2) 75 | let radius = max(bounds.width, bounds.height) 76 | let path = UIBezierPath() 77 | path.move(to: center) 78 | path.addArc(withCenter: center, 79 | radius: radius, 80 | startAngle: -CGFloat(M_PI_2), 81 | endAngle: -CGFloat(M_PI * 2) * CGFloat(fraction) - CGFloat(M_PI_2), 82 | clockwise: false) 83 | path.addLine(to: center) 84 | return path 85 | } 86 | 87 | // MARK: - Auto Layout 88 | 89 | override var intrinsicContentSize: CGSize { 90 | return CGSize(width: 40, height: 40) 91 | } 92 | 93 | // MARK: - Tracking 94 | 95 | override func endTracking(_ touch: UITouch?, with event: UIEvent?) { 96 | super.endTracking(touch, with: event) 97 | if let touchLocation = touch?.location(in: self), bounds.contains(touchLocation) { 98 | setChecked(!isChecked, animated: true) 99 | sendActions(for: .valueChanged) 100 | } 101 | } 102 | 103 | // MARK: - Interface Builder 104 | 105 | override func prepareForInterfaceBuilder() { 106 | super.prepareForInterfaceBuilder() 107 | setNeedsDisplay() 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /SnapchatCheckbox/SnapchatCheckbox/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SnapchatCheckbox 4 | // 5 | // Created by Daniel Inoa on 11/26/16. 6 | // Copyright © 2016 Daniel Inoa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var checkbox: SnapchatCheckbox! 14 | @IBOutlet weak var isCheckedLabel: UILabel! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | isCheckedLabel.text = checkbox.isChecked ? "checked" : "unchecked" 19 | } 20 | 21 | @IBAction func checkboxTapped(_ sender: Any) { 22 | isCheckedLabel.text = checkbox.isChecked ? "checked" : "unchecked" 23 | } 24 | 25 | } 26 | --------------------------------------------------------------------------------