├── .gitattributes ├── .gitignore ├── CONTRIBUTING.md ├── Example ├── Podfile ├── QUIckControlExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── K-o-D-e-N.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── K-o-D-e-N.xcuserdatad │ │ └── xcschemes │ │ ├── QUIckControlExample.xcscheme │ │ └── xcschememanagement.plist ├── QUIckControlExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── QUIckControlExampleTests │ ├── Info.plist │ └── QUIckControlExampleTests.swift └── QUIckControlTests │ ├── Info.plist │ └── QUIckControlTests.swift ├── LICENSE ├── PinCodeControl.podspec ├── PinCodeControl └── PinCodeControl.swift ├── QUIckControl.podspec ├── QUIckControl ├── QUICStateDescriptor.swift ├── QUIckControl.swift ├── QUIckControlActionTargetImp.swift ├── QUIckControlPrivate.swift ├── QUIckControlValue.swift └── QUIckControlValueTarget.swift ├── QUIckControlObjC ├── QUIckControl.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── K-o-D-e-N.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── K-o-D-e-N.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── QUIckControl.xcscheme │ │ └── xcschememanagement.plist ├── QUIckControl │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── PincodeControl │ │ ├── PinCodeControl.h │ │ └── PinCodeControl.m │ ├── QUIckControl │ │ ├── QUICStateDescriptorKey.h │ │ ├── QUICStateDescriptorKey.m │ │ ├── QUIckControl.h │ │ ├── QUIckControl.m │ │ ├── QUIckControlActionTarget.h │ │ ├── QUIckControlActionTargetImp.h │ │ ├── QUIckControlActionTargetImp.m │ │ ├── QUIckControlPrivate.h │ │ ├── QUIckControlValue.h │ │ ├── QUIckControlValue.m │ │ ├── QUIckControlValueTarget.h │ │ └── QUIckControlValueTarget.m │ ├── ViewController.h │ ├── ViewController.m │ └── main.m └── QUIckControlTests │ ├── Info.plist │ └── KDNControlTests.m ├── README.md └── Resources └── demo.gif /.gitattributes: -------------------------------------------------------------------------------- 1 | QUIckControlObjC/**/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | QUIckControl.mp4 2 | Example/Pods 3 | Podfile.lock 4 | Example/QUIckControlExample.xcworkspace 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | I am having a little time for developing this project and would be cool, if we will be to do it together. 4 | 5 | Recomendations for your changes: 6 | * Write tests. 7 | * Follow style guide. 8 | * Write a good commit message 9 | 10 | #### I wait your pull requests. 11 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'QUIckControlExample' do 4 | # pod 'PinCodeControl' # loaded all depended pods (QUIckControl, Statable) 5 | pod 'PinCodeControl', :path => '../' 6 | pod 'QUIckControl', :path => '../' 7 | # pod 8 | end 9 | 10 | post_install do |installer| 11 | installer.pods_project.targets.each do |target| 12 | target.build_configurations.each do |config| 13 | config.build_settings['SWIFT_VERSION'] = '5.1' 14 | config.build_settings['CONFIGURATION_BUILD_DIR'] = '$PODS_CONFIGURATION_BUILD_DIR' 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /Example/QUIckControlExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2E6A8B57998FDFF4743470E9 /* Pods_QUIckControlExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 162930C17046EA4700861203 /* Pods_QUIckControlExample.framework */; }; 11 | A48D45661E07340A003B4A7B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A48D45651E07340A003B4A7B /* AppDelegate.swift */; }; 12 | A48D45681E07340A003B4A7B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A48D45671E07340A003B4A7B /* ViewController.swift */; }; 13 | A48D456B1E07340A003B4A7B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A48D45691E07340A003B4A7B /* Main.storyboard */; }; 14 | A48D456D1E07340A003B4A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A48D456C1E07340A003B4A7B /* Assets.xcassets */; }; 15 | A48D45701E07340A003B4A7B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A48D456E1E07340A003B4A7B /* LaunchScreen.storyboard */; }; 16 | A48D457B1E07340A003B4A7B /* QUIckControlExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A48D457A1E07340A003B4A7B /* QUIckControlExampleTests.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | A48D45771E07340A003B4A7B /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = A48D455A1E07340A003B4A7B /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = A48D45611E07340A003B4A7B; 25 | remoteInfo = QUIckControlExample; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 162930C17046EA4700861203 /* Pods_QUIckControlExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QUIckControlExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 75DEE73C62D9157B14522F84 /* Pods-QUIckControlExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QUIckControlExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-QUIckControlExample/Pods-QUIckControlExample.debug.xcconfig"; sourceTree = ""; }; 32 | 9E1DEEC8E20B4644E39E1091 /* Pods-QUIckControlExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QUIckControlExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-QUIckControlExample/Pods-QUIckControlExample.release.xcconfig"; sourceTree = ""; }; 33 | A43186D623AA241600438915 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 34 | A48D45621E07340A003B4A7B /* QUIckControlExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QUIckControlExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | A48D45651E07340A003B4A7B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | A48D45671E07340A003B4A7B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 37 | A48D456A1E07340A003B4A7B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | A48D456C1E07340A003B4A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 39 | A48D456F1E07340A003B4A7B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 40 | A48D45711E07340A003B4A7B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | A48D45761E07340A003B4A7B /* QUIckControlExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QUIckControlExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | A48D457A1E07340A003B4A7B /* QUIckControlExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QUIckControlExampleTests.swift; sourceTree = ""; }; 43 | A48D457C1E07340A003B4A7B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | A48D455F1E07340A003B4A7B /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | 2E6A8B57998FDFF4743470E9 /* Pods_QUIckControlExample.framework in Frameworks */, 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | A48D45731E07340A003B4A7B /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | 8679763C6E6A9DFC16A2E875 /* Pods */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 75DEE73C62D9157B14522F84 /* Pods-QUIckControlExample.debug.xcconfig */, 69 | 9E1DEEC8E20B4644E39E1091 /* Pods-QUIckControlExample.release.xcconfig */, 70 | ); 71 | name = Pods; 72 | sourceTree = ""; 73 | }; 74 | A48D45591E07340A003B4A7B = { 75 | isa = PBXGroup; 76 | children = ( 77 | A43186D623AA241600438915 /* README.md */, 78 | A48D45641E07340A003B4A7B /* QUIckControlExample */, 79 | A48D45791E07340A003B4A7B /* QUIckControlExampleTests */, 80 | A48D45631E07340A003B4A7B /* Products */, 81 | 8679763C6E6A9DFC16A2E875 /* Pods */, 82 | DD35D1D2ABB744C6BD0635F2 /* Frameworks */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | A48D45631E07340A003B4A7B /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | A48D45621E07340A003B4A7B /* QUIckControlExample.app */, 90 | A48D45761E07340A003B4A7B /* QUIckControlExampleTests.xctest */, 91 | ); 92 | name = Products; 93 | sourceTree = ""; 94 | }; 95 | A48D45641E07340A003B4A7B /* QUIckControlExample */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | A48D45651E07340A003B4A7B /* AppDelegate.swift */, 99 | A48D45671E07340A003B4A7B /* ViewController.swift */, 100 | A48D45691E07340A003B4A7B /* Main.storyboard */, 101 | A48D456C1E07340A003B4A7B /* Assets.xcassets */, 102 | A48D456E1E07340A003B4A7B /* LaunchScreen.storyboard */, 103 | A48D45711E07340A003B4A7B /* Info.plist */, 104 | ); 105 | path = QUIckControlExample; 106 | sourceTree = ""; 107 | }; 108 | A48D45791E07340A003B4A7B /* QUIckControlExampleTests */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | A48D457A1E07340A003B4A7B /* QUIckControlExampleTests.swift */, 112 | A48D457C1E07340A003B4A7B /* Info.plist */, 113 | ); 114 | path = QUIckControlExampleTests; 115 | sourceTree = ""; 116 | }; 117 | DD35D1D2ABB744C6BD0635F2 /* Frameworks */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 162930C17046EA4700861203 /* Pods_QUIckControlExample.framework */, 121 | ); 122 | name = Frameworks; 123 | sourceTree = ""; 124 | }; 125 | /* End PBXGroup section */ 126 | 127 | /* Begin PBXNativeTarget section */ 128 | A48D45611E07340A003B4A7B /* QUIckControlExample */ = { 129 | isa = PBXNativeTarget; 130 | buildConfigurationList = A48D457F1E07340A003B4A7B /* Build configuration list for PBXNativeTarget "QUIckControlExample" */; 131 | buildPhases = ( 132 | FD2567B6EDEE6935E01DE35D /* [CP] Check Pods Manifest.lock */, 133 | A48D455E1E07340A003B4A7B /* Sources */, 134 | A48D455F1E07340A003B4A7B /* Frameworks */, 135 | A48D45601E07340A003B4A7B /* Resources */, 136 | FC6AC6D3A23B8C8C70C74FC1 /* [CP] Embed Pods Frameworks */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = QUIckControlExample; 143 | productName = QUIckControlExample; 144 | productReference = A48D45621E07340A003B4A7B /* QUIckControlExample.app */; 145 | productType = "com.apple.product-type.application"; 146 | }; 147 | A48D45751E07340A003B4A7B /* QUIckControlExampleTests */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = A48D45821E07340A003B4A7B /* Build configuration list for PBXNativeTarget "QUIckControlExampleTests" */; 150 | buildPhases = ( 151 | A48D45721E07340A003B4A7B /* Sources */, 152 | A48D45731E07340A003B4A7B /* Frameworks */, 153 | A48D45741E07340A003B4A7B /* Resources */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | A48D45781E07340A003B4A7B /* PBXTargetDependency */, 159 | ); 160 | name = QUIckControlExampleTests; 161 | productName = QUIckControlExampleTests; 162 | productReference = A48D45761E07340A003B4A7B /* QUIckControlExampleTests.xctest */; 163 | productType = "com.apple.product-type.bundle.unit-test"; 164 | }; 165 | /* End PBXNativeTarget section */ 166 | 167 | /* Begin PBXProject section */ 168 | A48D455A1E07340A003B4A7B /* Project object */ = { 169 | isa = PBXProject; 170 | attributes = { 171 | LastSwiftUpdateCheck = 0810; 172 | LastUpgradeCheck = 0810; 173 | ORGANIZATIONNAME = "Denis Koryttsev"; 174 | TargetAttributes = { 175 | A48D45611E07340A003B4A7B = { 176 | CreatedOnToolsVersion = 8.1; 177 | DevelopmentTeam = 39RU298KG9; 178 | LastSwiftMigration = 0810; 179 | ProvisioningStyle = Automatic; 180 | }; 181 | A48D45751E07340A003B4A7B = { 182 | CreatedOnToolsVersion = 8.1; 183 | LastSwiftMigration = 0810; 184 | ProvisioningStyle = Automatic; 185 | TestTargetID = A48D45611E07340A003B4A7B; 186 | }; 187 | }; 188 | }; 189 | buildConfigurationList = A48D455D1E07340A003B4A7B /* Build configuration list for PBXProject "QUIckControlExample" */; 190 | compatibilityVersion = "Xcode 3.2"; 191 | developmentRegion = English; 192 | hasScannedForEncodings = 0; 193 | knownRegions = ( 194 | English, 195 | en, 196 | Base, 197 | ); 198 | mainGroup = A48D45591E07340A003B4A7B; 199 | productRefGroup = A48D45631E07340A003B4A7B /* Products */; 200 | projectDirPath = ""; 201 | projectRoot = ""; 202 | targets = ( 203 | A48D45611E07340A003B4A7B /* QUIckControlExample */, 204 | A48D45751E07340A003B4A7B /* QUIckControlExampleTests */, 205 | ); 206 | }; 207 | /* End PBXProject section */ 208 | 209 | /* Begin PBXResourcesBuildPhase section */ 210 | A48D45601E07340A003B4A7B /* Resources */ = { 211 | isa = PBXResourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | A48D45701E07340A003B4A7B /* LaunchScreen.storyboard in Resources */, 215 | A48D456D1E07340A003B4A7B /* Assets.xcassets in Resources */, 216 | A48D456B1E07340A003B4A7B /* Main.storyboard in Resources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | A48D45741E07340A003B4A7B /* Resources */ = { 221 | isa = PBXResourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXResourcesBuildPhase section */ 228 | 229 | /* Begin PBXShellScriptBuildPhase section */ 230 | FC6AC6D3A23B8C8C70C74FC1 /* [CP] Embed Pods Frameworks */ = { 231 | isa = PBXShellScriptBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | ); 235 | inputPaths = ( 236 | "${PODS_ROOT}/Target Support Files/Pods-QUIckControlExample/Pods-QUIckControlExample-frameworks.sh", 237 | "${BUILT_PRODUCTS_DIR}/PinCodeControl/PinCodeControl.framework", 238 | "${BUILT_PRODUCTS_DIR}/QUIckControl/QUIckControl.framework", 239 | "${BUILT_PRODUCTS_DIR}/Statable/Statable.framework", 240 | ); 241 | name = "[CP] Embed Pods Frameworks"; 242 | outputPaths = ( 243 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PinCodeControl.framework", 244 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/QUIckControl.framework", 245 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Statable.framework", 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | shellPath = /bin/sh; 249 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-QUIckControlExample/Pods-QUIckControlExample-frameworks.sh\"\n"; 250 | showEnvVarsInLog = 0; 251 | }; 252 | FD2567B6EDEE6935E01DE35D /* [CP] Check Pods Manifest.lock */ = { 253 | isa = PBXShellScriptBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | ); 257 | inputPaths = ( 258 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 259 | "${PODS_ROOT}/Manifest.lock", 260 | ); 261 | name = "[CP] Check Pods Manifest.lock"; 262 | outputPaths = ( 263 | "$(DERIVED_FILE_DIR)/Pods-QUIckControlExample-checkManifestLockResult.txt", 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | shellPath = /bin/sh; 267 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 268 | showEnvVarsInLog = 0; 269 | }; 270 | /* End PBXShellScriptBuildPhase section */ 271 | 272 | /* Begin PBXSourcesBuildPhase section */ 273 | A48D455E1E07340A003B4A7B /* Sources */ = { 274 | isa = PBXSourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | A48D45681E07340A003B4A7B /* ViewController.swift in Sources */, 278 | A48D45661E07340A003B4A7B /* AppDelegate.swift in Sources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | A48D45721E07340A003B4A7B /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | A48D457B1E07340A003B4A7B /* QUIckControlExampleTests.swift in Sources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | /* End PBXSourcesBuildPhase section */ 291 | 292 | /* Begin PBXTargetDependency section */ 293 | A48D45781E07340A003B4A7B /* PBXTargetDependency */ = { 294 | isa = PBXTargetDependency; 295 | target = A48D45611E07340A003B4A7B /* QUIckControlExample */; 296 | targetProxy = A48D45771E07340A003B4A7B /* PBXContainerItemProxy */; 297 | }; 298 | /* End PBXTargetDependency section */ 299 | 300 | /* Begin PBXVariantGroup section */ 301 | A48D45691E07340A003B4A7B /* Main.storyboard */ = { 302 | isa = PBXVariantGroup; 303 | children = ( 304 | A48D456A1E07340A003B4A7B /* Base */, 305 | ); 306 | name = Main.storyboard; 307 | sourceTree = ""; 308 | }; 309 | A48D456E1E07340A003B4A7B /* LaunchScreen.storyboard */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | A48D456F1E07340A003B4A7B /* Base */, 313 | ); 314 | name = LaunchScreen.storyboard; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXVariantGroup section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | A48D457D1E07340A003B4A7B /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_ANALYZER_NONNULL = YES; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BOOL_CONVERSION = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 332 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INFINITE_RECURSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 338 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 339 | CLANG_WARN_UNREACHABLE_CODE = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 342 | COPY_PHASE_STRIP = NO; 343 | DEBUG_INFORMATION_FORMAT = dwarf; 344 | ENABLE_STRICT_OBJC_MSGSEND = YES; 345 | ENABLE_TESTABILITY = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu99; 347 | GCC_DYNAMIC_NO_PIC = NO; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_OPTIMIZATION_LEVEL = 0; 350 | GCC_PREPROCESSOR_DEFINITIONS = ( 351 | "DEBUG=1", 352 | "$(inherited)", 353 | ); 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 361 | MTL_ENABLE_DEBUG_INFO = YES; 362 | ONLY_ACTIVE_ARCH = YES; 363 | SDKROOT = iphoneos; 364 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 365 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Debug; 369 | }; 370 | A48D457E1E07340A003B4A7B /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ALWAYS_SEARCH_USER_PATHS = NO; 374 | CLANG_ANALYZER_NONNULL = YES; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 382 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 383 | CLANG_WARN_EMPTY_BODY = YES; 384 | CLANG_WARN_ENUM_CONVERSION = YES; 385 | CLANG_WARN_INFINITE_RECURSION = YES; 386 | CLANG_WARN_INT_CONVERSION = YES; 387 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 388 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 389 | CLANG_WARN_UNREACHABLE_CODE = YES; 390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 391 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 392 | COPY_PHASE_STRIP = NO; 393 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 394 | ENABLE_NS_ASSERTIONS = NO; 395 | ENABLE_STRICT_OBJC_MSGSEND = YES; 396 | GCC_C_LANGUAGE_STANDARD = gnu99; 397 | GCC_NO_COMMON_BLOCKS = YES; 398 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 399 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 400 | GCC_WARN_UNDECLARED_SELECTOR = YES; 401 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 402 | GCC_WARN_UNUSED_FUNCTION = YES; 403 | GCC_WARN_UNUSED_VARIABLE = YES; 404 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 405 | MTL_ENABLE_DEBUG_INFO = NO; 406 | SDKROOT = iphoneos; 407 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 408 | TARGETED_DEVICE_FAMILY = "1,2"; 409 | VALIDATE_PRODUCT = YES; 410 | }; 411 | name = Release; 412 | }; 413 | A48D45801E07340A003B4A7B /* Debug */ = { 414 | isa = XCBuildConfiguration; 415 | baseConfigurationReference = 75DEE73C62D9157B14522F84 /* Pods-QUIckControlExample.debug.xcconfig */; 416 | buildSettings = { 417 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 418 | DEVELOPMENT_TEAM = 39RU298KG9; 419 | INFOPLIST_FILE = QUIckControlExample/Info.plist; 420 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 421 | PRODUCT_BUNDLE_IDENTIFIER = koden.QUIckControlExample; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | SWIFT_VERSION = 5.0; 424 | }; 425 | name = Debug; 426 | }; 427 | A48D45811E07340A003B4A7B /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 9E1DEEC8E20B4644E39E1091 /* Pods-QUIckControlExample.release.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | DEVELOPMENT_TEAM = 39RU298KG9; 433 | INFOPLIST_FILE = QUIckControlExample/Info.plist; 434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 435 | PRODUCT_BUNDLE_IDENTIFIER = koden.QUIckControlExample; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | SWIFT_VERSION = 5.0; 438 | }; 439 | name = Release; 440 | }; 441 | A48D45831E07340A003B4A7B /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 445 | BUNDLE_LOADER = "$(TEST_HOST)"; 446 | INFOPLIST_FILE = QUIckControlExampleTests/Info.plist; 447 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 448 | PRODUCT_BUNDLE_IDENTIFIER = koden.QUIckControlExampleTests; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | SWIFT_VERSION = 5.0; 451 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QUIckControlExample.app/QUIckControlExample"; 452 | }; 453 | name = Debug; 454 | }; 455 | A48D45841E07340A003B4A7B /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | buildSettings = { 458 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 459 | BUNDLE_LOADER = "$(TEST_HOST)"; 460 | INFOPLIST_FILE = QUIckControlExampleTests/Info.plist; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 462 | PRODUCT_BUNDLE_IDENTIFIER = koden.QUIckControlExampleTests; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | SWIFT_VERSION = 5.0; 465 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QUIckControlExample.app/QUIckControlExample"; 466 | }; 467 | name = Release; 468 | }; 469 | /* End XCBuildConfiguration section */ 470 | 471 | /* Begin XCConfigurationList section */ 472 | A48D455D1E07340A003B4A7B /* Build configuration list for PBXProject "QUIckControlExample" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | A48D457D1E07340A003B4A7B /* Debug */, 476 | A48D457E1E07340A003B4A7B /* Release */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Release; 480 | }; 481 | A48D457F1E07340A003B4A7B /* Build configuration list for PBXNativeTarget "QUIckControlExample" */ = { 482 | isa = XCConfigurationList; 483 | buildConfigurations = ( 484 | A48D45801E07340A003B4A7B /* Debug */, 485 | A48D45811E07340A003B4A7B /* Release */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Release; 489 | }; 490 | A48D45821E07340A003B4A7B /* Build configuration list for PBXNativeTarget "QUIckControlExampleTests" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | A48D45831E07340A003B4A7B /* Debug */, 494 | A48D45841E07340A003B4A7B /* Release */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Release; 498 | }; 499 | /* End XCConfigurationList section */ 500 | }; 501 | rootObject = A48D455A1E07340A003B4A7B /* Project object */; 502 | } 503 | -------------------------------------------------------------------------------- /Example/QUIckControlExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/QUIckControlExample.xcodeproj/project.xcworkspace/xcuserdata/K-o-D-e-N.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/QUIckControl/fc16c900569b1df905803e8221c6d3fec0f6c830/Example/QUIckControlExample.xcodeproj/project.xcworkspace/xcuserdata/K-o-D-e-N.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/QUIckControlExample.xcodeproj/xcuserdata/K-o-D-e-N.xcuserdatad/xcschemes/QUIckControlExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Example/QUIckControlExample.xcodeproj/xcuserdata/K-o-D-e-N.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | QUIckControlExample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | A48D45611E07340A003B4A7B 16 | 17 | primary 18 | 19 | 20 | A48D45751E07340A003B4A7B 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/QUIckControlExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // QUIckControlExample 4 | // 5 | // Created by Denis Koryttsev on 19/12/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(_ application: UIApplication) { 21 | // 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. 22 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 23 | } 24 | 25 | func applicationDidEnterBackground(_ application: UIApplication) { 26 | // 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. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(_ application: UIApplication) { 31 | // 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. 32 | } 33 | 34 | func applicationDidBecomeActive(_ application: UIApplication) { 35 | // 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. 36 | } 37 | 38 | func applicationWillTerminate(_ application: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | } 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Example/QUIckControlExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Example/QUIckControlExample/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/QUIckControlExample/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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /Example/QUIckControlExample/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 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/QUIckControlExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 23/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PinCodeControl 11 | import QUIckControl 12 | import Statable 13 | 14 | struct PinCodeElementsGroup { 15 | weak var control: PinCodeControl! 16 | weak var label: UILabel! 17 | 18 | init(control: PinCodeControl, label: UILabel) { 19 | self.control = control 20 | self.label = label 21 | } 22 | 23 | private func addEnabledDependencyFor(target: NSObject, enabledValue: Any?, disabledValue: Any?, keyPath: String) { 24 | self.control.setValue(enabledValue, forTarget: target, forKeyPath: keyPath, forAllStatesContained: [.valid]) 25 | self.control.setValue(disabledValue, forTarget: target, forKeyPath: keyPath, forInvertedState: [.valid]) 26 | } 27 | 28 | func addDependencyFor(group: PinCodeElementsGroup) { 29 | addEnabledDependencyFor(target: group.control, enabledValue: true, disabledValue: false, keyPath: #keyPath(UIControl.enabled)) 30 | addEnabledDependencyFor(target: group.label, enabledValue: UIColor.gray, disabledValue: UIColor.lightGray.withAlphaComponent(0.5), keyPath: #keyPath(UILabel.textColor)) 31 | } 32 | 33 | func addDependencyFor(button: UIButton) { 34 | addEnabledDependencyFor(target: button, enabledValue: true, disabledValue: false, keyPath: #keyPath(UIButton.enabled)) 35 | addEnabledDependencyFor(target: button, enabledValue: UIColor.black.withAlphaComponent(0.6), disabledValue: UIColor.lightGray.withAlphaComponent(0.2), keyPath: #keyPath(UIButton.backgroundColor)) 36 | } 37 | } 38 | 39 | class ViewController: UIViewController { 40 | var logger: String = "" { didSet { print(logger) } } 41 | 42 | @IBOutlet weak var oldPinCodeLabel: UILabel! 43 | @IBOutlet weak var oldPinCodeControl: PinCodeControl! 44 | @IBOutlet weak var newPinCodeLabel: UILabel! 45 | @IBOutlet weak var newPinCodeControl: PinCodeControl! 46 | @IBOutlet weak var repeatPinCodeLabel: UILabel! 47 | @IBOutlet weak var repeatPinCodeControl: PinCodeControl! 48 | @IBOutlet weak var applyButton: UIButton! 49 | 50 | lazy var oldGroup: PinCodeElementsGroup = PinCodeElementsGroup(control: self.oldPinCodeControl, label: self.oldPinCodeLabel) 51 | lazy var newGroup: PinCodeElementsGroup = PinCodeElementsGroup(control: self.newPinCodeControl, label: self.newPinCodeLabel) 52 | lazy var repeatGroup: PinCodeElementsGroup = PinCodeElementsGroup(control: self.repeatPinCodeControl, label: self.repeatPinCodeLabel) 53 | 54 | @objc var stateLogger: String = "" { didSet { print("Received string: " + stateLogger) } } 55 | var stateIsEnabled: Bool = false { didSet { print("State is " + String(stateIsEnabled)) } } 56 | var printFunction: () -> () = { print("default state") } 57 | 58 | override func viewDidLoad() { 59 | super.viewDidLoad() 60 | 61 | oldGroup.addDependencyFor(group: newGroup) 62 | newGroup.addDependencyFor(group: repeatGroup) 63 | repeatGroup.addDependencyFor(button: applyButton) 64 | applyButton.addTarget(self, action: #selector(touchUpInside(sender:)), for: .touchUpInside) 65 | 66 | // newPinCodeControl.itemPath = UIBezierPath(rect: CGRect(origin: .zero, size: CGSize(width: 35, height: 35))) 67 | // oldPinCodeControl.itemPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: 20, height: 20)), byRoundingCorners: [.bottomLeft, .topRight], cornerRadii: CGSize(width: 10, height: 10)) 68 | newPinCodeControl.subscribe(on: .typeComplete) { print($0) }.start() 69 | repeatGroup.control.validator = BlockPredicate() { $0 == self.newGroup.control.code } 70 | 71 | oldGroup.control.setValue("Old PIN-code is invalid", 72 | forTarget: self, 73 | forKeyPath: #keyPath(ViewController.stateLogger), 74 | for: .init(state: .invalid, priority: 1000, predicate: { $0.contains(.invalid) })) 75 | oldGroup.control.setValue("Old PIN-code is valid", 76 | forTarget: self, 77 | forKeyPath: #keyPath(ViewController.stateLogger), 78 | for: .init(state: [.filled, .invalid], priority: 1000, predicate: { $0.contains(.filled) && !$0.contains(.invalid) })) 79 | 80 | newGroup.control.subscribe(on: .init(usual: .valid), { print("New pin code is valid") }) 81 | 82 | let target = self 83 | oldGroup.control.subscribe(on: .init(intersected: .invalid), { [unowned target] in 84 | let button = target.applyButton! 85 | let title = button.currentTitle 86 | button.setTitle("FAIL", for: .disabled) 87 | button.setTitleColor(.red, for: .disabled) 88 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1), execute: { 89 | button.setTitle(title, for: .disabled) 90 | button.setTitleColor(nil, for: .disabled) 91 | }) 92 | }) 93 | } 94 | 95 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 96 | view.endEditing(false) 97 | } 98 | 99 | @objc func touchUpInside(sender: UIButton) { 100 | print("PIN-code '" + newGroup.control.code + "' saved") 101 | oldGroup.control.clear() 102 | newGroup.control.clear() 103 | repeatGroup.control.clear() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Example/QUIckControlExampleTests/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/QUIckControlExampleTests/QUIckControlExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlExampleTests.swift 3 | // QUIckControlExampleTests 4 | // 5 | // Created by Denis Koryttsev on 19/12/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import QUIckControlExample 11 | 12 | class QUIckControlExampleTests: 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 | -------------------------------------------------------------------------------- /Example/QUIckControlTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/QUIckControlTests/QUIckControlTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlTests.swift 3 | // QUIckControlTests 4 | // 5 | // Created by Denis Koryttsev on 23/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import QUIckControl 11 | 12 | class QUIckControlTests: XCTestCase { 13 | private var control = PinCodeControl(codeLength: 4, sideSize: 20, spaceSize: 10) 14 | @objc private var functionForTest: (() -> ())? = nil { 15 | didSet { 16 | // print(functionForTest) 17 | } 18 | } 19 | 20 | override func setUp() { 21 | super.setUp() 22 | // Put setup code here. This method is called before the invocation of each test method in the class. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | super.tearDown() 28 | } 29 | 30 | func example() { 31 | print("testExample") 32 | // XCTAssertTrue(control.isSelected == true) 33 | } 34 | 35 | func secondExample() { 36 | print("second testExample") 37 | // XCTAssertTrue(control.isEnabled == false) 38 | } 39 | 40 | func testPerformanceExample() { 41 | self.measure { 42 | _ = self.control.value(for: self.control.value(forKey: "applier") as! NSObject, forKey: #keyPath(CAShapeLayer.strokeColor), for: [.invalid, .filled]) 43 | } 44 | } 45 | 46 | func testCorrectValueForState() { 47 | control.setValue({ print("test") }, forTarget: self, forKeyPath: #keyPath(QUIckControlTests.functionForTest), for: .selected) 48 | control.setValue({ print("second") }, forTarget: self, forKeyPath: #keyPath(QUIckControlTests.functionForTest), for: .disabled) 49 | 50 | control.isEnabled = false 51 | if let function: (() -> ()) = functionForTest { 52 | function() 53 | } 54 | control.isEnabled = true 55 | control.isSelected = true 56 | functionForTest?() 57 | } 58 | 59 | } 60 | 61 | struct PrintState: Predicate, StateApplier { 62 | typealias EvaluatedEntity = StatableObject 63 | typealias ApplyObject = StatableObject 64 | 65 | let value: () -> String 66 | let evaluateFunction: (_: StatableObject) -> Bool 67 | 68 | func evaluate(with entity: StatableObject) -> Bool { 69 | return evaluateFunction(entity) 70 | } 71 | 72 | func apply(for target: StatableObject) { 73 | target.printFunction = value 74 | } 75 | } 76 | 77 | class StatableObject: Statable { 78 | typealias StateType = PrintState 79 | typealias StateFactor = PrintState 80 | 81 | var boolState: Bool = false { didSet { applyCurrentState() } } 82 | var printFunction: (() -> String)? = nil 83 | var factors = [PrintState]() 84 | var defaultState = PrintState(value: { return "default state" }, evaluateFunction: { object in object.boolState == false && object.printFunction == nil }) 85 | var state: PrintState { 86 | return factors.first { $0.evaluate(with: self) } ?? defaultState 87 | } 88 | 89 | func apply(state: PrintState) { 90 | state.apply(for: self) 91 | } 92 | } 93 | 94 | class StatableTests: XCTestCase { 95 | func testStatableObject() { 96 | let statable = StatableObject() 97 | statable.applyCurrentState() 98 | 99 | XCTAssertTrue(statable.printFunction?() == "default state") 100 | 101 | statable.factors.append(PrintState(value: { return "bool is true" }, evaluateFunction: { $0.boolState == true })) 102 | statable.factors.append(PrintState(value: { return "bool is false" }, evaluateFunction: { $0.boolState == false })) 103 | 104 | statable.boolState = true 105 | XCTAssertTrue(statable.printFunction?() == "bool is true") 106 | statable.boolState = false 107 | XCTAssertTrue(statable.printFunction?() == "bool is false") 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Denis Koryttsev 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 | -------------------------------------------------------------------------------- /PinCodeControl.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint QUIckControl.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'PinCodeControl' 11 | s.version = '0.2.1' 12 | s.summary = 'QUIckControl subclass for enter pin code.' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | # s.description = '' 21 | 22 | s.homepage = 'https://github.com/k-o-d-e-n/QUIckControl' 23 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 24 | s.license = { :type => 'MIT', :file => 'LICENSE' } 25 | s.author = { 'Denis Koryttsev' => 'koden.u8800@gmail.com' } 26 | s.source = { :git => 'https://github.com/k-o-d-e-n/QUIckControl.git', :tag => s.version.to_s } 27 | # s.social_media_url = 'https://twitter.com/' 28 | 29 | s.ios.deployment_target = '9.0' 30 | 31 | s.source_files = 'PinCodeControl/**/*' 32 | s.swift_versions = '4.2', '5.0', '5.1' 33 | 34 | # s.resource_bundles = { 35 | # 'QUIckControl' => ['QUIckControl/Assets/*.png'] 36 | # } 37 | 38 | # s.public_header_files = 'Pod/Classes/**/*.h' 39 | s.frameworks = 'UIKit' 40 | s.dependency 'QUIckControl' 41 | end 42 | -------------------------------------------------------------------------------- /PinCodeControl/PinCodeControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PinCodeControl.swift 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 07/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Statable 11 | import QUIckControl 12 | 13 | extension UIControl.Event { 14 | public static var typeComplete = UIControl.Event(rawValue: 1 << 24) 15 | } 16 | 17 | extension UIControl.State { 18 | public static var filled = UIControl.State(rawValue: 1 << 16) 19 | public static var invalid = UIControl.State(rawValue: 1 << 17) 20 | public static let valid = UIControl.State(rawValue: (1 << 18) | filled.rawValue) 21 | } 22 | 23 | fileprivate class ValueApplier: NSObject { 24 | private weak var control: PinCodeControl! 25 | 26 | init(control: PinCodeControl) { 27 | super.init() 28 | 29 | self.control = control 30 | } 31 | 32 | override func setValue(_ value: Any?, forKey key: String) { 33 | if control.sublayers.count == 0 { return } 34 | 35 | if !key.isEqual(#keyPath(CAShapeLayer.fillColor)) || control.codeLength == control.code.count { 36 | control.sublayers.forEach { $0.setValue(value, forKey: key) } 37 | return 38 | } 39 | for i in 0.. Any? { 46 | return control.sublayers.last?.value(forKeyPath: keyPath) 47 | } 48 | } 49 | 50 | // TODO: Remove limit on input only numbers. Create enum with types of control for enter secure code. Create base class SecureCodeControl with private class PinCodeControl. 51 | @IBDesignable open class PinCodeControl: QUIckControl, UIKeyInput, UITextInputTraits { 52 | /// preset states 53 | public enum States { 54 | public static let plain = QUICStateDescriptor(inverted: .filled) 55 | public static let valid = QUICStateDescriptor(intersected: .valid) 56 | public static let invalid = QUICStateDescriptor(intersected: [.filled, .invalid]) 57 | public static let highlighted = QUICStateDescriptor(intersected: .highlighted) 58 | public static let disabled = QUICStateDescriptor(intersected: .disabled, priority: 1000) 59 | } 60 | 61 | /// structure for initialize 62 | public struct Parameters { 63 | let length: Int 64 | let spaceSize: CGFloat 65 | let sideSize: CGFloat 66 | 67 | public init(length: Int, spaceSize: CGFloat, sideSize: CGFloat) { 68 | self.length = length 69 | self.spaceSize = spaceSize 70 | self.sideSize = sideSize 71 | } 72 | } 73 | 74 | /// current code string 75 | open var code: String { return text } 76 | 77 | /// full pin code length == count code items 78 | @IBInspectable open private(set) var codeLength: Int = 0 { 79 | didSet { if (oldValue != codeLength) { loadSublayers() } } 80 | } 81 | 82 | /// space between code items 83 | @IBInspectable open var spaceSize: CGFloat = 15 { 84 | didSet { if (oldValue != spaceSize && codeLength != 0) { setNeedsLayout() } } 85 | } 86 | 87 | /// size of side code item 88 | @IBInspectable open private(set) var sideSize: CGFloat = 0 { 89 | didSet { if (oldValue != sideSize) { loadDefaultPath(); setNeedsLayout() } } 90 | } 91 | 92 | /// filled state, yes when code type ended. 93 | @objc open private(set) var filled = false { 94 | didSet { 95 | if oldValue != filled { 96 | applyCurrentState() 97 | if filled { sendActions(for: .typeComplete) } 98 | } 99 | } 100 | } 101 | 102 | /// valid state, yes if entered code is valid. 103 | @objc open private(set) var valid = true { 104 | didSet { if oldValue != valid { applyCurrentState() } } 105 | } 106 | 107 | /// object for user validation pin code value. 108 | open var validator: BlockPredicate? 109 | 110 | /// if true, then code equal strings, such as '1111', '1234', '9876' will be defined as invalid values 111 | open var shouldUseDefaultValidation = true 112 | 113 | /// color for filled code item 114 | @objc open dynamic var filledItemColor: UIColor? 115 | 116 | /// bezier path for code item 117 | open var itemPath: UIBezierPath? 118 | 119 | private lazy var applier: ValueApplier = ValueApplier(control: self) 120 | private var text = String() 121 | fileprivate var sublayers: [CAShapeLayer] { return (layer.sublayers as? [CAShapeLayer]) ?? [] } 122 | private var defaultPath: UIBezierPath! 123 | 124 | required public init?(coder aDecoder: NSCoder) { 125 | super.init(coder: aDecoder) 126 | initializeInstance() 127 | } 128 | 129 | required public init(parameters: Parameters, frame: CGRect? = nil) { 130 | super.init(frame: frame ?? .zero) 131 | initializeInstance() 132 | 133 | self.spaceSize = parameters.spaceSize 134 | self.sideSize = parameters.sideSize 135 | self.codeLength = parameters.length 136 | 137 | loadDefaultPath() 138 | loadSublayers() 139 | } 140 | 141 | public override init(frame: CGRect) { 142 | #if !TARGET_INTERFACE_BUILDER 143 | fatalError("You should use init(parameters: Parameters, frame: CGRect?).") 144 | #else 145 | super.init(frame: frame) 146 | #endif 147 | } 148 | 149 | private func initializeInstance() { 150 | register(.filled, forBoolKeyPath: #keyPath(PinCodeControl.filled), inverted: false) 151 | register(.invalid, forBoolKeyPath: #keyPath(PinCodeControl.valid), inverted: true) 152 | // register(.valid, with: NSPredicate(format: "\(#keyPath(PinCodeControl.valid)) == YES AND \(#keyPath(PinCodeControl.filled)) == YES")) 153 | register(.valid, with: NSPredicate { control, _ in 154 | let control = control as! PinCodeControl 155 | 156 | return control.filled && control.valid 157 | }) 158 | // example use block factor 159 | // register(.valid) { control in 160 | // let control = control as! PinCodeControl 161 | // 162 | // return control.filled && control.valid 163 | // } 164 | if PinCodeControl.isDisabledAppearance { 165 | loadAppearance() 166 | } 167 | } 168 | 169 | private func loadDefaultPath() { 170 | defaultPath = UIBezierPath(ovalIn: CGRect(origin: .zero, size: CGSize(width: sideSize, height: sideSize))) 171 | } 172 | 173 | private func loadSublayers() { 174 | // let strokeColor = value(for: applier, forKey: #keyPath(CAShapeLayer.strokeColor), for: lastAppliedState) 175 | // let fillColor = value(for: applier, forKey: #keyPath(CAShapeLayer.fillColor), for: lastAppliedState) 176 | 177 | for _ in 0.., with event: UIEvent?) { 274 | performTransition(withCommit: false) { 275 | super.touchesBegan(touches, with: event) 276 | } 277 | } 278 | 279 | override open func touchesEnded(_ touches: Set, with event: UIEvent?) { 280 | performTransition { 281 | super.touchesEnded(touches, with: event) 282 | _ = self.becomeFirstResponder() 283 | } 284 | } 285 | 286 | override open func becomeFirstResponder() -> Bool { 287 | isHighlighted = true 288 | return super.becomeFirstResponder() 289 | } 290 | 291 | override open func resignFirstResponder() -> Bool { 292 | isHighlighted = false 293 | return super.resignFirstResponder() 294 | } 295 | 296 | // MARK: - UIKeyInput 297 | 298 | public var hasText: Bool { return text.count > 0 } 299 | 300 | public func deleteBackward() { 301 | if hasText { 302 | if text.count == codeLength { 303 | beginTransition() 304 | filled = false 305 | valid = true 306 | } 307 | deleteCharacters(in: text.count-1..) { 313 | let start = text.index(text.startIndex, offsetBy: range.lowerBound) 314 | let end = text.index(text.startIndex, offsetBy: range.upperBound) 315 | text.removeSubrange(start.. Bool { 356 | return validate(text) 357 | } 358 | 359 | /// method for validation entered pin code. Declared for subclasses override. 360 | open func validate(_ pin: String) -> Bool { 361 | return (shouldUseDefaultValidation ? defaultValidator.evaluate(with: pin) : true) && (validator != nil ? validator!.evaluate(with: pin) : true) 362 | } 363 | 364 | private let defaultValidator = BlockPredicate { (pin) -> Bool in 365 | let result = pin.reduce((true, true, true, 0)) { (result, character) -> (Bool, Bool, Bool, Int) in 366 | if result.3 == pin.count - 1 { return result } 367 | 368 | let number: Int = Int(String(character))! 369 | let next: Int = Int(String(pin[pin.index(pin.startIndex, offsetBy: result.3 + 1)]))! 370 | return (result.0 && number == next, result.1 && (number + 1) == next, result.2 && (number - 1) == next, result.3 + 1) 371 | } 372 | return !(result.0 || result.1 || result.2) 373 | } 374 | } 375 | 376 | /// Methods for configure appearance 377 | // TODO: Create appearance configurator class. 378 | extension PinCodeControl { 379 | static var isDisabledAppearance: Bool = true 380 | 381 | fileprivate func loadAppearance() { 382 | filledItemColor = UIColor.gray 383 | let filledColor = UIColor(red: 76.0 / 255.0, green: 145.0 / 255.0, blue: 65.0 / 255.0, alpha: 1).withAlphaComponent(0.7) 384 | let invalidColor = UIColor(red: 250.0 / 255.0, green: 88.0 / 255.0, blue: 87.0 / 255.0, alpha: 1) 385 | 386 | setFillColorForDisabledState(fillColor: filledItemColor!.withAlphaComponent(0.5)) 387 | setBorderColorForDisabledState(borderColor: UIColor.black.withAlphaComponent(0.3)) 388 | setBorderColorForPlainState(borderColor: UIColor.gray) 389 | setBorderColorForHighlightedState(borderColor: UIColor.lightGray.withAlphaComponent(0.5)) 390 | setFillColorForValidState(fillColor: filledColor) 391 | setBorderColorForValidState(borderColor: filledColor) 392 | setFillColorForInvalidState(fillColor: invalidColor) 393 | setBorderColorForInvalidState(borderColor: invalidColor) 394 | } 395 | 396 | @objc fileprivate dynamic func setFillColorForDisabledState(fillColor: UIColor?) { 397 | setFillColor(fillColor: fillColor, for: States.disabled) 398 | } 399 | 400 | @objc fileprivate dynamic func setFillColorForValidState(fillColor: UIColor?) { 401 | setFillColor(fillColor: fillColor, for: States.valid) 402 | } 403 | 404 | @objc fileprivate dynamic func setFillColorForInvalidState(fillColor: UIColor?) { 405 | setFillColor(fillColor: fillColor, for: States.invalid) 406 | } 407 | 408 | @objc fileprivate dynamic func setBorderColorForDisabledState(borderColor: UIColor?) { 409 | setBorderColor(borderColor: borderColor, for: States.disabled) 410 | } 411 | 412 | @objc fileprivate dynamic func setBorderColorForPlainState(borderColor: UIColor?) { 413 | setBorderColor(borderColor: borderColor, for: States.plain) 414 | } 415 | 416 | @objc fileprivate dynamic func setBorderColorForHighlightedState(borderColor: UIColor?) { 417 | setBorderColor(borderColor: borderColor, for: States.highlighted) 418 | } 419 | 420 | @objc fileprivate dynamic func setBorderColorForValidState(borderColor: UIColor?) { 421 | setBorderColor(borderColor: borderColor, for: States.valid) 422 | } 423 | 424 | @objc fileprivate dynamic func setBorderColorForInvalidState(borderColor: UIColor?) { 425 | setBorderColor(borderColor: borderColor, for: States.invalid) 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /QUIckControl.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint QUIckControl.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'QUIckControl' 11 | s.version = '0.1.1' 12 | s.summary = 'UIControl wrapper for quick implementation controls.' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = 'Base class for quick implementation UIControl subclass based on standard(enabled, highlighted, selected) and custom states. Implementation based on KVC.' 21 | 22 | s.homepage = 'https://github.com/k-o-d-e-n/QUIckControl' 23 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 24 | s.license = { :type => 'MIT', :file => 'LICENSE' } 25 | s.author = { 'Denis Koryttsev' => 'koden.u8800@gmail.com' } 26 | s.source = { :git => 'https://github.com/k-o-d-e-n/QUIckControl.git', :tag => s.version.to_s } 27 | # s.social_media_url = 'https://twitter.com/' 28 | 29 | s.ios.deployment_target = '9.0' 30 | 31 | s.source_files = 'QUIckControl/**/*' 32 | 33 | # s.resource_bundles = { 34 | # 'QUIckControl' => ['QUIckControl/Assets/*.png'] 35 | # } 36 | 37 | # s.public_header_files = 'Pod/Classes/**/*.h' 38 | s.frameworks = 'UIKit' 39 | s.dependency 'Statable' 40 | end 41 | -------------------------------------------------------------------------------- /QUIckControl/QUICStateDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QUICStateDescriptorKey.swift 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 06/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Statable 11 | 12 | public final class QUICStateDescriptor: BlockPredicate, Hashable, StateDescriptor { 13 | public typealias StateType = UIControl.State 14 | 15 | public let priority: Int 16 | public let state: UIControl.State // is used only as identifier, may be deleted, because not used as required 17 | public func hash(into hasher: inout Hasher) { 18 | hasher.combine(state.rawValue) 19 | hasher.combine(priority) 20 | } 21 | 22 | override init(predicate: @escaping (_ object: UIControl.State) -> Bool) { 23 | fatalError("This initializer not used") 24 | } 25 | 26 | public convenience init(usual: UIControl.State, priority: Int = 1000) { 27 | self.init(state: usual, priority: priority, predicate: { usual == $0 }) 28 | } 29 | 30 | public convenience init(inverted: UIControl.State, priority: Int = 750) { 31 | self.init(state: inverted, priority: priority, predicate: { (inverted.rawValue & $0.rawValue) != inverted.rawValue }) 32 | } 33 | 34 | public convenience init(intersected: UIControl.State, priority: Int = 999) { 35 | self.init(state: intersected, priority: priority, predicate: { (intersected.rawValue & $0.rawValue) == intersected.rawValue }) 36 | } 37 | 38 | public convenience init(oneOfSeveral: UIControl.State, priority: Int = 500) { 39 | self.init(state: oneOfSeveral, priority: priority, predicate: { (oneOfSeveral.rawValue & $0.rawValue) != 0 }) 40 | } 41 | 42 | public convenience init(noneOfThis: UIControl.State, priority: Int = 500) { 43 | self.init(state: noneOfThis, priority: priority, predicate: { (noneOfThis.rawValue & $0.rawValue) == 0 }) 44 | } 45 | 46 | public init(state: UIControl.State, priority: Int, predicate: @escaping (_ state: UIControl.State) -> Bool) { 47 | self.priority = priority 48 | self.state = state 49 | super.init(predicate: predicate) 50 | } 51 | 52 | static public func ==(lhs: QUICStateDescriptor, rhs: QUICStateDescriptor) -> Bool { 53 | return lhs.state == rhs.state && lhs.priority == rhs.priority 54 | } 55 | } 56 | 57 | // deprecated 58 | 59 | enum QUICStateType : Int16 { 60 | case usual 61 | case intersected 62 | case inverted 63 | case oneOfSeveral 64 | case noneOfThis 65 | case custom 66 | } 67 | -------------------------------------------------------------------------------- /QUIckControl/QUIckControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControl.swift 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 23/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Statable 11 | 12 | public protocol QUIckControlActionTarget { 13 | func start() 14 | func stop() 15 | } 16 | 17 | open class QUIckControl : UIControl, KnownStatable { 18 | public typealias Factor = QUIckControlStateFactor 19 | public typealias StateType = UIControl.State 20 | 21 | public var isTransitionTime = false 22 | private lazy var thisValueTarget: QUIckControlValueTarget = QUIckControlValueTarget(target: self) 23 | 24 | public var factors = [Factor]() 25 | private var targets = [QUIckControlValueTarget]() 26 | private let scheduledActions = NSMutableSet() 27 | private var actionTargets = [QUIckControlActionTarget]() 28 | private lazy var subscribers: [QUIckControlSubscriber] = [QUIckControlSubscriber]() 29 | public var lastAppliedState: UIControl.State = .normal { 30 | didSet { 31 | for subscriber in subscribers { 32 | subscriber.invoke(ifMatched: lastAppliedState) 33 | } 34 | } 35 | } 36 | 37 | required public init?(coder aDecoder: NSCoder) { 38 | super.init(coder: aDecoder) 39 | 40 | registerExistedStates() 41 | } 42 | 43 | public override init(frame: CGRect) { 44 | super.init(frame: frame) 45 | 46 | registerExistedStates() 47 | } 48 | 49 | private func registerExistedStates() { 50 | register(.selected, forBoolKeyPath: #keyPath(UIControl.selected), inverted: false) 51 | register(.highlighted, forBoolKeyPath: #keyPath(UIControl.highlighted), inverted: false) 52 | register(.disabled, forBoolKeyPath: #keyPath(UIControl.enabled), inverted: true) 53 | } 54 | 55 | // MARK: States 56 | 57 | override open var isSelected: Bool { 58 | didSet { if oldValue != isSelected { applyCurrentState() } } 59 | } 60 | 61 | override open var isEnabled: Bool { 62 | didSet { if oldValue != isEnabled { applyCurrentState() } } 63 | } 64 | 65 | override open var isHighlighted: Bool { 66 | didSet { if oldValue != isHighlighted { applyCurrentState() } } 67 | } 68 | 69 | public func beginTransition() { 70 | isTransitionTime = true 71 | } 72 | 73 | public func endTransition() { 74 | isTransitionTime = false 75 | scheduledActions.removeAllObjects() 76 | } 77 | 78 | public func commitTransition() { 79 | if !isTransitionTime { return } 80 | 81 | isTransitionTime = false 82 | applyCurrentState() 83 | let actions = scheduledActions 84 | scheduledActions.removeAllObjects() 85 | for action: Any in actions { 86 | sendActions(for: action as! UIControl.Event) 87 | } 88 | } 89 | 90 | public func performTransition(withCommit commit: Bool = true, transition: () -> Void) { 91 | beginTransition() 92 | transition() 93 | commit ? commitTransition() : endTransition() 94 | } 95 | 96 | public func register(_ state: UIControl.State, forBoolKeyPath keyPath: String, inverted: Bool) { 97 | // & UIControlStateApplication ? 98 | // factors.append(QBoolStateFactor(property: keyPath, state: state, inverted: inverted)) 99 | factors.append(QUIckControlStateFactor(state: state, predicate: NSPredicate(format: "\(keyPath) == \(inverted ? "NO" : "YES")"))) 100 | 101 | // example use block factor 102 | // let boolFactor = QBoolStateFactor(property: keyPath, state: state, inverted: inverted) 103 | // factors.append(StateFactor(state: state, predicate: { (control) -> Bool in 104 | // return boolFactor.evaluate(with: control) 105 | // })) 106 | } 107 | 108 | public func register(_ state: UIControl.State, with predicate: NSPredicate) { 109 | factors.append(QUIckControlStateFactor(state: state, predicate: predicate)) 110 | } 111 | 112 | // example use block factor 113 | // func register(_ state: UIControlState, with factor: @escaping (_ object: QUIckControl) -> Bool) { 114 | // factors.append(StateFactor(state: state, predicate: factor)) 115 | // } 116 | 117 | // MARK: - Actions 118 | 119 | open func subscribe(on events: UIControl.Event, _ action: @escaping (QUIckControl) -> Void) -> QUIckControlActionTarget { 120 | let actionTarget = QUIckControlActionTargetImp(control: self, controlEvents: events) 121 | actionTarget.action = action 122 | actionTargets.append(actionTarget) 123 | return actionTarget 124 | } 125 | 126 | override open func sendActions(for controlEvents: UIControl.Event) { 127 | if isTransitionTime { 128 | scheduledActions.add(controlEvents) 129 | return 130 | } 131 | super.sendActions(for: controlEvents) 132 | } 133 | 134 | open func subscribe(on state: QUICStateDescriptor, _ action: @escaping () -> ()) { 135 | self.subscribers.append(QUIckControlSubscriber(for: state, action: action)) 136 | } 137 | 138 | // MARK: - Values 139 | 140 | public func removeValues(forTarget target: NSObject, forKeyPath key: String, forState state: UIControl.State) { 141 | valueTarget(forTarget: target).removeValues(for: key, forState: state) 142 | } 143 | 144 | public func removeValues(forTarget target: NSObject, forKeyPath key: String) { 145 | valueTarget(forTarget: target).removeValues(for: key) 146 | } 147 | 148 | public func removeValues(forTarget target: NSObject? = nil) { 149 | guard let externalTarget = target else { thisValueTarget.removeValues(); return } 150 | 151 | let targetIndex = indexOfTarget(externalTarget) 152 | if targetIndex != nil { 153 | targets.remove(at: targetIndex!) 154 | } 155 | } 156 | 157 | public func setValue(_ value: Any?, forTarget target: NSObject, forKeyPath key: String, forInvertedState state: UIControl.State) { 158 | setValue(value, forTarget: target, forKeyPath: key, for: QUICStateDescriptor(inverted: state)) 159 | } 160 | 161 | public func setValue(_ value: Any?, forTarget target: NSObject, forKeyPath key: String, forAllStatesContained state: UIControl.State) { 162 | setValue(value, forTarget: target, forKeyPath: key, for: QUICStateDescriptor(intersected: state)) 163 | } 164 | 165 | public func setValue(_ value: Any?, forKeyPath key: String, for state: UIControl.State) { 166 | setValue(value, forTarget: self, forKeyPath: key, for: state) 167 | } 168 | 169 | public func setValue(_ value: Any?, forTarget target: NSObject, forKeyPath key: String, for state: UIControl.State) { 170 | setValue(value, forTarget: target, forKeyPath: key, for: QUICStateDescriptor(usual: state)) 171 | } 172 | 173 | public func setValue(_ value: Any?, forTarget target: NSObject? = nil, forKeyPath key: String, for descriptor: QUICStateDescriptor) { 174 | let valTarget = valueTarget(forTarget: target ?? self) 175 | valTarget.setValue(value, forKeyPath: key, for: descriptor) 176 | if descriptor.evaluate(with: state) { 177 | valTarget.applyValue(value, forKey: key) 178 | } 179 | } 180 | 181 | private func indexOfTarget(_ target: NSObject) -> Int? { 182 | return targets.firstIndex(where: { return $0.target.isEqual(target) }) 183 | } 184 | 185 | private func valueTarget(forTarget target: NSObject!) -> QUIckControlValueTarget { 186 | if self == target { return thisValueTarget } 187 | 188 | var index = indexOfTarget(target) 189 | if index == nil { 190 | let valueTarget = QUIckControlValueTarget(target: target) 191 | targets.append(valueTarget) 192 | index = targets.count - 1 193 | } 194 | return targets[index!] 195 | } 196 | 197 | // MARK: - Apply state values 198 | 199 | open func applyCurrentState() { 200 | guard !isTransitionTime else { return } 201 | 202 | let currentState = state 203 | guard lastAppliedState != currentState else { return } 204 | 205 | apply(state: currentState) 206 | lastAppliedState = currentState 207 | } 208 | 209 | open func applyCurrentState(forTarget target: NSObject) { 210 | valueTarget(forTarget: target).apply(state: state) 211 | } 212 | 213 | // force apply state 214 | open func apply(state: UIControl.State) { 215 | setNeedsDisplay() 216 | setNeedsLayout() 217 | thisValueTarget.apply(state: state) 218 | for target: QUIckControlValueTarget in targets { 219 | target.apply(state: state) 220 | } 221 | } 222 | 223 | public func value(for target: NSObject, forKey key: String, for state: UIControl.State) -> Any? { 224 | return valueTarget(forTarget: target).valueForKey(key: key, forState: state) 225 | } 226 | 227 | override open var state: UIControl.State { 228 | var result: UIControl.State = .normal 229 | for factor in factors { 230 | factor.mark(state: &result, ifEvaluatedWith: self) 231 | } 232 | return result 233 | } 234 | 235 | // MARK: Deinitialier 236 | 237 | deinit { 238 | for target in actionTargets { 239 | target.stop() 240 | } 241 | actionTargets.removeAll() 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /QUIckControl/QUIckControlActionTargetImp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlActionTargetImp.swift 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 08/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class QUIckControlActionTargetImp: NSObject, QUIckControlActionTarget { 12 | weak var parentControl: QUIckControl? 13 | var events: UIControl.Event! 14 | var action: ((_ control: QUIckControl) -> ())? 15 | 16 | init(control: QUIckControl, controlEvents events: UIControl.Event) { 17 | super.init() 18 | 19 | self.events = events 20 | self.parentControl = control 21 | } 22 | 23 | @objc func actionSelector(_ control: QUIckControl) { 24 | action?(control) 25 | } 26 | 27 | func start() { 28 | self.parentControl?.addTarget(self, action: #selector(self.actionSelector), for: self.events) 29 | } 30 | 31 | func stop() { 32 | self.parentControl?.removeTarget(self, action: #selector(self.actionSelector), for: self.events) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /QUIckControl/QUIckControlPrivate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlPrivate.swift 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 07/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Statable 11 | 12 | private extension Bool { 13 | init(_ integer: T) { 14 | self.init(integer != 0) 15 | } 16 | } 17 | 18 | public final class QUIckControlStateFactor: Predicate, StateFactor { 19 | public typealias EvaluatedEntity = Control 20 | public typealias StateType = UIControl.State 21 | 22 | let predicate: NSPredicate 23 | let state: UIControl.State 24 | 25 | required public init(state: UIControl.State, predicate: NSPredicate) { 26 | self.state = state 27 | self.predicate = predicate 28 | } 29 | 30 | public func evaluate(with object: Control) -> Bool { 31 | return predicate.evaluate(with: object) 32 | } 33 | 34 | public func mark(state: inout UIControl.State) { 35 | state.formUnion(self.state) 36 | } 37 | } 38 | 39 | final class QUIckControlSubscriber: StateSubscriber { 40 | typealias EvaluatedEntity = UIControl.State 41 | 42 | let action: () -> () 43 | let descriptor: QUICStateDescriptor 44 | 45 | init(for descriptor: QUICStateDescriptor, action: @escaping () -> ()) { 46 | self.action = action 47 | self.descriptor = descriptor 48 | } 49 | 50 | func invoke() { 51 | action() 52 | } 53 | 54 | func evaluate(with entity: UIControl.State) -> Bool { 55 | return descriptor.evaluate(with: entity) 56 | } 57 | } 58 | 59 | // for example using BlockPredicate 60 | final class QUIckControlFactor: BlockPredicate, StateFactor { 61 | typealias StateType = UIControl.State 62 | 63 | let state: StateType 64 | 65 | required init(state: UIControl.State, predicate: @escaping (_ object: Control) -> Bool) { 66 | self.state = state 67 | 68 | super.init(predicate: predicate) 69 | } 70 | 71 | func mark(state: inout UIControl.State) { 72 | state.formUnion(self.state) 73 | } 74 | } 75 | 76 | // deprecated 77 | 78 | struct QBoolStateFactor: Predicate { 79 | typealias EvaluatedEntity = QUIckControl 80 | let property: String 81 | let state: UIControl.State 82 | let inverted: Bool 83 | 84 | func evaluate(with object: QUIckControl) -> Bool { 85 | let propertyValue = object.value(forKeyPath: property) as! Bool 86 | return Bool(propertyValue.hashValue ^ inverted.hashValue) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /QUIckControl/QUIckControlValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlValue.swift 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 06/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class QUIckControlValue { 12 | let key: String 13 | private var values = [QUICStateDescriptor: Any]() 14 | 15 | init(key: String) { 16 | self.key = key 17 | } 18 | 19 | // TODO: Find decision problem with idle descriptors 20 | func setValue(_ value: Any?, for descriptor: QUICStateDescriptor) { 21 | values[descriptor] = value 22 | } 23 | 24 | func value(for state: UIControl.State) -> Any? { 25 | return values 26 | .filter { return $0.key.evaluate(with: state) } 27 | .max { $0.key.priority < $1.key.priority }? 28 | .value 29 | } 30 | 31 | func removeValues(for state: UIControl.State) { 32 | while let index = values.firstIndex(where: { return $0.key.evaluate(with: state) }) { 33 | values.remove(at: index) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /QUIckControl/QUIckControlValueTarget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlValueTarget.swift 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 06/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Statable 11 | 12 | final class QUIckControlValueTarget: StatesApplier { 13 | typealias ApplyObject = NSObject 14 | typealias StateType = UIControl.State 15 | 16 | private var values = [String: QUIckControlValue]() 17 | private var defaults = [String: Any]() 18 | weak var target: NSObject! 19 | 20 | init(target: NSObject) { 21 | self.target = target 22 | } 23 | 24 | func setValue(_ value: Any?, forKeyPath key: String, for descriptor: QUICStateDescriptor) { 25 | keyValue(forKey: key).setValue(value, for: descriptor) 26 | } 27 | 28 | private func keyValue(forKey key: String) -> QUIckControlValue { 29 | var keyValue = values[key] 30 | if (keyValue == nil) { 31 | keyValue = registerKey(key) 32 | } 33 | return keyValue! 34 | } 35 | 36 | private func registerKey(_ key: String) -> QUIckControlValue { 37 | let keyValue = QUIckControlValue(key: key) 38 | values[key] = keyValue 39 | let defaultValue = target.value(forKeyPath: key) 40 | if (defaultValue != nil) { 41 | defaults[key] = defaultValue 42 | } 43 | return keyValue 44 | } 45 | 46 | func apply(state: UIControl.State, for target: NSObject) { 47 | for (key, value) in values { 48 | let keyValue = value.value(for: state) 49 | target.setValue(keyValue ?? defaults[key], forKeyPath: key) 50 | } 51 | } 52 | 53 | func apply(state: UIControl.State) { 54 | apply(state: state, for: target) 55 | } 56 | 57 | func applyValue(_ value: Any?, forKey key: String) { 58 | target.setValue(value, forKeyPath: key) 59 | } 60 | 61 | func valueForKey(key: String, forState state: UIControl.State) -> Any? { 62 | return values[key]?.value(for: state) ?? defaults[key] 63 | } 64 | 65 | func removeValues() { 66 | values.removeAll() 67 | } 68 | 69 | func removeValues(for key: String) { 70 | values.removeValue(forKey: key) 71 | } 72 | 73 | func removeValues(for key: String, forState state: UIControl.State) { 74 | values[key]?.removeValues(for: state) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A4372D441DCE73AE0005FBBC /* QUIckControlActionTargetImp.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D3E1DCE73AE0005FBBC /* QUIckControlActionTargetImp.m */; }; 11 | A4372D451DCE73AE0005FBBC /* QUIckControlValue.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D411DCE73AE0005FBBC /* QUIckControlValue.m */; }; 12 | A4372D461DCE73AE0005FBBC /* QUIckControlValueTarget.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D431DCE73AE0005FBBC /* QUIckControlValueTarget.m */; }; 13 | A4372D4A1DCE73E30005FBBC /* QUIckControl.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D481DCE73E30005FBBC /* QUIckControl.m */; }; 14 | A4372D4D1DCE73F70005FBBC /* PinCodeControl.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D4C1DCE73F70005FBBC /* PinCodeControl.m */; }; 15 | A4372D521DCE74150005FBBC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D4F1DCE74150005FBBC /* AppDelegate.m */; }; 16 | A4372D531DCE74150005FBBC /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D511DCE74150005FBBC /* ViewController.m */; }; 17 | A4372D551DCE74790005FBBC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A4372D541DCE74790005FBBC /* Assets.xcassets */; }; 18 | A4372D591DCE74AC0005FBBC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D581DCE74AC0005FBBC /* main.m */; }; 19 | A4372D5E1DCE75400005FBBC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A4372D5A1DCE75400005FBBC /* LaunchScreen.storyboard */; }; 20 | A4372D5F1DCE75400005FBBC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A4372D5C1DCE75400005FBBC /* Main.storyboard */; }; 21 | A4372D641DCE76E40005FBBC /* KDNControlTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A4372D611DCE75F30005FBBC /* KDNControlTests.m */; }; 22 | A4372D651DCE76ED0005FBBC /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = A4372D601DCE75F30005FBBC /* Info.plist */; }; 23 | A479130D1DCEBB830063D244 /* QUICStateDescriptorKey.m in Sources */ = {isa = PBXBuildFile; fileRef = A479130C1DCEBB830063D244 /* QUICStateDescriptorKey.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | A428E9D31DB38F91008DE27C /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = A428E9B11DB38F90008DE27C /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = A428E9B81DB38F91008DE27C; 32 | remoteInfo = KDNControl; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | A428E9B91DB38F91008DE27C /* QUIckControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QUIckControl.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | A428E9D21DB38F91008DE27C /* QUIckControl.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QUIckControl.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | A4372D3D1DCE73AE0005FBBC /* QUIckControlActionTargetImp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QUIckControlActionTargetImp.h; path = QUIckControl/QUIckControl/QUIckControlActionTargetImp.h; sourceTree = SOURCE_ROOT; }; 40 | A4372D3E1DCE73AE0005FBBC /* QUIckControlActionTargetImp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QUIckControlActionTargetImp.m; path = QUIckControl/QUIckControl/QUIckControlActionTargetImp.m; sourceTree = SOURCE_ROOT; }; 41 | A4372D3F1DCE73AE0005FBBC /* QUIckControlPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QUIckControlPrivate.h; path = QUIckControl/QUIckControl/QUIckControlPrivate.h; sourceTree = SOURCE_ROOT; }; 42 | A4372D401DCE73AE0005FBBC /* QUIckControlValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QUIckControlValue.h; path = QUIckControl/QUIckControl/QUIckControlValue.h; sourceTree = SOURCE_ROOT; }; 43 | A4372D411DCE73AE0005FBBC /* QUIckControlValue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QUIckControlValue.m; path = QUIckControl/QUIckControl/QUIckControlValue.m; sourceTree = SOURCE_ROOT; }; 44 | A4372D421DCE73AE0005FBBC /* QUIckControlValueTarget.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QUIckControlValueTarget.h; path = QUIckControl/QUIckControl/QUIckControlValueTarget.h; sourceTree = SOURCE_ROOT; }; 45 | A4372D431DCE73AE0005FBBC /* QUIckControlValueTarget.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QUIckControlValueTarget.m; path = QUIckControl/QUIckControl/QUIckControlValueTarget.m; sourceTree = SOURCE_ROOT; }; 46 | A4372D471DCE73E30005FBBC /* QUIckControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QUIckControl.h; path = QUIckControl/QUIckControl/QUIckControl.h; sourceTree = SOURCE_ROOT; }; 47 | A4372D481DCE73E30005FBBC /* QUIckControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QUIckControl.m; path = QUIckControl/QUIckControl/QUIckControl.m; sourceTree = SOURCE_ROOT; }; 48 | A4372D491DCE73E30005FBBC /* QUIckControlActionTarget.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QUIckControlActionTarget.h; path = QUIckControl/QUIckControl/QUIckControlActionTarget.h; sourceTree = SOURCE_ROOT; }; 49 | A4372D4B1DCE73F70005FBBC /* PinCodeControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PinCodeControl.h; path = QUIckControl/PincodeControl/PinCodeControl.h; sourceTree = SOURCE_ROOT; }; 50 | A4372D4C1DCE73F70005FBBC /* PinCodeControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PinCodeControl.m; path = QUIckControl/PincodeControl/PinCodeControl.m; sourceTree = SOURCE_ROOT; }; 51 | A4372D4E1DCE74150005FBBC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = QUIckControl/AppDelegate.h; sourceTree = SOURCE_ROOT; }; 52 | A4372D4F1DCE74150005FBBC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = QUIckControl/AppDelegate.m; sourceTree = SOURCE_ROOT; }; 53 | A4372D501DCE74150005FBBC /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ViewController.h; path = QUIckControl/ViewController.h; sourceTree = SOURCE_ROOT; }; 54 | A4372D511DCE74150005FBBC /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ViewController.m; path = QUIckControl/ViewController.m; sourceTree = SOURCE_ROOT; }; 55 | A4372D541DCE74790005FBBC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = QUIckControl/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 56 | A4372D561DCE74860005FBBC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = QUIckControl/Info.plist; sourceTree = SOURCE_ROOT; }; 57 | A4372D581DCE74AC0005FBBC /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = QUIckControl/main.m; sourceTree = SOURCE_ROOT; }; 58 | A4372D5B1DCE75400005FBBC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = QUIckControl/Base.lproj/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; }; 59 | A4372D5D1DCE75400005FBBC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = QUIckControl/Base.lproj/Main.storyboard; sourceTree = SOURCE_ROOT; }; 60 | A4372D601DCE75F30005FBBC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = QUIckControlTests/Info.plist; sourceTree = SOURCE_ROOT; }; 61 | A4372D611DCE75F30005FBBC /* KDNControlTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KDNControlTests.m; path = QUIckControlTests/KDNControlTests.m; sourceTree = SOURCE_ROOT; }; 62 | A479130B1DCEBB830063D244 /* QUICStateDescriptorKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QUICStateDescriptorKey.h; path = QUIckControl/QUIckControl/QUICStateDescriptorKey.h; sourceTree = SOURCE_ROOT; }; 63 | A479130C1DCEBB830063D244 /* QUICStateDescriptorKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QUICStateDescriptorKey.m; path = QUIckControl/QUIckControl/QUICStateDescriptorKey.m; sourceTree = SOURCE_ROOT; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | A428E9B61DB38F91008DE27C /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | A428E9CF1DB38F91008DE27C /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXFrameworksBuildPhase section */ 82 | 83 | /* Begin PBXGroup section */ 84 | A428E9B01DB38F90008DE27C = { 85 | isa = PBXGroup; 86 | children = ( 87 | A428E9BB1DB38F91008DE27C /* QUIckControl */, 88 | A428E9D51DB38F91008DE27C /* KDNControlTests */, 89 | A428E9BA1DB38F91008DE27C /* Products */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | A428E9BA1DB38F91008DE27C /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | A428E9B91DB38F91008DE27C /* QUIckControl.app */, 97 | A428E9D21DB38F91008DE27C /* QUIckControl.xctest */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | A428E9BB1DB38F91008DE27C /* QUIckControl */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | A4372D5A1DCE75400005FBBC /* LaunchScreen.storyboard */, 106 | A4372D5C1DCE75400005FBBC /* Main.storyboard */, 107 | A4372D561DCE74860005FBBC /* Info.plist */, 108 | A4372D541DCE74790005FBBC /* Assets.xcassets */, 109 | A4372D4E1DCE74150005FBBC /* AppDelegate.h */, 110 | A4372D4F1DCE74150005FBBC /* AppDelegate.m */, 111 | A4372D501DCE74150005FBBC /* ViewController.h */, 112 | A4372D511DCE74150005FBBC /* ViewController.m */, 113 | A428E9E51DB3F6B0008DE27C /* PincodeControl */, 114 | A428E9E11DB38FA9008DE27C /* QUIckControl */, 115 | A428E9BC1DB38F91008DE27C /* Supporting Files */, 116 | ); 117 | name = QUIckControl; 118 | path = KDNControl; 119 | sourceTree = ""; 120 | }; 121 | A428E9BC1DB38F91008DE27C /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | A4372D581DCE74AC0005FBBC /* main.m */, 125 | ); 126 | name = "Supporting Files"; 127 | sourceTree = ""; 128 | }; 129 | A428E9D51DB38F91008DE27C /* KDNControlTests */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | A4372D601DCE75F30005FBBC /* Info.plist */, 133 | A4372D611DCE75F30005FBBC /* KDNControlTests.m */, 134 | ); 135 | path = KDNControlTests; 136 | sourceTree = ""; 137 | }; 138 | A428E9E11DB38FA9008DE27C /* QUIckControl */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | A4372D471DCE73E30005FBBC /* QUIckControl.h */, 142 | A4372D481DCE73E30005FBBC /* QUIckControl.m */, 143 | A4372D491DCE73E30005FBBC /* QUIckControlActionTarget.h */, 144 | A499FB421DCA8EB6003624B0 /* QUIckControlPrivate */, 145 | ); 146 | name = QUIckControl; 147 | sourceTree = ""; 148 | }; 149 | A428E9E51DB3F6B0008DE27C /* PincodeControl */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | A4372D4B1DCE73F70005FBBC /* PinCodeControl.h */, 153 | A4372D4C1DCE73F70005FBBC /* PinCodeControl.m */, 154 | ); 155 | name = PincodeControl; 156 | sourceTree = ""; 157 | }; 158 | A499FB421DCA8EB6003624B0 /* QUIckControlPrivate */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | A479130B1DCEBB830063D244 /* QUICStateDescriptorKey.h */, 162 | A479130C1DCEBB830063D244 /* QUICStateDescriptorKey.m */, 163 | A4372D3D1DCE73AE0005FBBC /* QUIckControlActionTargetImp.h */, 164 | A4372D3E1DCE73AE0005FBBC /* QUIckControlActionTargetImp.m */, 165 | A4372D3F1DCE73AE0005FBBC /* QUIckControlPrivate.h */, 166 | A4372D401DCE73AE0005FBBC /* QUIckControlValue.h */, 167 | A4372D411DCE73AE0005FBBC /* QUIckControlValue.m */, 168 | A4372D421DCE73AE0005FBBC /* QUIckControlValueTarget.h */, 169 | A4372D431DCE73AE0005FBBC /* QUIckControlValueTarget.m */, 170 | ); 171 | name = QUIckControlPrivate; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXNativeTarget section */ 177 | A428E9B81DB38F91008DE27C /* QUIckControl */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = A428E9DB1DB38F91008DE27C /* Build configuration list for PBXNativeTarget "QUIckControl" */; 180 | buildPhases = ( 181 | A428E9B51DB38F91008DE27C /* Sources */, 182 | A428E9B61DB38F91008DE27C /* Frameworks */, 183 | A428E9B71DB38F91008DE27C /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | ); 189 | name = QUIckControl; 190 | productName = KDNControl; 191 | productReference = A428E9B91DB38F91008DE27C /* QUIckControl.app */; 192 | productType = "com.apple.product-type.application"; 193 | }; 194 | A428E9D11DB38F91008DE27C /* QUIckControlTests */ = { 195 | isa = PBXNativeTarget; 196 | buildConfigurationList = A428E9DE1DB38F91008DE27C /* Build configuration list for PBXNativeTarget "QUIckControlTests" */; 197 | buildPhases = ( 198 | A428E9CE1DB38F91008DE27C /* Sources */, 199 | A428E9CF1DB38F91008DE27C /* Frameworks */, 200 | A428E9D01DB38F91008DE27C /* Resources */, 201 | ); 202 | buildRules = ( 203 | ); 204 | dependencies = ( 205 | A428E9D41DB38F91008DE27C /* PBXTargetDependency */, 206 | ); 207 | name = QUIckControlTests; 208 | productName = KDNControlTests; 209 | productReference = A428E9D21DB38F91008DE27C /* QUIckControl.xctest */; 210 | productType = "com.apple.product-type.bundle.unit-test"; 211 | }; 212 | /* End PBXNativeTarget section */ 213 | 214 | /* Begin PBXProject section */ 215 | A428E9B11DB38F90008DE27C /* Project object */ = { 216 | isa = PBXProject; 217 | attributes = { 218 | LastUpgradeCheck = 0730; 219 | ORGANIZATIONNAME = "Denis Koryttsev"; 220 | TargetAttributes = { 221 | A428E9B81DB38F91008DE27C = { 222 | CreatedOnToolsVersion = 7.3.1; 223 | }; 224 | A428E9D11DB38F91008DE27C = { 225 | CreatedOnToolsVersion = 7.3.1; 226 | TestTargetID = A428E9B81DB38F91008DE27C; 227 | }; 228 | }; 229 | }; 230 | buildConfigurationList = A428E9B41DB38F91008DE27C /* Build configuration list for PBXProject "QUIckControl" */; 231 | compatibilityVersion = "Xcode 3.2"; 232 | developmentRegion = English; 233 | hasScannedForEncodings = 0; 234 | knownRegions = ( 235 | en, 236 | Base, 237 | ); 238 | mainGroup = A428E9B01DB38F90008DE27C; 239 | productRefGroup = A428E9BA1DB38F91008DE27C /* Products */; 240 | projectDirPath = ""; 241 | projectRoot = ""; 242 | targets = ( 243 | A428E9B81DB38F91008DE27C /* QUIckControl */, 244 | A428E9D11DB38F91008DE27C /* QUIckControlTests */, 245 | ); 246 | }; 247 | /* End PBXProject section */ 248 | 249 | /* Begin PBXResourcesBuildPhase section */ 250 | A428E9B71DB38F91008DE27C /* Resources */ = { 251 | isa = PBXResourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | A4372D5E1DCE75400005FBBC /* LaunchScreen.storyboard in Resources */, 255 | A4372D551DCE74790005FBBC /* Assets.xcassets in Resources */, 256 | A4372D5F1DCE75400005FBBC /* Main.storyboard in Resources */, 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | }; 260 | A428E9D01DB38F91008DE27C /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | A4372D651DCE76ED0005FBBC /* Info.plist in Resources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXResourcesBuildPhase section */ 269 | 270 | /* Begin PBXSourcesBuildPhase section */ 271 | A428E9B51DB38F91008DE27C /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | A4372D451DCE73AE0005FBBC /* QUIckControlValue.m in Sources */, 276 | A4372D521DCE74150005FBBC /* AppDelegate.m in Sources */, 277 | A4372D441DCE73AE0005FBBC /* QUIckControlActionTargetImp.m in Sources */, 278 | A4372D531DCE74150005FBBC /* ViewController.m in Sources */, 279 | A4372D4A1DCE73E30005FBBC /* QUIckControl.m in Sources */, 280 | A4372D4D1DCE73F70005FBBC /* PinCodeControl.m in Sources */, 281 | A479130D1DCEBB830063D244 /* QUICStateDescriptorKey.m in Sources */, 282 | A4372D591DCE74AC0005FBBC /* main.m in Sources */, 283 | A4372D461DCE73AE0005FBBC /* QUIckControlValueTarget.m in Sources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | A428E9CE1DB38F91008DE27C /* Sources */ = { 288 | isa = PBXSourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | A4372D641DCE76E40005FBBC /* KDNControlTests.m in Sources */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXSourcesBuildPhase section */ 296 | 297 | /* Begin PBXTargetDependency section */ 298 | A428E9D41DB38F91008DE27C /* PBXTargetDependency */ = { 299 | isa = PBXTargetDependency; 300 | target = A428E9B81DB38F91008DE27C /* QUIckControl */; 301 | targetProxy = A428E9D31DB38F91008DE27C /* PBXContainerItemProxy */; 302 | }; 303 | /* End PBXTargetDependency section */ 304 | 305 | /* Begin PBXVariantGroup section */ 306 | A4372D5A1DCE75400005FBBC /* LaunchScreen.storyboard */ = { 307 | isa = PBXVariantGroup; 308 | children = ( 309 | A4372D5B1DCE75400005FBBC /* Base */, 310 | ); 311 | name = LaunchScreen.storyboard; 312 | sourceTree = ""; 313 | }; 314 | A4372D5C1DCE75400005FBBC /* Main.storyboard */ = { 315 | isa = PBXVariantGroup; 316 | children = ( 317 | A4372D5D1DCE75400005FBBC /* Base */, 318 | ); 319 | name = Main.storyboard; 320 | sourceTree = ""; 321 | }; 322 | /* End PBXVariantGroup section */ 323 | 324 | /* Begin XCBuildConfiguration section */ 325 | A428E9D91DB38F91008DE27C /* Debug */ = { 326 | isa = XCBuildConfiguration; 327 | buildSettings = { 328 | ALWAYS_SEARCH_USER_PATHS = NO; 329 | CLANG_ANALYZER_NONNULL = YES; 330 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 331 | CLANG_CXX_LIBRARY = "libc++"; 332 | CLANG_ENABLE_MODULES = YES; 333 | CLANG_ENABLE_OBJC_ARC = YES; 334 | CLANG_WARN_BOOL_CONVERSION = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 344 | COPY_PHASE_STRIP = NO; 345 | DEBUG_INFORMATION_FORMAT = dwarf; 346 | ENABLE_STRICT_OBJC_MSGSEND = YES; 347 | ENABLE_TESTABILITY = YES; 348 | GCC_C_LANGUAGE_STANDARD = gnu99; 349 | GCC_DYNAMIC_NO_PIC = NO; 350 | GCC_NO_COMMON_BLOCKS = YES; 351 | GCC_OPTIMIZATION_LEVEL = 0; 352 | GCC_PREPROCESSOR_DEFINITIONS = ( 353 | "DEBUG=1", 354 | "$(inherited)", 355 | ); 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 363 | MTL_ENABLE_DEBUG_INFO = YES; 364 | ONLY_ACTIVE_ARCH = YES; 365 | SDKROOT = iphoneos; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Debug; 369 | }; 370 | A428E9DA1DB38F91008DE27C /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ALWAYS_SEARCH_USER_PATHS = NO; 374 | CLANG_ANALYZER_NONNULL = YES; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 382 | CLANG_WARN_EMPTY_BODY = YES; 383 | CLANG_WARN_ENUM_CONVERSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | SDKROOT = iphoneos; 404 | TARGETED_DEVICE_FAMILY = "1,2"; 405 | VALIDATE_PRODUCT = YES; 406 | }; 407 | name = Release; 408 | }; 409 | A428E9DC1DB38F91008DE27C /* Debug */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | INFOPLIST_FILE = "$(SRCROOT)/QUIckControl/Info.plist"; 414 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 415 | PRODUCT_BUNDLE_IDENTIFIER = koden.QUIckControl; 416 | PRODUCT_NAME = QUIckControl; 417 | }; 418 | name = Debug; 419 | }; 420 | A428E9DD1DB38F91008DE27C /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 424 | INFOPLIST_FILE = "$(SRCROOT)/QUIckControl/Info.plist"; 425 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 426 | PRODUCT_BUNDLE_IDENTIFIER = koden.QUIckControl; 427 | PRODUCT_NAME = QUIckControl; 428 | }; 429 | name = Release; 430 | }; 431 | A428E9DF1DB38F91008DE27C /* Debug */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | BUNDLE_LOADER = "$(TEST_HOST)"; 435 | INFOPLIST_FILE = QUIckControlTests/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = koden.QUIckControlTests; 438 | PRODUCT_NAME = QUIckControl; 439 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QUIckControl.app/QUIckControl"; 440 | }; 441 | name = Debug; 442 | }; 443 | A428E9E01DB38F91008DE27C /* Release */ = { 444 | isa = XCBuildConfiguration; 445 | buildSettings = { 446 | BUNDLE_LOADER = "$(TEST_HOST)"; 447 | INFOPLIST_FILE = QUIckControlTests/Info.plist; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 449 | PRODUCT_BUNDLE_IDENTIFIER = koden.QUIckControlTests; 450 | PRODUCT_NAME = QUIckControl; 451 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QUIckControl.app/QUIckControl"; 452 | }; 453 | name = Release; 454 | }; 455 | /* End XCBuildConfiguration section */ 456 | 457 | /* Begin XCConfigurationList section */ 458 | A428E9B41DB38F91008DE27C /* Build configuration list for PBXProject "QUIckControl" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | A428E9D91DB38F91008DE27C /* Debug */, 462 | A428E9DA1DB38F91008DE27C /* Release */, 463 | ); 464 | defaultConfigurationIsVisible = 0; 465 | defaultConfigurationName = Release; 466 | }; 467 | A428E9DB1DB38F91008DE27C /* Build configuration list for PBXNativeTarget "QUIckControl" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | A428E9DC1DB38F91008DE27C /* Debug */, 471 | A428E9DD1DB38F91008DE27C /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Release; 475 | }; 476 | A428E9DE1DB38F91008DE27C /* Build configuration list for PBXNativeTarget "QUIckControlTests" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | A428E9DF1DB38F91008DE27C /* Debug */, 480 | A428E9E01DB38F91008DE27C /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | /* End XCConfigurationList section */ 486 | }; 487 | rootObject = A428E9B11DB38F90008DE27C /* Project object */; 488 | } 489 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl.xcodeproj/project.xcworkspace/xcuserdata/K-o-D-e-N.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/QUIckControl/fc16c900569b1df905803e8221c6d3fec0f6c830/QUIckControlObjC/QUIckControl.xcodeproj/project.xcworkspace/xcuserdata/K-o-D-e-N.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl.xcodeproj/xcuserdata/K-o-D-e-N.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl.xcodeproj/xcuserdata/K-o-D-e-N.xcuserdatad/xcschemes/QUIckControl.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl.xcodeproj/xcuserdata/K-o-D-e-N.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | QUIckControl.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | A428E9B81DB38F91008DE27C 16 | 17 | primary 18 | 19 | 20 | A428E9D11DB38F91008DE27C 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/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 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/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 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 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 | 100 | 101 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/PincodeControl/PinCodeControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // PincodeControl.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "QUIckControl.h" 10 | 11 | static const UIControlState PinCodeControlStateFilled = 1 << 16; 12 | static const UIControlState PinCodeControlStateInvalid = 1 << 17; 13 | static const UIControlEvents PinCodeControlEventTypeComplete = 1 << 24; 14 | 15 | @interface PinCodeControl : QUIckControl 16 | 17 | // current code string 18 | @property (nonatomic, readonly) NSString * code; 19 | 20 | // full code length, defined number elements 21 | @property (nonatomic, readonly) NSUInteger codeLength; 22 | 23 | // space between code items 24 | @property (nonatomic) IBInspectable CGFloat spaceBetweenItems; 25 | 26 | // size of side code item 27 | @property (nonatomic, readonly) IBInspectable CGFloat sideSize; 28 | 29 | // filled state, yes when code type ended. 30 | @property (nonatomic, readonly) BOOL filled; 31 | 32 | // valid state, yes if entered code is valid. 33 | @property (nonatomic, readonly) BOOL valid; 34 | 35 | @property (nonatomic, copy) BOOL(^validationBlock)(NSString * code); 36 | 37 | @property (nonatomic) BOOL shouldUseDefaultValidation; 38 | 39 | // color for fill code item when user input code symbol 40 | @property (nonatomic) UIColor * filledItemColor; 41 | 42 | // bezier path for code item 43 | @property (nonatomic) UIBezierPath * itemPath; 44 | 45 | -(instancetype)initWithCodeLength:(NSUInteger)codeLength sideSize:(CGFloat)sideSize; 46 | 47 | // border width of code item 48 | -(void)setBorderWidth:(CGFloat)borderWidth forState:(UIControlState)state; 49 | -(void)setBorderWidth:(CGFloat)borderWidth forIntersectedState:(UIControlState)state; 50 | 51 | // border color of code item 52 | -(void)setBorderColor:(UIColor*)borderColor forState:(UIControlState)state; 53 | -(void)setBorderColor:(UIColor*)borderColor forIntersectedState:(UIControlState)state; 54 | 55 | // fill color of code item 56 | -(void)setFillColor:(UIColor*)fillColor forState:(UIControlState)state; 57 | -(void)setFillColor:(UIColor*)fillColor forIntersectedState:(UIControlState)state; 58 | 59 | // clear all entered code 60 | -(void)clear; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/PincodeControl/PinCodeControl.m: -------------------------------------------------------------------------------- 1 | // 2 | // PincodeControl.m 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "PinCodeControl.h" 10 | 11 | @interface NSString (PincodeControl) 12 | -(void)enumerateCharacters:(void(^)(NSString* character, NSUInteger index, BOOL * stop))enumerator; 13 | @end 14 | 15 | @implementation NSString (PincodeControl) 16 | 17 | -(void)enumerateCharacters:(void(^)(NSString* character, NSUInteger index, BOOL * stop))enumerator { 18 | BOOL stop = NO; 19 | NSRange range = NSMakeRange(0, 1); 20 | for (range.location = 0; range.location < [self length]; ++range.location) { 21 | enumerator([self substringWithRange:range], range.location, &stop); 22 | if (stop) return; 23 | } 24 | } 25 | 26 | @end 27 | 28 | @interface ValueApplier : NSObject 29 | @property (nonatomic) id defaultValue; 30 | @property (nonatomic, weak) PinCodeControl * control; 31 | @end 32 | 33 | @interface PinCodeControl () 34 | @property (nonatomic, strong) ValueApplier * applier; 35 | @property (nonatomic, strong) NSMutableString * text; 36 | @property (nonatomic, readwrite) IBInspectable NSUInteger codeLength; 37 | @property (nonatomic) BOOL filled; 38 | @property (nonatomic) BOOL valid; 39 | @property (nonatomic, readonly) NSArray* sublayers; 40 | @property (nonatomic, strong) UIBezierPath * defaultPath; 41 | @end 42 | 43 | @implementation ValueApplier 44 | 45 | -(instancetype)initWithControl:(PinCodeControl*)control { 46 | if (self = [super init]) { 47 | _control = control; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | -(void)setValue:(id)value forKey:(NSString *)key { 54 | if (![key isEqual:keyPath(CAShapeLayer, fillColor)] || self.control.codeLength == self.control.code.length) { 55 | [self.control.sublayers setValue:value forKey:key]; 56 | return; 57 | } 58 | 59 | for (short i = 0; i < self.control.codeLength; ++i) { 60 | [self.control.sublayers[i] setValue:i < self.control.code.length ? (id)self.control.filledItemColor.CGColor : value 61 | forKey:key]; 62 | } 63 | } 64 | 65 | -(id)valueForKeyPath:(NSString *)keyPath { 66 | return [self.control.sublayers.lastObject valueForKeyPath:keyPath]; 67 | } 68 | 69 | @end 70 | 71 | @implementation PinCodeControl 72 | 73 | -(instancetype)initWithCoder:(NSCoder *)aDecoder { 74 | if (self = [super initWithCoder:aDecoder]) { 75 | [self initializeInstance]; 76 | } 77 | 78 | return self; 79 | } 80 | 81 | -(instancetype)initWithCodeLength:(NSUInteger)codeLength sideSize:(CGFloat)sideSize { 82 | if (self = [self initWithFrame:CGRectZero]) { 83 | _spaceBetweenItems = 15; 84 | self.sideSize = sideSize; 85 | self.codeLength = codeLength; 86 | } 87 | 88 | return self; 89 | } 90 | 91 | -(instancetype)initWithFrame:(CGRect)frame { 92 | if (self = [super initWithFrame:frame]) { 93 | [self initializeInstance]; 94 | } 95 | 96 | return self; 97 | } 98 | 99 | -(void)initializeInstance { 100 | _valid = YES; 101 | _shouldUseDefaultValidation = YES; 102 | self.text = [[NSMutableString alloc] init]; 103 | self.applier = [[ValueApplier alloc] initWithControl:self]; 104 | 105 | [self registerState:PinCodeControlStateFilled forBoolKeyPath:keyPath(PinCodeControl, filled) inverted:NO]; 106 | [self registerState:PinCodeControlStateInvalid forBoolKeyPath:keyPath(PinCodeControl, valid) inverted:YES]; 107 | } 108 | 109 | -(NSArray *)sublayers { 110 | return (NSArray*)self.layer.sublayers; 111 | } 112 | 113 | -(NSString *)code { 114 | return [self.text copy]; 115 | } 116 | 117 | -(void)setSideSize:(CGFloat)sideSize { 118 | _sideSize = sideSize; 119 | self.defaultPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.sideSize, self.sideSize)]; 120 | } 121 | 122 | -(void)setFilled:(BOOL)filled { 123 | if (_filled != filled) { 124 | _filled = filled; 125 | [self applyCurrentState]; 126 | if (filled) { 127 | [self sendActionsForControlEvents:PinCodeControlEventTypeComplete]; 128 | } 129 | } 130 | } 131 | 132 | -(void)setValid:(BOOL)valid { 133 | if (_valid != valid) { 134 | _valid = valid; 135 | [self applyCurrentState]; 136 | } 137 | } 138 | 139 | -(void)setCodeLength:(NSUInteger)codeLength { 140 | _codeLength = codeLength; 141 | [self loadSublayers]; 142 | [self loadDefaults]; 143 | } 144 | 145 | -(void)loadDefaults { 146 | self.filledItemColor = [UIColor grayColor]; 147 | UIColor * filledColor = [UIColor colorWithRed:76.0/255.0 green:145.0/255.0 blue:65.0/255.0 alpha:1]; 148 | UIColor * invalidColor = [UIColor colorWithRed:250.0/255.0 green:88.0/255.0 blue:87.0/255.0 alpha:1]; 149 | [self setBorderColor:[UIColor lightGrayColor] forState:UIControlStateNormal]; 150 | [self setBorderColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; 151 | // [self setFillColor:filledColor forState:PinCodeControlStateFilled]; 152 | // [self setBorderColor:filledColor forState:PinCodeControlStateFilled]; 153 | // [self setFillColor:filledColor forState:PinCodeControlStateFilled | UIControlStateHighlighted]; 154 | // [self setBorderColor:filledColor forState:PinCodeControlStateFilled | UIControlStateHighlighted]; 155 | // [self setFillColor:filledColor forIntersectedState:PinCodeControlStateFilled]; 156 | // [self setBorderColor:filledColor forIntersectedState:PinCodeControlStateFilled]; 157 | [self setFillColor:filledColor forInvertedState:PinCodeControlStateInvalid]; 158 | [self setBorderColor:filledColor forInvertedState:PinCodeControlStateInvalid]; 159 | [self setFillColor:nil forInvertedState:PinCodeControlStateFilled]; 160 | [self setBorderColor:nil forInvertedState:PinCodeControlStateFilled]; 161 | [self setFillColor:invalidColor forIntersectedState:PinCodeControlStateInvalid | PinCodeControlStateFilled]; 162 | [self setBorderColor:invalidColor forIntersectedState:PinCodeControlStateInvalid | PinCodeControlStateFilled]; 163 | [self applyCurrentState]; 164 | } 165 | 166 | -(void)loadSublayers { 167 | for (NSUInteger i = 0; i < self.codeLength; ++i) { 168 | CAShapeLayer * sublayer = [CAShapeLayer layer]; 169 | sublayer.actions = @{keyPath(CAShapeLayer, fillColor):[NSNull null], keyPath(CAShapeLayer, lineWidth):[NSNull null], 170 | keyPath(CAShapeLayer, strokeColor):[NSNull null]}; 171 | sublayer.lineWidth = 1; 172 | sublayer.strokeColor = [UIColor lightGrayColor].CGColor; 173 | sublayer.fillColor = [UIColor clearColor].CGColor; 174 | 175 | [self.layer addSublayer:sublayer]; 176 | } 177 | [self setNeedsLayout]; 178 | } 179 | 180 | -(void)layoutSubviews { 181 | [super layoutSubviews]; 182 | [self layoutCodeItemLayers]; 183 | } 184 | 185 | -(void)layoutCodeItemLayers { 186 | CGFloat fullWidth = (self.codeLength * (self.sideSize)) + (self.codeLength - 1) * self.spaceBetweenItems; 187 | CGFloat originX = CGRectGetMidX(self.bounds) - (fullWidth / 2); 188 | for (NSUInteger i = 0; i < self.layer.sublayers.count; ++i) { 189 | CAShapeLayer * sublayer = (CAShapeLayer*)self.layer.sublayers[i]; 190 | [sublayer setFrame:CGRectMake(originX + (i * (self.spaceBetweenItems + self.sideSize)), CGRectGetMidY(self.bounds) - self.sideSize / 2, self.sideSize, self.sideSize)]; 191 | sublayer.path = self.itemPath.CGPath ?: self.defaultPath.CGPath; 192 | } 193 | } 194 | 195 | -(void)clear { 196 | [self deleteCharactersInRange:NSMakeRange(0, self.text.length)]; 197 | [self performTransition:^{ 198 | self.filled = NO; 199 | self.valid = YES; 200 | }]; 201 | } 202 | 203 | #pragma mark - UIControl 204 | 205 | -(void)setBorderWidth:(CGFloat)borderWidth forInvertedState:(UIControlState)state { 206 | [self setValue:@(borderWidth) forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, lineWidth) forInvertedState:state]; 207 | } 208 | 209 | -(void)setBorderColor:(UIColor*)borderColor forInvertedState:(UIControlState)state { 210 | [self setValue:(id)borderColor.CGColor forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, strokeColor) forInvertedState:state]; 211 | } 212 | 213 | -(void)setFillColor:(UIColor*)fillColor forInvertedState:(UIControlState)state { 214 | [self setValue:(id)fillColor.CGColor forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, fillColor) forInvertedState:state]; 215 | } 216 | 217 | -(void)setBorderWidth:(CGFloat)borderWidth forIntersectedState:(UIControlState)state { 218 | [self setValue:@(borderWidth) forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, lineWidth) forAllStatesContained:state]; 219 | } 220 | 221 | -(void)setBorderColor:(UIColor*)borderColor forIntersectedState:(UIControlState)state { 222 | [self setValue:(id)borderColor.CGColor forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, strokeColor) forAllStatesContained:state]; 223 | } 224 | 225 | -(void)setFillColor:(UIColor*)fillColor forIntersectedState:(UIControlState)state { 226 | [self setValue:(id)fillColor.CGColor forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, fillColor) forAllStatesContained:state]; 227 | } 228 | 229 | -(void)setBorderWidth:(CGFloat)borderWidth forState:(UIControlState)state { 230 | [self setValue:@(borderWidth) forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, lineWidth) forState:state]; 231 | } 232 | 233 | -(void)setBorderColor:(UIColor*)borderColor forState:(UIControlState)state { 234 | [self setValue:(id)borderColor.CGColor forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, strokeColor) forState:state]; 235 | } 236 | 237 | -(void)setFillColor:(UIColor*)fillColor forState:(UIControlState)state { 238 | [self setValue:(id)fillColor.CGColor forTarget:self.applier forKeyPath:keyPath(CAShapeLayer, fillColor) forState:state]; 239 | } 240 | 241 | #pragma mark - UIResponder 242 | 243 | -(BOOL)canBecomeFirstResponder { 244 | return YES; 245 | } 246 | 247 | -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 248 | [self performTransition:^{ 249 | [super touchesEnded:touches withEvent:event]; 250 | [self becomeFirstResponder]; 251 | }]; 252 | } 253 | 254 | -(BOOL)becomeFirstResponder { 255 | self.highlighted = YES; 256 | return [super becomeFirstResponder]; 257 | } 258 | 259 | -(BOOL)resignFirstResponder { 260 | self.highlighted = NO; 261 | return [super resignFirstResponder]; 262 | } 263 | 264 | #pragma mark - UIKeyInput 265 | 266 | -(BOOL)hasText { 267 | return self.text.length > 0; 268 | } 269 | 270 | -(void)deleteBackward { 271 | if ([self hasText]) { 272 | if (self.text.length == self.codeLength) { 273 | [self beginTransition]; 274 | self.filled = NO; 275 | self.valid = YES; 276 | } 277 | [self deleteCharactersInRange:NSMakeRange(self.text.length - 1, 1)]; 278 | [self commitTransition]; 279 | } 280 | } 281 | 282 | -(void)deleteCharactersInRange:(NSRange)range { 283 | [self.text deleteCharactersInRange:range]; 284 | NSArray * elements = [self.layer.sublayers subarrayWithRange:range]; 285 | [elements setValue:[self valueForTarget:self.applier forKey:keyPath(CAShapeLayer, fillColor) forState:self.state] forKey:keyPath(CAShapeLayer, fillColor)]; 286 | } 287 | 288 | -(void)insertText:(NSString *)text { 289 | if (self.text.length < self.codeLength) { 290 | [self.sublayers[self.text.length] setFillColor:self.filledItemColor.CGColor]; 291 | [self.text appendString:text]; 292 | if (self.text.length == self.codeLength) { 293 | [self performTransition:^{ 294 | self.filled = YES; 295 | self.valid = [self validate:self.text]; 296 | }]; 297 | } 298 | } 299 | } 300 | 301 | -(UITextAutocorrectionType)autocorrectionType { 302 | return UITextAutocorrectionTypeNo; 303 | } 304 | 305 | -(UIKeyboardType)keyboardType { 306 | return UIKeyboardTypeNumberPad; 307 | } 308 | 309 | -(UITextAutocapitalizationType)autocapitalizationType { 310 | return UITextAutocapitalizationTypeNone; 311 | } 312 | 313 | #pragma mark - Validation 314 | 315 | -(BOOL)validate:(NSString*)pin { 316 | return (self.shouldUseDefaultValidation ? [self defaultValidation:pin] : YES) && 317 | (self.validationBlock ? self.validationBlock([pin copy]) : YES); 318 | } 319 | 320 | -(BOOL)defaultValidation:(NSString*)pin { 321 | __block BOOL isEqual = YES; 322 | __block BOOL isIncremented = YES; 323 | __block BOOL isDecremented = YES; 324 | [pin enumerateCharacters:^(NSString *character, NSUInteger index, BOOL *stop) { 325 | BOOL isLast = index == pin.length - 1; 326 | if (isLast) return; 327 | 328 | short number = character.intValue; 329 | short next = [pin substringWithRange:NSMakeRange(index + 1, 1)].intValue; 330 | isEqual = isEqual && number == next; 331 | isIncremented = isIncremented && (number + 1) == next; 332 | isDecremented = isDecremented && (number - 1) == next; 333 | }]; 334 | 335 | return !(isEqual || isIncremented || isDecremented); 336 | } 337 | 338 | @end 339 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUICStateDescriptorKey.h: -------------------------------------------------------------------------------- 1 | // 2 | // QUICStateDescriptorKey.h 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 06/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(short, QUICStateType) { 12 | QUICStateTypeUsual, 13 | QUICStateTypeInverted, 14 | QUICStateTypeIntersected 15 | }; 16 | 17 | typedef struct { 18 | UIControlState state; 19 | QUICStateType type; 20 | } QUICStateDescriptor; 21 | 22 | extern QUICStateDescriptor QUICStateDescriptorMake(UIControlState state, QUICStateType type); 23 | extern BOOL QUICStateEvaluateWithState(QUICStateDescriptor descriptor, UIControlState state); 24 | 25 | @interface QUICStateDescriptorKey : NSObject 26 | 27 | @property (nonatomic, readonly) QUICStateDescriptor descriptor; 28 | 29 | -(instancetype)initWithDescriptor:(QUICStateDescriptor)descriptor; 30 | 31 | -(BOOL)evaluateWithState:(UIControlState)state; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUICStateDescriptorKey.m: -------------------------------------------------------------------------------- 1 | // 2 | // QUICStateDescriptorKey.m 3 | // QUIckControl 4 | // 5 | // Created by Denis Koryttsev on 06/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "QUICStateDescriptorKey.h" 10 | 11 | QUICStateDescriptor QUICStateDescriptorMake(UIControlState state, QUICStateType type) { 12 | QUICStateDescriptor descr; descr.state = state; descr.type = type; 13 | return descr; 14 | } 15 | 16 | BOOL QUICStateEvaluateWithState(QUICStateDescriptor descriptor, UIControlState state) { 17 | switch (descriptor.type) { 18 | case QUICStateTypeUsual: 19 | return descriptor.state == state; 20 | case QUICStateTypeInverted: 21 | return state != UIControlStateNormal && (descriptor.state & state) != descriptor.state; 22 | case QUICStateTypeIntersected: 23 | return (descriptor.state & state) == descriptor.state; 24 | } 25 | } 26 | 27 | @implementation QUICStateDescriptorKey 28 | 29 | -(instancetype)initWithDescriptor:(QUICStateDescriptor)descriptor { 30 | if (self = [super init]) { 31 | _descriptor = descriptor; 32 | } 33 | 34 | return self; 35 | } 36 | 37 | -(id)copyWithZone:(NSZone *)zone { 38 | return self;//[[QUICStateDescriptorKey allocWithZone:zone] initWithDescriptor:self.descriptor]; 39 | } 40 | 41 | -(BOOL)isEqual:(id)object { 42 | if (self == object) return YES; 43 | if ([object isKindOfClass:[NSNumber class]]) { 44 | NSNumber * stateValue = object; 45 | return [self evaluateWithState:[stateValue unsignedIntegerValue]]; 46 | } 47 | if ([object isMemberOfClass:self.class]) { 48 | QUICStateDescriptorKey * stateValue = object; 49 | return self.descriptor.state == stateValue.descriptor.state && 50 | self.descriptor.type == stateValue.descriptor.type; 51 | } 52 | 53 | return NO; 54 | } 55 | 56 | -(BOOL)evaluateWithState:(UIControlState)state { 57 | switch (self.descriptor.type) { 58 | case QUICStateTypeUsual: 59 | return self.descriptor.state == state; 60 | case QUICStateTypeInverted: 61 | return state != UIControlStateNormal && (self.descriptor.state & state) != self.descriptor.state; 62 | case QUICStateTypeIntersected: 63 | return (self.descriptor.state & state) == self.descriptor.state; 64 | } 65 | } 66 | 67 | -(NSString *)description { 68 | return [NSString stringWithFormat:@"%@, %@", @(self.descriptor.state), @(self.descriptor.type)]; 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // KDNControl.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QUIckControlActionTarget.h" 11 | 12 | #define keyPath(class, key) ((class *)nil).key ? @#key : @#key 13 | 14 | @interface QUIckControl : UIControl 15 | 16 | -(void)beginTransition; // after call this method applyCurrentState method not effect 17 | -(void)commitTransition; // close transition process and apply changes. If call without beginTransition not effect - for update you should use applyCurrentState method. 18 | -(void)performTransition:(void(^)())transition; // block wrapper for beginTransition and commitTransition 19 | 20 | // in good case this methods should use only inside subclass 21 | -(void)setValue:(id)value forTarget:(id)target forKeyPath:(NSString *)key forInvertedState:(UIControlState)state; 22 | -(void)setValue:(id)value forTarget:(id)target forKeyPath:(NSString *)key forState:(UIControlState)state; 23 | -(void)setValue:(id)value forTarget:(id)target forKeyPath:(NSString *)key forAllStatesContained:(UIControlState)state; 24 | -(void)setValue:(id)value forKeyPath:(NSString *)key forState:(UIControlState)state; 25 | -(void)registerState:(UIControlState)state forBoolKeyPath:(NSString*)keyPath inverted:(BOOL)inverted; 26 | -(void)removeValuesForTarget:(id)target; 27 | 28 | -(void)applyCurrentState; 29 | -(void)applyCurrentStateForTarget:(id)target; 30 | 31 | -(id)valueForTarget:(id)target forKey:(NSString*)key forState:(UIControlState)state; 32 | 33 | -(QUIckControlActionTarget)addAction:(void(^)(__kindof __weak QUIckControl* control))action forControlEvents:(UIControlEvents)events; 34 | -(QUIckControlActionTarget)addActionTarget:(QUIckControlActionTarget)target; // TODO: Remove it, this bad implementation, need other solution 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControl.m: -------------------------------------------------------------------------------- 1 | // 2 | // KDNControl.m 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "QUIckControl.h" 10 | #import "QUIckControlPrivate.h" 11 | 12 | @interface QUIckControl () 13 | @property (nonatomic) BOOL isTransitionTime; 14 | @property (nonatomic, strong) QUIckControlValueTarget * thisValueTarget; 15 | @property (nonatomic, strong) NSMutableArray * states; 16 | @property (nonatomic, strong) NSMutableArray * targets; 17 | @property (nonatomic, strong) NSMutableSet * scheduledActions; 18 | @property (nonatomic, strong) NSMutableArray * actionTargets; 19 | @end 20 | 21 | @implementation QUIckControl 22 | 23 | -(instancetype)initWithCoder:(NSCoder *)aDecoder { 24 | if (self = [super initWithCoder:aDecoder]) { 25 | [self loadStorages]; 26 | [self registerExistedStates]; 27 | } 28 | 29 | return self; 30 | } 31 | 32 | -(instancetype)initWithFrame:(CGRect)frame { 33 | if (self = [super initWithFrame:frame]) { 34 | [self loadStorages]; 35 | [self registerExistedStates]; 36 | } 37 | 38 | return self; 39 | } 40 | 41 | -(void)registerExistedStates { 42 | [self registerState:UIControlStateSelected forBoolKeyPath:keyPath(UIControl, selected) inverted:NO]; 43 | [self registerState:UIControlStateHighlighted forBoolKeyPath:keyPath(UIControl, highlighted) inverted:NO]; 44 | [self registerState:UIControlStateDisabled forBoolKeyPath:keyPath(UIControl, enabled) inverted:YES]; 45 | } 46 | 47 | -(void)loadStorages { 48 | self.states = [NSMutableArray array]; 49 | self.thisValueTarget = [[QUIckControlValueTarget alloc] initWithTarget:self]; 50 | 51 | self.targets = [NSMutableArray array]; 52 | 53 | self.scheduledActions = [NSMutableSet set]; 54 | self.actionTargets = [NSMutableArray array]; 55 | } 56 | 57 | #pragma mark - States 58 | 59 | -(void)setSelected:(BOOL)selected { 60 | if (self.selected != selected) { 61 | [super setSelected:selected]; 62 | [self applyCurrentState]; 63 | } 64 | } 65 | 66 | -(void)setEnabled:(BOOL)enabled { 67 | if (self.enabled != enabled) { 68 | [super setEnabled:enabled]; 69 | [self applyCurrentState]; 70 | } 71 | } 72 | 73 | -(void)setHighlighted:(BOOL)highlighted { 74 | if (self.highlighted != highlighted) { 75 | [super setHighlighted:highlighted]; 76 | [self applyCurrentState]; 77 | } 78 | } 79 | 80 | -(void)beginTransition { 81 | self.isTransitionTime = YES; 82 | } 83 | 84 | -(void)commitTransition { 85 | if (!self.isTransitionTime) return; 86 | 87 | self.isTransitionTime = NO; 88 | [self applyCurrentState]; 89 | for (NSNumber * action in self.scheduledActions) { 90 | [self sendActionsForControlEvents:[action unsignedIntegerValue]]; 91 | } 92 | [self.scheduledActions removeAllObjects]; 93 | } 94 | 95 | -(void)performTransition:(void(^)())transition { 96 | [self beginTransition]; 97 | transition(); 98 | [self commitTransition]; 99 | } 100 | 101 | -(void)registerState:(UIControlState)state forBoolKeyPath:(NSString*)keyPath inverted:(BOOL)inverted { 102 | // & UIControlStateApplication ? 103 | QUICStateObject * stateObject = [QUICStateObject stateWithProperty:keyPath quickControlState:QUIckControlStateMake(state, inverted)]; 104 | [self.states addObject:stateObject]; 105 | } 106 | 107 | #pragma mark - Actions 108 | 109 | -(QUIckControlActionTarget)addAction:(void (^)(__kindof QUIckControl *__weak))action forControlEvents:(UIControlEvents)events { 110 | QUIckControlActionTargetImp * actionTarget = [[QUIckControlActionTargetImp alloc] initWithControl:self controlEvents:events]; 111 | actionTarget.action = action; 112 | [self.actionTargets addObject:actionTarget]; 113 | 114 | return actionTarget; 115 | } 116 | 117 | -(QUIckControlActionTarget)addActionTarget:(QUIckControlActionTarget)target { 118 | QUIckControlActionTargetImp * sourceTarget = (QUIckControlActionTargetImp*)target; 119 | QUIckControlActionTargetImp * actionTarget = [[QUIckControlActionTargetImp alloc] initWithControl:self controlEvents:sourceTarget.events]; 120 | actionTarget.action = sourceTarget.action; 121 | [self.actionTargets addObject:actionTarget]; 122 | 123 | return actionTarget; 124 | } 125 | 126 | -(void)sendActionsForControlEvents:(UIControlEvents)controlEvents { 127 | if (self.isTransitionTime) { [self.scheduledActions addObject:@(controlEvents)]; return; } 128 | 129 | [super sendActionsForControlEvents:controlEvents]; 130 | } 131 | 132 | #pragma mark - Values 133 | 134 | -(void)removeValuesForTarget:(id)target { 135 | NSUInteger targetIndex = [self indexOfTarget:target]; 136 | if (targetIndex != NSNotFound) { 137 | [self.targets removeObjectAtIndex:targetIndex]; 138 | } 139 | } 140 | 141 | -(void)setValue:(id)value forTarget:(id)target forKeyPath:(NSString *)key forInvertedState:(UIControlState)state { 142 | [self setValue:value forTarget:target forKeyPath:key forStateDescriptor:QUICStateDescriptorMake(state, QUICStateTypeInverted)]; 143 | } 144 | 145 | -(void)setValue:(id)value forTarget:(id)target forKeyPath:(NSString *)key forAllStatesContained:(UIControlState)state { 146 | [self setValue:value forTarget:target forKeyPath:key forStateDescriptor:QUICStateDescriptorMake(state, QUICStateTypeIntersected)]; 147 | } 148 | 149 | -(void)setValue:(id)value forKeyPath:(NSString *)key forState:(UIControlState)state { 150 | [self setValue:value forTarget:self forKeyPath:key forState:state]; 151 | } 152 | 153 | // TODO: Create possible add values for multiple states 154 | -(void)setValue:(id)value forTarget:(id)target forKeyPath:(NSString *)key forState:(UIControlState)state { 155 | [self setValue:value forTarget:target forKeyPath:key forStateDescriptor:QUICStateDescriptorMake(state, QUICStateTypeUsual)]; 156 | } 157 | 158 | -(void)setValue:(id)value forTarget:(id)target forKeyPath:(NSString *)key forStateDescriptor:(QUICStateDescriptor)descr { 159 | QUIckControlValueTarget * valueTarget = [self valueTargetForTarget:target]; 160 | [valueTarget setValue:value forKeyPath:key forStateDescriptor:descr]; 161 | if (QUICStateEvaluateWithState(descr, self.state)) { 162 | [valueTarget applyValue:value forKey:key]; 163 | } 164 | } 165 | 166 | -(NSUInteger)indexOfTarget:(id)target { 167 | return [self.targets indexOfObjectPassingTest:^BOOL(QUIckControlValueTarget * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 168 | if ([obj.target isEqual:target]) { 169 | *stop = YES; 170 | return YES; 171 | } 172 | return NO; 173 | }]; 174 | } 175 | 176 | -(QUIckControlValueTarget*)valueTargetForTarget:(id)target { 177 | if (self == target) return self.thisValueTarget; 178 | 179 | NSUInteger index = [self indexOfTarget:target]; 180 | if (index == NSNotFound) { 181 | QUIckControlValueTarget * valueTarget = [[QUIckControlValueTarget alloc] initWithTarget:target]; 182 | [self.targets addObject:valueTarget]; 183 | index = self.targets.count - 1; 184 | } 185 | 186 | return [self.targets objectAtIndex:index]; 187 | } 188 | 189 | #pragma mark - Apply state values 190 | 191 | -(void)applyCurrentStateForTarget:(id)target { 192 | [[self valueTargetForTarget:target] applyValuesForState:self.state]; 193 | } 194 | 195 | -(void)applyState:(UIControlState)state { 196 | if (self.isTransitionTime) return; 197 | 198 | [self setNeedsDisplay]; 199 | [self setNeedsLayout]; 200 | 201 | [self.thisValueTarget applyValuesForState:state]; 202 | for (QUIckControlValueTarget * target in self.targets) { 203 | [target applyValuesForState:state]; 204 | } 205 | } 206 | 207 | -(id)valueForTarget:(id)target forKey:(NSString*)key forState:(UIControlState)state { 208 | return [[self valueTargetForTarget:target] valueForKey:key forState:state]; 209 | } 210 | 211 | -(void)applyCurrentState { 212 | [self applyState:self.state]; 213 | } 214 | 215 | - (UIControlState)state { 216 | UIControlState state = UIControlStateNormal; 217 | 218 | for (QUICStateObject * stateValue in self.states) { 219 | if ([stateValue evaluateWithObject:self]) { 220 | state |= stateValue.state.controlState; 221 | } 222 | } 223 | 224 | return state; 225 | } 226 | 227 | -(void)dealloc { 228 | for (QUIckControlActionTarget target in self.actionTargets) { 229 | [target stop]; 230 | } 231 | [self.actionTargets removeAllObjects]; 232 | } 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControlActionTarget.h: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlActionTarget.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 03/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | @protocol QUIckControlActionTarget 10 | -(void)start; 11 | -(void)stop; 12 | @end 13 | typedef id QUIckControlActionTarget; 14 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControlActionTargetImp.h: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlActionTargetImp.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 03/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QUIckControlActionTarget.h" 11 | 12 | @class QUIckControl; 13 | 14 | @interface QUIckControlActionTargetImp : NSObject 15 | 16 | @property (nonatomic, weak) QUIckControl * parentControl; 17 | @property (nonatomic) UIControlEvents events; 18 | @property (nonatomic, copy) void(^action)(__weak QUIckControl* control); 19 | 20 | -(instancetype)initWithControl:(QUIckControl*)control controlEvents:(UIControlEvents)events; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControlActionTargetImp.m: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlActionTargetImp.m 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 03/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "QUIckControlActionTargetImp.h" 10 | #import "QUIckControl.h" 11 | 12 | @implementation QUIckControlActionTargetImp 13 | 14 | -(instancetype)initWithControl:(QUIckControl *)control controlEvents:(UIControlEvents)events { 15 | if (self = [super init]) { 16 | _events = events; 17 | _parentControl = control; 18 | } 19 | 20 | return self; 21 | } 22 | 23 | -(void)actionSelector:(QUIckControl*)control { 24 | if (self.action) self.action(self.parentControl); 25 | } 26 | 27 | -(void)start { 28 | [self.parentControl addTarget:self action:@selector(actionSelector:) forControlEvents:self.events]; 29 | } 30 | 31 | -(void)stop { 32 | [self.parentControl removeTarget:self action:@selector(actionSelector:) forControlEvents:self.events]; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControlPrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlPrivate.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 05/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "QUIckControlActionTargetImp.h" 10 | #import "QUIckControlValueTarget.h" 11 | #import "QUICStateDescriptorKey.h" 12 | 13 | typedef struct { 14 | UIControlState controlState; 15 | BOOL inverted; 16 | } QUIckControlState; 17 | 18 | QUIckControlState QUIckControlStateMake(UIControlState state, BOOL inverted) { 19 | QUIckControlState qstate; qstate.controlState = state; qstate.inverted = inverted; 20 | return qstate; 21 | } 22 | 23 | @interface QUICStateObject : NSObject 24 | 25 | @property (nonatomic, copy, readonly) NSString * boolProperty; 26 | @property (nonatomic, readonly) QUIckControlState state; 27 | 28 | +(QUICStateObject*)stateWithProperty:(NSString*)name quickControlState:(QUIckControlState)state; 29 | 30 | -(instancetype)initWithPropertyName:(NSString*)name state:(QUIckControlState)state; 31 | 32 | @end 33 | 34 | @implementation QUICStateObject 35 | 36 | +(QUICStateObject *)stateWithProperty:(NSString *)name quickControlState:(QUIckControlState)state { 37 | return [[QUICStateObject alloc] initWithPropertyName:name state:state]; 38 | } 39 | 40 | -(instancetype)initWithPropertyName:(NSString*)name state:(QUIckControlState)state { 41 | if (self = [super init]) { 42 | _boolProperty = name; 43 | _state = state; 44 | } 45 | 46 | return self; 47 | } 48 | 49 | -(BOOL)evaluateWithObject:(id)object { 50 | return [[object valueForKeyPath:self.boolProperty] boolValue] ^ self.state.inverted; 51 | } 52 | 53 | @end -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControlValue.h: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlValue.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 03/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QUICStateDescriptorKey.h" 11 | 12 | @class QUICStateDescriptorKey; 13 | 14 | @interface QUIckControlValue : NSObject 15 | 16 | -(instancetype)initWithKey:(NSString*)key; 17 | -(void)setValue:(id)value forInvertedState:(UIControlState)state; 18 | -(void)setValue:(id)value forState:(UIControlState)state; 19 | -(void)setValue:(id)value forIntersectedState:(UIControlState)state; 20 | 21 | -(void)setValue:(id)value forStateDescriptor:(QUICStateDescriptor)descriptor; 22 | -(id)valueForState:(UIControlState)state; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControlValue.m: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlValue.m 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 03/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "QUIckControlValue.h" 10 | 11 | @interface QUIckControlValue () 12 | @property (nonatomic, strong) NSString * key; 13 | @property (nonatomic, strong) NSMutableDictionary * values; 14 | @property (nonatomic, strong) NSMutableDictionary * intersectedValues; // TODO: Try apply some transform for state and save to values 15 | @property (nonatomic, strong) NSMutableIndexSet * intersectedStates; 16 | @property (nonatomic, strong) NSMutableIndexSet * invertedStates; 17 | @end 18 | 19 | @implementation QUIckControlValue 20 | 21 | // TODO: Init containers only when need - implement lazy initialization. 22 | -(instancetype)initWithKey:(NSString *)key { 23 | if (self = [super init]) { 24 | _key = key; 25 | _values = [NSMutableDictionary dictionary]; 26 | _intersectedValues = [NSMutableDictionary dictionary]; 27 | _intersectedStates = [NSMutableIndexSet indexSet]; 28 | _invertedStates = [NSMutableIndexSet indexSet]; 29 | } 30 | 31 | return self; 32 | } 33 | 34 | // TODO: state descriptor not used on full power, need remake select value. 35 | -(void)setValue:(id)value forStateDescriptor:(QUICStateDescriptor)descriptor { 36 | switch (descriptor.type) { 37 | case QUICStateTypeUsual: 38 | [self setValue:value forState:descriptor.state]; 39 | break; 40 | case QUICStateTypeInverted: 41 | [self setValue:value forInvertedState:descriptor.state]; 42 | break; 43 | case QUICStateTypeIntersected: 44 | [self setValue:value forIntersectedState:descriptor.state]; 45 | break; 46 | } 47 | } 48 | 49 | -(void)setValue:(id)value forInvertedState:(UIControlState)state { 50 | [self.invertedStates addIndex:state]; 51 | value ? [self.values setObject:value forKey:@(~state)] : [self.values removeObjectForKey:@(~state)]; 52 | } 53 | 54 | -(void)setValue:(id)value forIntersectedState:(UIControlState)state { 55 | [self.intersectedStates addIndex:state]; 56 | value ? [self.intersectedValues setObject:value forKey:@(state)] : [self.intersectedValues removeObjectForKey:@(state)]; 57 | } 58 | 59 | -(void)setValue:(id)value forState:(UIControlState)state { 60 | value ? [self.values setObject:value forKey:@(state)] : [self.values removeObjectForKey:@(state)]; 61 | } 62 | 63 | -(id)valueForState:(UIControlState)state { 64 | id value = [self.values objectForKey:@(state)]; 65 | 66 | if (!value) { 67 | NSUInteger invertedState = [self.invertedStates indexPassingTest:^BOOL(NSUInteger invertedState, BOOL * _Nonnull stop) { 68 | if ((state & invertedState) != invertedState) { // now not inverted, need rename 69 | *stop = YES; 70 | return YES; 71 | } 72 | return NO; 73 | }]; 74 | if (invertedState != NSNotFound) { 75 | value = [self.values objectForKey:@(~invertedState)]; 76 | } 77 | } 78 | 79 | if (!value) { 80 | NSUInteger intersectedState = [self.intersectedStates indexPassingTest:^BOOL(NSUInteger intersectedState, BOOL * _Nonnull stop) { 81 | if ((state & intersectedState) == intersectedState) { 82 | *stop = YES; 83 | return YES; 84 | } 85 | return NO; 86 | }]; 87 | if (intersectedState != NSNotFound) { 88 | value = [self.intersectedValues objectForKey:@(intersectedState)]; 89 | } 90 | } 91 | 92 | return value; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControlValueTarget.h: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlValueTarget.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 03/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QUICStateDescriptorKey.h" 11 | 12 | @interface QUIckControlValueTarget : NSObject 13 | 14 | @property (nonatomic, weak, readonly) id target; 15 | 16 | -(instancetype)initWithTarget:(id)target; 17 | 18 | -(void)setValue:(id)value forKeyPath:(NSString *)key forStateDescriptor:(QUICStateDescriptor)descriptor; 19 | 20 | -(void)applyValuesForState:(UIControlState)state; 21 | -(void)applyValue:(id)value forKey:(NSString*)key; 22 | 23 | -(id)valueForKey:(NSString*)key forState:(UIControlState)state; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/QUIckControl/QUIckControlValueTarget.m: -------------------------------------------------------------------------------- 1 | // 2 | // QUIckControlValueTarget.m 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 03/11/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "QUIckControlValueTarget.h" 10 | #import "QUIckControlValue.h" 11 | 12 | @interface QUIckControlValueTarget () 13 | @property (nonatomic, strong) NSMutableDictionary * values; 14 | @property (nonatomic, strong) NSMutableDictionary * defaults; 15 | @property (nonatomic, weak) id target; 16 | @end 17 | 18 | @implementation QUIckControlValueTarget 19 | 20 | -(instancetype)initWithTarget:(id)target { 21 | if (self = [super init]) { 22 | _target = target; 23 | _defaults = [NSMutableDictionary dictionary]; 24 | _values = [NSMutableDictionary dictionary]; 25 | } 26 | 27 | return self; 28 | } 29 | 30 | -(void)setValue:(id)value forKeyPath:(NSString *)key forStateDescriptor:(QUICStateDescriptor)descriptor { 31 | [[self keyValueForKey:key registerIfNeeded:value != nil] setValue:value forStateDescriptor:descriptor]; 32 | } 33 | 34 | -(QUIckControlValue*)keyValueForKey:(NSString*)key registerIfNeeded:(BOOL)needed { 35 | QUIckControlValue * keyValue = [self.values objectForKey:key]; 36 | if (!keyValue && needed) { 37 | keyValue = [self registerKey:key]; 38 | } 39 | 40 | return keyValue; 41 | } 42 | 43 | -(QUIckControlValue*)registerKey:(NSString*)key { 44 | QUIckControlValue * keyValue = [[QUIckControlValue alloc] initWithKey:key]; 45 | [self.values setObject:keyValue forKey:key]; 46 | id defaultValue = [self.target valueForKeyPath:key]; 47 | if (defaultValue) { 48 | [self.defaults setObject:defaultValue forKey:key]; 49 | } 50 | 51 | return keyValue; 52 | } 53 | 54 | -(void)applyValuesForState:(UIControlState)state { 55 | for (NSString * key in self.values) { 56 | [self.target setValue:[[self.values objectForKey:key] valueForState:state] ?: [self.defaults objectForKey:key] 57 | forKeyPath:key]; 58 | } 59 | } 60 | 61 | -(void)applyValue:(id)value forKey:(NSString*)key { 62 | [self.target setValue:value forKeyPath:key]; 63 | } 64 | 65 | // intersected states not corrected working if two intersected states mathed in current state and contained values for same key. 66 | -(id)valueForKey:(NSString*)key forState:(UIControlState)state { 67 | return [[self.values objectForKey:key] valueForState:state] ?: [self.defaults objectForKey:key]; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "PinCodeControl.h" 11 | 12 | @interface ExampleControl : QUIckControl 13 | @property (nonatomic) BOOL exampleState; 14 | @end 15 | 16 | static const UIControlState ExampleState = 1 << 16; 17 | 18 | @implementation ExampleControl 19 | 20 | -(instancetype)initWithFrame:(CGRect)frame { 21 | if (self = [super initWithFrame:frame]) { 22 | [self registerState:ExampleState forBoolKeyPath:keyPath(ExampleControl, exampleState) inverted:NO]; 23 | } 24 | 25 | return self; 26 | } 27 | 28 | -(void)setExampleState:(BOOL)exampleState { 29 | _exampleState = exampleState; 30 | [self applyCurrentState]; 31 | } 32 | 33 | @end 34 | 35 | static const UIControlState QUIckControlStateOpaque = 1 << 16; 36 | 37 | @interface ViewController () 38 | @property (weak, nonatomic) IBOutlet UIButton *actionButton; 39 | @property (weak, nonatomic) IBOutlet UILabel *dependedLabel; 40 | @property (weak, nonatomic) IBOutlet QUIckControl *control; 41 | @property (weak, nonatomic) IBOutlet ExampleControl *example; 42 | @property (weak, nonatomic) IBOutlet PinCodeControl *pincodeControl; 43 | @end 44 | 45 | @implementation ViewController 46 | 47 | - (void)viewDidLoad { 48 | [super viewDidLoad]; 49 | 50 | [self.control registerState:QUIckControlStateOpaque forBoolKeyPath:keyPath(UIView, opaque) inverted:YES]; 51 | [self.control setValue:[UIColor blackColor] forKeyPath:keyPath(UIView, backgroundColor) forState:QUIckControlStateOpaque]; 52 | [self.control setValue:@2 forKeyPath:keyPath(UIView, layer.borderWidth) forState:UIControlStateSelected]; 53 | [self.control setValue:[UIColor redColor] forKeyPath:keyPath(UIView, backgroundColor) forState:UIControlStateSelected]; 54 | [self.control setValue:[UIColor yellowColor] forKeyPath:keyPath(UIView, backgroundColor) forState:UIControlStateHighlighted | UIControlStateSelected]; 55 | [self.control setValue:[UIColor yellowColor] forKeyPath:keyPath(UIView, backgroundColor) forState:UIControlStateHighlighted]; 56 | [self.control addTarget:self action:@selector(controlTouchUpInside:) forControlEvents:UIControlEventTouchUpInside]; 57 | 58 | [self.control setValue:[UIColor grayColor] forTarget:self.example forKeyPath:keyPath(ExampleControl, backgroundColor) forState:UIControlStateSelected]; 59 | [self.control setValue:@5 forTarget:self.example forKeyPath:keyPath(ExampleControl, layer.borderWidth) forState:UIControlStateSelected]; 60 | 61 | [self.pincodeControl setValue:[UIColor colorWithWhite:112.0/255.0 alpha:.7] forTarget:self.dependedLabel forKeyPath:keyPath(UILabel, textColor) forState:UIControlStateNormal]; 62 | [self.pincodeControl setValue:[UIColor colorWithWhite:1 alpha:.7] forTarget:self.dependedLabel forKeyPath:keyPath(UILabel, textColor) forState:UIControlStateHighlighted]; 63 | [self.pincodeControl addTarget:self action:@selector(pincodeTypeComplete:) forControlEvents:PinCodeControlEventTypeComplete]; 64 | [[self.pincodeControl addAction:^(__kindof QUIckControl *__weak control) { 65 | NSLog(@"%@", control); 66 | } forControlEvents:UIControlEventTouchUpInside] start]; 67 | [self.pincodeControl setValue:[UIColor colorWithWhite:1 alpha:.3] forTarget:self.pincodeControl forKeyPath:keyPath(UIView, backgroundColor) forAllStatesContained:UIControlStateHighlighted]; 68 | [self.pincodeControl setValue:[self starShape:CGRectMake(0, 0, self.pincodeControl.sideSize, self.pincodeControl.sideSize)] forTarget:self.pincodeControl forKeyPath:keyPath(PinCodeControl, itemPath) forAllStatesContained:UIControlStateHighlighted]; 69 | [self.pincodeControl setValue:@5 forTarget:self.pincodeControl forKeyPath:keyPath(PinCodeControl, layer.cornerRadius) forInvertedState:PinCodeControlStateFilled]; 70 | [self.pincodeControl setValue:[UIColor brownColor] forTarget:self.pincodeControl forKeyPath:keyPath(PinCodeControl, backgroundColor) forInvertedState:UIControlStateHighlighted]; 71 | 72 | static const UIControlState PinCodeControlStateValid = 1 << 18; 73 | [self.pincodeControl registerState:PinCodeControlStateValid forBoolKeyPath:keyPath(PinCodeControl, valid) inverted:NO]; 74 | [self.pincodeControl setValue:@NO forTarget:self.actionButton forKeyPath:keyPath(UIButton, enabled) forInvertedState:PinCodeControlStateFilled | PinCodeControlStateValid]; 75 | [self.pincodeControl setValue:@YES forTarget:self.actionButton forKeyPath:keyPath(UIButton, enabled) forAllStatesContained:PinCodeControlStateFilled | PinCodeControlStateValid]; 76 | } 77 | 78 | -(UIBezierPath *)starShape:(CGRect)frame { 79 | UIBezierPath* bezierPath = [UIBezierPath bezierPath]; 80 | [bezierPath moveToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.05000 * CGRectGetHeight(frame))]; 81 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.67634 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.30729 * CGRectGetHeight(frame))]; 82 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.97553 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.39549 * CGRectGetHeight(frame))]; 83 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.78532 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.64271 * CGRectGetHeight(frame))]; 84 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.79389 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.95451 * CGRectGetHeight(frame))]; 85 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.85000 * CGRectGetHeight(frame))]; 86 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.20611 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.95451 * CGRectGetHeight(frame))]; 87 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.21468 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.64271 * CGRectGetHeight(frame))]; 88 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.02447 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.39549 * CGRectGetHeight(frame))]; 89 | [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.32366 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.30729 * CGRectGetHeight(frame))]; 90 | [bezierPath closePath]; 91 | 92 | return bezierPath; 93 | } 94 | 95 | -(void)pincodeTypeComplete:(PinCodeControl*)control { 96 | NSLog(@"%@", control.code); 97 | } 98 | 99 | -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 100 | [super touchesBegan:touches withEvent:event]; 101 | [self.view endEditing:NO]; 102 | } 103 | 104 | -(void)controlTouchUpInside:(QUIckControl*)control { 105 | [control setSelected:!control.isSelected]; 106 | self.example.exampleState = control.isSelected; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControl/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // KDNControl 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControlTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /QUIckControlObjC/QUIckControlTests/KDNControlTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // KDNControlTests.m 3 | // KDNControlTests 4 | // 5 | // Created by Denis Koryttsev on 16/10/16. 6 | // Copyright © 2016 Denis Koryttsev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QUIckControlPrivate.h" 11 | 12 | @interface QUIckControlTests : XCTestCase 13 | 14 | @end 15 | 16 | @interface KeyObject : NSObject 17 | 18 | @end 19 | 20 | @implementation KeyObject 21 | 22 | -(id)copyWithZone:(NSZone *)zone { 23 | return [[KeyObject allocWithZone:zone] init]; 24 | } 25 | 26 | -(BOOL)isEqual:(id)object { 27 | return YES; 28 | } 29 | 30 | @end 31 | 32 | @interface TestNSValue : NSValue 33 | @end 34 | @implementation TestNSValue 35 | @end 36 | 37 | @implementation QUIckControlTests 38 | 39 | - (void)setUp { 40 | [super setUp]; 41 | // Put setup code here. This method is called before the invocation of each test method in the class. 42 | } 43 | 44 | - (void)tearDown { 45 | // Put teardown code here. This method is called after the invocation of each test method in the class. 46 | [super tearDown]; 47 | } 48 | 49 | - (void)testExample { 50 | // This is an example of a functional test case. 51 | // Use XCTAssert and related functions to verify your tests produce the correct results. 52 | } 53 | 54 | - (void)testPerformanceExample { 55 | // This is an example of a performance test case. 56 | [self measureBlock:^{ 57 | // Put the code you want to measure the time of here. 58 | }]; 59 | } 60 | 61 | -(void)testInverted { 62 | BOOL inverted = YES; 63 | BOOL boolProperty = YES; 64 | XCTAssertTrue(boolProperty ^ inverted); 65 | } 66 | 67 | -(void)testNSValueSubclass { 68 | BOOL yes = YES; 69 | TestNSValue * value = [TestNSValue value:&yes withObjCType:@encode(BOOL)]; 70 | 71 | XCTAssertTrue([value isKindOfClass:[TestNSValue class]]); 72 | } 73 | 74 | -(void)testQUIckControlStateValueEqual { 75 | // NSValue * value = [NSValue valueWithQUIckControlState:QUIckControlStateMake(UIControlStateDisabled, YES)]; 76 | // 77 | // XCTAssertTrue([value isEqualToValue:[NSValue valueWithQUIckControlState:QUIckControlStateMake(UIControlStateDisabled, YES)]]); 78 | } 79 | 80 | -(void)testNSDictionaryEqual { 81 | NSDictionary * dictionary = @{[[KeyObject alloc] init]:@"value"}; 82 | 83 | XCTAssertTrue([[dictionary objectForKey:[[KeyObject alloc] init]] isEqual:@"value"]); 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QUIckControl 2 | Base class for quick implementation UIControl subclass based on standard(enabled, highlighted, selected) and custom states. 3 | Implementation based on KVC. 4 | 5 | [![Version](https://img.shields.io/cocoapods/v/QUIckControl.svg?style=flat)](http://cocoapods.org/pods/QUIckControl) 6 | [![License](https://img.shields.io/cocoapods/l/QUIckControl.svg?style=flat)](http://cocoapods.org/pods/QUIckControl) 7 | [![Platform](https://img.shields.io/cocoapods/p/QUIckControl.svg?style=flat)](http://cocoapods.org/pods/QUIckControl) 8 | 9 | ## Installation 10 | 11 | QUIckControl is available through [CocoaPods](http://cocoapods.org). To install 12 | it, simply add the following line to your Podfile: 13 | 14 | ```ruby 15 | pod "QUIckControl" 16 | ``` 17 | 18 |

Manage states:

19 | 20 | You may to bind value for specific target with state of types: 21 | - simple state as bitmask (UIControlState); 22 | - state, which contains in current state (.intersected); 23 | - all states, which not matched specified state (.inverted); 24 | - all states, where current state contains one or more specified substates (.oneOfSeveral); 25 | - all states, where current state not contains specified substates (.noneOfThis); 26 | - custom state, which you implemented (.custom); 27 | 28 | All state types have priority and value setup from most high priority state. 29 | In default implementation simple state have priority 1000, intersected 999, inverted 750, oneOfSeveral and noneOfThis 500, custom user defined. For any type of state descriptor you may set your priority. 30 | 31 | Example usage: 32 | 33 | In most cases, state looks like bool property or him may be represented in bool property. So, before setup value for state, it need register using: 34 | ```swift 35 | func register(_ state: UIControlState, forBoolKeyPath keyPath: String, inverted: Bool) 36 | func register(_ state: UIControlState, with predicate: NSPredicate) 37 | ``` 38 | example: 39 | ```swift 40 | register(.disabled, forBoolKeyPath: #keyPath(UIControl.enabled), inverted: true) 41 | register(.valid, with: NSPredicate { control, _ in 42 | return control.filled && control.valid 43 | }) 44 | ``` 45 | 46 | Immediately, after registration you may setup values for this state using: 47 | ```swift 48 | func setValue(_ value: Any?, forTarget: NSObject = default, forKeyPath: String, forInvertedState: UIControlState) { 49 | func setValue(_ value: Any?, forTarget: NSObject = default, forKeyPath: String, forAllStatesContained: UIControlState) 50 | func setValue(_ value: Any?, forTarget: NSObject = default, forKeyPath: String, for: UIControlState) 51 | func setValue(_ value: Any?, forTarget: NSObject = default, forKeyPath: String, for: QUICStateDescriptor) 52 | ``` 53 | examples: 54 | ```swift 55 | control.setValue(UIColor.black, forKeyPath: #keyPath(UIView.backgroundColor), forAllStatesContained: .highlighted) 56 | control.setValue("QuickControl sended this string", 57 | forTarget:receiver 58 | forKeyPath: #keyPath(StringReceiver.value), 59 | for: QUICStateDescriptor(state: [.filled, .invalid], priority: 1000, predicate: { $0.contains(.filled) && !$0.contains(.invalid) })) 60 | ``` 61 | Remove values: 62 | ```swift 63 | func removeValues(forTarget target: NSObject, forKeyPath key: String, forState state: UIControlState) 64 | func removeValues(forTarget target: NSObject, forKeyPath key: String) 65 | func removeValues(forTarget target: NSObject? = default) 66 | ``` 67 | 68 | For multiple switches state factors (aka bool property) use transitions: 69 | ```swift 70 | func beginTransition() 71 | func endTransition() // without apply current state 72 | func commitTransition() // with apply current state 73 | func performTransition(withCommit commit: Bool = default, transition: () -> Void) 74 | ``` 75 | 76 |

Other possibilities:

77 | 78 | You may subscribe on state and events using: 79 | ```swift 80 | func subscribe(on events: UIControlEvents, _ action: @escaping (QUIckControl) -> Void) -> QUIckControlActionTarget 81 | func subscribe(on state: QUICStateDescriptor, _ action: @escaping () -> ()) 82 | ``` 83 | example: 84 | ```swift 85 | control.subscribe(on: QUICStateDescriptor(intersected: .valid), { button.enabled = true }) 86 | ``` 87 | 88 | # PinCodeControl 89 | 90 |

91 | 92 |

93 | 94 | QUIckControl subclass, which is used for input PIN code. It uses programming states for change visual view. 95 | 96 | ```ruby 97 | pod "PinCodeControl" 98 | ``` 99 | 100 | Custom event and states: 101 | ```swift 102 | extension UIControlEvents { 103 | public static var typeComplete = UIControlEvents(rawValue: 1 << 24) 104 | } 105 | extension UIControlState { 106 | public static var filled = UIControlState(rawValue: 1 << 16) 107 | public static var invalid = UIControlState(rawValue: 1 << 17) 108 | public static var valid = UIControlState(rawValue: (1 << 18) | filled.rawValue) 109 | } 110 | // preset state descriptors 111 | enum States { 112 | static public let plain: QUICStateDescriptor 113 | static public let valid: QUICStateDescriptor 114 | static public let invalid: QUICStateDescriptor 115 | static public let highlighted: QUICStateDescriptor 116 | static public let disabled: QUICStateDescriptor 117 | } 118 | ``` 119 | 120 | Main API 121 | ```swift 122 | var code: String { get } // entered code 123 | var filled: Bool { get } // enabled, when all code entered 124 | var valid: Bool { get } // disabled, when entered code invalid 125 | var validator: BlockPredicate? // object for user validation pin code value 126 | var shouldUseDefaultValidation: Bool // if true, then code equal strings, such as '1111', '1234', '9876' will be defined as invalid values 127 | var filledItemColor: UIColor? // color for entered code element 128 | var itemPath: UIBezierPath? // bezier path for code element 129 | 130 | init(parameters: Parameters, frame: CGRect? = default) // main initializer 131 | func clear() // clear all entered code 132 | 133 | // methods for set parameters for each state 134 | func setFillColor(fillColor: UIColor?, for state: QUICStateDescriptor) 135 | func setBorderColor(borderColor: UIColor?, for state: QUICStateDescriptor) 136 | func setBorderWidth(borderWidth: CGFloat, for state: QUICStateDescriptor) 137 | func setForValidState(fillColor: UIColor?, borderColor: UIColor?, borderWidth: CGFloat) 138 | func setForInvalidState(fillColor: UIColor?, borderColor: UIColor?, borderWidth: CGFloat) 139 | func setForPlainState(fillColor: UIColor?, borderColor: UIColor?, borderWidth: CGFloat) 140 | func setForHighlightedState(fillColor: UIColor?, borderColor: UIColor?, borderWidth: CGFloat) 141 | func setForDisabledState(fillColor: UIColor?, borderColor: UIColor?, borderWidth: CGFloat) 142 | // validation 143 | func validate() -> Bool // perform validation current code value 144 | func validate(_ pin: String) -> Bool // method for validation entered pin code. Declared for subclasses override. 145 | ``` 146 | 147 |

Support information:

148 | Objective C version is not supported. 149 | 150 | For quick research pattern this implementation, you may see project: 151 | https://github.com/k-o-d-e-n/Statable 152 | 153 | ## Contributing 154 | 155 | [Read here](https://github.com/k-o-d-e-n/QUIckControl/blob/master/CONTRIBUTING.md) 156 | 157 | ## Author 158 | 159 | Denis Koryttsev, koden.u8800@gmail.com 160 | 161 | ## License 162 | 163 | QUIckControl is available under the MIT license. See the LICENSE file for more info. 164 | -------------------------------------------------------------------------------- /Resources/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k-o-d-e-n/QUIckControl/fc16c900569b1df905803e8221c6d3fec0f6c830/Resources/demo.gif --------------------------------------------------------------------------------