├── CHANGELOG ├── LICENSE ├── README.md ├── SCPinViewController.podspec ├── SCPinViewController.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── maxim.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── maxim.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── SCPinViewController.xcscheme │ └── xcschememanagement.plist ├── SCPinViewController ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SCPinViewController │ ├── Appearance │ │ ├── SCPinAppearance.h │ │ └── SCPinAppearance.m │ ├── SCPinViewController.h │ ├── SCPinViewController.m │ ├── SCPinViewController.xib │ ├── View │ │ ├── PinButton │ │ │ ├── SCPinButton.h │ │ │ └── SCPinButton.m │ │ └── PinView │ │ │ ├── SCPinView.h │ │ │ └── SCPinView.m │ └── images │ │ ├── sc_img_delete.png │ │ ├── sc_img_delete@2x.png │ │ ├── sc_img_delete@3x.png │ │ ├── sc_img_touchid.png │ │ ├── sc_img_touchid@2x.png │ │ └── sc_img_touchid@3x.png ├── Supporting Files │ └── main.m ├── ViewController.h └── ViewController.m └── Screen.png /CHANGELOG: -------------------------------------------------------------------------------- 1 | ### Version 1.0.0 2 | Initial version 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sugar and Candy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

SCPinViewController

2 | 3 |

4 | Platform 5 |

6 | 7 | Overview 8 | ------------- 9 | Super customization Pin controller. 10 | 11 | Feature 12 | ------------- 13 | - Touch ID 14 | - Autolayout 15 | - Cocoapods 16 | - Customization 17 | 18 | ### Installation with CocoaPods 19 | 20 | [CocoaPods](http://cocoapods.org) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries like SCPinViewController in your projects. See the ["Getting Started" guide for more information]. 21 | 22 | 23 | #### Podfile 24 | 25 | ```ruby 26 | platform :ios, '8.0' 27 | pod 'SCPinViewController' 28 | 29 | ``` 30 | 31 | 32 | ### Usage for validate 33 | 34 | ``` 35 | // Import the class 36 | #import 37 | 38 | ... 39 | 40 | - (void)showPinAction:(UIButton *)sender { 41 | SCPinViewController *vc = [[SCPinViewController alloc] initWithScope:SCPinViewControllerScopeValidate]; 42 | 43 | vc.dataSource = self; 44 | vc.validateDelegate = self; 45 | [self presentViewController:vc animated:YES completion:nil]; 46 | } 47 | 48 | -(NSString *)codeForPinViewController:(SCPinViewController *)pinViewController { 49 | return @"1234"; 50 | } 51 | 52 | 53 | -(void)pinViewControllerDidSetWrongPin:(SCPinViewController *)pinViewController { 54 | NSLog(@"Wrong") 55 | } 56 | 57 | -(void)pinViewControllerDidSetСorrectPin:(SCPinViewController *)pinViewController{ 58 | [self dismissViewControllerAnimated:YES completion:nil]; 59 | 60 | } 61 | 62 | ... 63 | 64 | ``` 65 | 66 | ### Usage for create 67 | 68 | ``` 69 | // Import the class 70 | #import 71 | 72 | ... 73 | 74 | - (void)showPinAction:(UIButton *)sender { 75 | SCPinViewController *vc = [[SCPinViewController alloc] initWithScope:SCPinViewControllerScopeCreate]; 76 | vc.createDelegate = self; 77 | [self presentViewController:vc animated:YES completion:nil]; 78 | } 79 | 80 | -(void)pinViewController:(SCPinViewController *)pinViewController didSetNewPin:(NSString *)pin { 81 | NSLog(@"pinViewController: %@",pinViewController); 82 | [[NSUserDefaults standardUserDefaults] setObject:pin forKey:kViewControllerPin]; 83 | [[NSUserDefaults standardUserDefaults] synchronize]; 84 | [self dismissViewControllerAnimated:YES completion:nil]; 85 | } 86 | 87 | ... 88 | 89 | ``` 90 | 91 | 92 | ### Apply custom appearance 93 | 94 | ``` 95 | // Import the class 96 | #import 97 | #import 98 | 99 | ... 100 | 101 | - (void)showPinAction:(UIButton *)sender { 102 | SCPinAppearance *appearance = [SCPinAppearance defaultAppearance]; 103 | 104 | appearance.titleText = @"Enter PIN"; 105 | appearance.numberButtonStrokeWitdh = 1.0f; 106 | appearance.pinSize = CGSizeMake(8.0f, 8.0f); 107 | [SCPinViewController setNewAppearance:appearance]; 108 | 109 | SCPinViewController *vc = [[SCPinViewController alloc] initWithScope:SCPinViewControllerScopeValidate]; 110 | 111 | vc.dataSource = self; 112 | vc.validateDelegate = self; 113 | [self presentViewController:vc animated:YES completion:nil]; 114 | } 115 | 116 | -(NSString *)codeForPinViewController:(SCPinViewController *)pinViewController { 117 | return @"1234"; 118 | } 119 | 120 | 121 | -(void)pinViewControllerDidSetWrongPin:(SCPinViewController *)pinViewController { 122 | NSLog(@"Wrong") 123 | } 124 | 125 | -(void)pinViewControllerDidSetСorrectPin:(SCPinViewController *)pinViewController{ 126 | [self dismissViewControllerAnimated:YES completion:nil]; 127 | 128 | } 129 | 130 | ... 131 | 132 | ``` 133 | 134 | 135 | ![DropAlert](https://github.com/SugarAndCandy/SCPinViewController/blob/master/Screen.png) 136 | 137 | 138 | License 139 | ------------------------------------------------------- 140 | The MIT License (MIT) 141 | 142 | Copyright (c) 2016 Sugar and Candy 143 | 144 | Permission is hereby granted, free of charge, to any person obtaining a copy 145 | of this software and associated documentation files (the "Software"), to deal 146 | in the Software without restriction, including without limitation the rights 147 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 148 | copies of the Software, and to permit persons to whom the Software is 149 | furnished to do so, subject to the following conditions: 150 | 151 | The above copyright notice and this permission notice shall be included in all 152 | copies or substantial portions of the Software. 153 | 154 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 155 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 156 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 157 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 158 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 159 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 160 | SOFTWARE. 161 | -------------------------------------------------------------------------------- /SCPinViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SCPinViewController' 3 | s.version = '1.0.2' 4 | s.license = 'MIT' 5 | s.summary = 'Super customization pin code validator' 6 | s.homepage = 'https://github.com/SugarAndCandy/SCPinViewController.git' 7 | s.social_media_url = 'https://twitter.com/sugarandcandyru' 8 | s.authors = { 'Sugar and Candy' => 'hi@sugarandcandy.ru' } 9 | s.source = { :git => 'https://github.com/SugarAndCandy/SCPinViewController.git', :tag => s.version, :submodules => true } 10 | s.requires_arc = true 11 | s.platform = :ios, "8.0" 12 | s.ios.deployment_target = "8.0" 13 | 14 | s.public_header_files = 'SCPinViewController/**/SC*.h' 15 | s.source_files = 'SCPinViewController/**/SC*.{m,h}' 16 | #s.resource_bundles = { 17 | # 'SCPinViewController' => ['SCPinViewController/**/*.{png}'] 18 | #} 19 | s.resources = ['SCPinViewController/**/*.{xib}','SCPinViewController/**/*.{png}'] 20 | 21 | end 22 | -------------------------------------------------------------------------------- /SCPinViewController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 942AA3871D396DE8008E39F9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 942AA3861D396DE8008E39F9 /* main.m */; }; 11 | 942AA38A1D396DE8008E39F9 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 942AA3891D396DE8008E39F9 /* AppDelegate.m */; }; 12 | 942AA38D1D396DE8008E39F9 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 942AA38C1D396DE8008E39F9 /* ViewController.m */; }; 13 | 942AA3901D396DE8008E39F9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 942AA38E1D396DE8008E39F9 /* Main.storyboard */; }; 14 | 942AA3921D396DE8008E39F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 942AA3911D396DE8008E39F9 /* Assets.xcassets */; }; 15 | 942AA3951D396DE8008E39F9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 942AA3931D396DE8008E39F9 /* LaunchScreen.storyboard */; }; 16 | 942AA3A01D396E1A008E39F9 /* SCPinViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 942AA39E1D396E1A008E39F9 /* SCPinViewController.m */; }; 17 | 942AA3A11D396E1A008E39F9 /* SCPinViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 942AA39F1D396E1A008E39F9 /* SCPinViewController.xib */; }; 18 | 942AA3A41D397141008E39F9 /* SCPinButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 942AA3A31D397141008E39F9 /* SCPinButton.m */; }; 19 | 9471BF4A1E046F8E00DE5C51 /* sc_img_delete.png in Resources */ = {isa = PBXBuildFile; fileRef = 9471BF441E046F8E00DE5C51 /* sc_img_delete.png */; }; 20 | 9471BF4B1E046F8E00DE5C51 /* sc_img_delete@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9471BF451E046F8E00DE5C51 /* sc_img_delete@2x.png */; }; 21 | 9471BF4C1E046F8E00DE5C51 /* sc_img_delete@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9471BF461E046F8E00DE5C51 /* sc_img_delete@3x.png */; }; 22 | 9471BF4D1E046F8E00DE5C51 /* sc_img_touchid.png in Resources */ = {isa = PBXBuildFile; fileRef = 9471BF471E046F8E00DE5C51 /* sc_img_touchid.png */; }; 23 | 9471BF4E1E046F8E00DE5C51 /* sc_img_touchid@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9471BF481E046F8E00DE5C51 /* sc_img_touchid@2x.png */; }; 24 | 9471BF4F1E046F8E00DE5C51 /* sc_img_touchid@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9471BF491E046F8E00DE5C51 /* sc_img_touchid@3x.png */; }; 25 | 94AF9D621D3A2BC800C9B7E0 /* SCPinView.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AF9D611D3A2BC800C9B7E0 /* SCPinView.m */; }; 26 | 94AF9D871D3A94A400C9B7E0 /* SCPinAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AF9D861D3A94A400C9B7E0 /* SCPinAppearance.m */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 942AA3821D396DE8008E39F9 /* SCPinViewController.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SCPinViewController.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 942AA3861D396DE8008E39F9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 32 | 942AA3881D396DE8008E39F9 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 33 | 942AA3891D396DE8008E39F9 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 34 | 942AA38B1D396DE8008E39F9 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 35 | 942AA38C1D396DE8008E39F9 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 36 | 942AA38F1D396DE8008E39F9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 37 | 942AA3911D396DE8008E39F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38 | 942AA3941D396DE8008E39F9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 39 | 942AA3961D396DE8008E39F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 942AA39D1D396E1A008E39F9 /* SCPinViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCPinViewController.h; sourceTree = ""; }; 41 | 942AA39E1D396E1A008E39F9 /* SCPinViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCPinViewController.m; sourceTree = ""; }; 42 | 942AA39F1D396E1A008E39F9 /* SCPinViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SCPinViewController.xib; sourceTree = ""; }; 43 | 942AA3A21D397141008E39F9 /* SCPinButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCPinButton.h; sourceTree = ""; }; 44 | 942AA3A31D397141008E39F9 /* SCPinButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCPinButton.m; sourceTree = ""; }; 45 | 9471BF441E046F8E00DE5C51 /* sc_img_delete.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sc_img_delete.png; sourceTree = ""; }; 46 | 9471BF451E046F8E00DE5C51 /* sc_img_delete@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "sc_img_delete@2x.png"; sourceTree = ""; }; 47 | 9471BF461E046F8E00DE5C51 /* sc_img_delete@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "sc_img_delete@3x.png"; sourceTree = ""; }; 48 | 9471BF471E046F8E00DE5C51 /* sc_img_touchid.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sc_img_touchid.png; sourceTree = ""; }; 49 | 9471BF481E046F8E00DE5C51 /* sc_img_touchid@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "sc_img_touchid@2x.png"; sourceTree = ""; }; 50 | 9471BF491E046F8E00DE5C51 /* sc_img_touchid@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "sc_img_touchid@3x.png"; sourceTree = ""; }; 51 | 94AF9D601D3A2BC800C9B7E0 /* SCPinView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCPinView.h; sourceTree = ""; }; 52 | 94AF9D611D3A2BC800C9B7E0 /* SCPinView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCPinView.m; sourceTree = ""; }; 53 | 94AF9D851D3A94A400C9B7E0 /* SCPinAppearance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCPinAppearance.h; sourceTree = ""; }; 54 | 94AF9D861D3A94A400C9B7E0 /* SCPinAppearance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCPinAppearance.m; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | 942AA37F1D396DE8008E39F9 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 942AA3791D396DE8008E39F9 = { 69 | isa = PBXGroup; 70 | children = ( 71 | 942AA3831D396DE8008E39F9 /* Products */, 72 | 942AA3841D396DE8008E39F9 /* SCPinViewController */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 942AA3831D396DE8008E39F9 /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 942AA3821D396DE8008E39F9 /* SCPinViewController.app */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 942AA3841D396DE8008E39F9 /* SCPinViewController */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 942AA39C1D396DFD008E39F9 /* SCPinViewController */, 88 | 942AA3851D396DE8008E39F9 /* Supporting Files */, 89 | 942AA3881D396DE8008E39F9 /* AppDelegate.h */, 90 | 942AA3891D396DE8008E39F9 /* AppDelegate.m */, 91 | 942AA3911D396DE8008E39F9 /* Assets.xcassets */, 92 | 942AA3961D396DE8008E39F9 /* Info.plist */, 93 | 942AA3931D396DE8008E39F9 /* LaunchScreen.storyboard */, 94 | 942AA38E1D396DE8008E39F9 /* Main.storyboard */, 95 | 942AA38B1D396DE8008E39F9 /* ViewController.h */, 96 | 942AA38C1D396DE8008E39F9 /* ViewController.m */, 97 | ); 98 | path = SCPinViewController; 99 | sourceTree = ""; 100 | }; 101 | 942AA3851D396DE8008E39F9 /* Supporting Files */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 942AA3861D396DE8008E39F9 /* main.m */, 105 | ); 106 | path = "Supporting Files"; 107 | sourceTree = ""; 108 | }; 109 | 942AA39C1D396DFD008E39F9 /* SCPinViewController */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 94AF9D841D3A948200C9B7E0 /* Appearance */, 113 | 94AF9D5E1D3A2BA100C9B7E0 /* View */, 114 | 944E832E1D3987AA004A565E /* images */, 115 | 942AA39D1D396E1A008E39F9 /* SCPinViewController.h */, 116 | 942AA39E1D396E1A008E39F9 /* SCPinViewController.m */, 117 | 942AA39F1D396E1A008E39F9 /* SCPinViewController.xib */, 118 | ); 119 | path = SCPinViewController; 120 | sourceTree = ""; 121 | }; 122 | 944E832E1D3987AA004A565E /* images */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 9471BF441E046F8E00DE5C51 /* sc_img_delete.png */, 126 | 9471BF451E046F8E00DE5C51 /* sc_img_delete@2x.png */, 127 | 9471BF461E046F8E00DE5C51 /* sc_img_delete@3x.png */, 128 | 9471BF471E046F8E00DE5C51 /* sc_img_touchid.png */, 129 | 9471BF481E046F8E00DE5C51 /* sc_img_touchid@2x.png */, 130 | 9471BF491E046F8E00DE5C51 /* sc_img_touchid@3x.png */, 131 | ); 132 | path = images; 133 | sourceTree = ""; 134 | }; 135 | 94AF9D5E1D3A2BA100C9B7E0 /* View */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 94AF9D5F1D3A2BB300C9B7E0 /* PinButton */, 139 | 94AF9D631D3A2BCB00C9B7E0 /* PinView */, 140 | ); 141 | path = View; 142 | sourceTree = ""; 143 | }; 144 | 94AF9D5F1D3A2BB300C9B7E0 /* PinButton */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 942AA3A21D397141008E39F9 /* SCPinButton.h */, 148 | 942AA3A31D397141008E39F9 /* SCPinButton.m */, 149 | ); 150 | path = PinButton; 151 | sourceTree = ""; 152 | }; 153 | 94AF9D631D3A2BCB00C9B7E0 /* PinView */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 94AF9D601D3A2BC800C9B7E0 /* SCPinView.h */, 157 | 94AF9D611D3A2BC800C9B7E0 /* SCPinView.m */, 158 | ); 159 | path = PinView; 160 | sourceTree = ""; 161 | }; 162 | 94AF9D841D3A948200C9B7E0 /* Appearance */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 94AF9D851D3A94A400C9B7E0 /* SCPinAppearance.h */, 166 | 94AF9D861D3A94A400C9B7E0 /* SCPinAppearance.m */, 167 | ); 168 | path = Appearance; 169 | sourceTree = ""; 170 | }; 171 | /* End PBXGroup section */ 172 | 173 | /* Begin PBXNativeTarget section */ 174 | 942AA3811D396DE8008E39F9 /* SCPinViewController */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = 942AA3991D396DE8008E39F9 /* Build configuration list for PBXNativeTarget "SCPinViewController" */; 177 | buildPhases = ( 178 | 942AA37E1D396DE8008E39F9 /* Sources */, 179 | 942AA37F1D396DE8008E39F9 /* Frameworks */, 180 | 942AA3801D396DE8008E39F9 /* Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | ); 186 | name = SCPinViewController; 187 | productName = SCPinViewController; 188 | productReference = 942AA3821D396DE8008E39F9 /* SCPinViewController.app */; 189 | productType = "com.apple.product-type.application"; 190 | }; 191 | /* End PBXNativeTarget section */ 192 | 193 | /* Begin PBXProject section */ 194 | 942AA37A1D396DE8008E39F9 /* Project object */ = { 195 | isa = PBXProject; 196 | attributes = { 197 | LastUpgradeCheck = 0730; 198 | ORGANIZATIONNAME = "Sugar and Candy"; 199 | TargetAttributes = { 200 | 942AA3811D396DE8008E39F9 = { 201 | CreatedOnToolsVersion = 7.3.1; 202 | DevelopmentTeam = 6PHPGAAG6M; 203 | ProvisioningStyle = Manual; 204 | }; 205 | }; 206 | }; 207 | buildConfigurationList = 942AA37D1D396DE8008E39F9 /* Build configuration list for PBXProject "SCPinViewController" */; 208 | compatibilityVersion = "Xcode 3.2"; 209 | developmentRegion = English; 210 | hasScannedForEncodings = 0; 211 | knownRegions = ( 212 | en, 213 | Base, 214 | ); 215 | mainGroup = 942AA3791D396DE8008E39F9; 216 | productRefGroup = 942AA3831D396DE8008E39F9 /* Products */; 217 | projectDirPath = ""; 218 | projectRoot = ""; 219 | targets = ( 220 | 942AA3811D396DE8008E39F9 /* SCPinViewController */, 221 | ); 222 | }; 223 | /* End PBXProject section */ 224 | 225 | /* Begin PBXResourcesBuildPhase section */ 226 | 942AA3801D396DE8008E39F9 /* Resources */ = { 227 | isa = PBXResourcesBuildPhase; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | 9471BF4A1E046F8E00DE5C51 /* sc_img_delete.png in Resources */, 231 | 9471BF4D1E046F8E00DE5C51 /* sc_img_touchid.png in Resources */, 232 | 942AA3A11D396E1A008E39F9 /* SCPinViewController.xib in Resources */, 233 | 9471BF4E1E046F8E00DE5C51 /* sc_img_touchid@2x.png in Resources */, 234 | 942AA3951D396DE8008E39F9 /* LaunchScreen.storyboard in Resources */, 235 | 942AA3921D396DE8008E39F9 /* Assets.xcassets in Resources */, 236 | 9471BF4B1E046F8E00DE5C51 /* sc_img_delete@2x.png in Resources */, 237 | 9471BF4F1E046F8E00DE5C51 /* sc_img_touchid@3x.png in Resources */, 238 | 9471BF4C1E046F8E00DE5C51 /* sc_img_delete@3x.png in Resources */, 239 | 942AA3901D396DE8008E39F9 /* Main.storyboard in Resources */, 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | /* End PBXResourcesBuildPhase section */ 244 | 245 | /* Begin PBXSourcesBuildPhase section */ 246 | 942AA37E1D396DE8008E39F9 /* Sources */ = { 247 | isa = PBXSourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | 942AA3A01D396E1A008E39F9 /* SCPinViewController.m in Sources */, 251 | 942AA38D1D396DE8008E39F9 /* ViewController.m in Sources */, 252 | 942AA38A1D396DE8008E39F9 /* AppDelegate.m in Sources */, 253 | 94AF9D871D3A94A400C9B7E0 /* SCPinAppearance.m in Sources */, 254 | 942AA3A41D397141008E39F9 /* SCPinButton.m in Sources */, 255 | 94AF9D621D3A2BC800C9B7E0 /* SCPinView.m in Sources */, 256 | 942AA3871D396DE8008E39F9 /* main.m in Sources */, 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | }; 260 | /* End PBXSourcesBuildPhase section */ 261 | 262 | /* Begin PBXVariantGroup section */ 263 | 942AA38E1D396DE8008E39F9 /* Main.storyboard */ = { 264 | isa = PBXVariantGroup; 265 | children = ( 266 | 942AA38F1D396DE8008E39F9 /* Base */, 267 | ); 268 | name = Main.storyboard; 269 | path = .; 270 | sourceTree = ""; 271 | }; 272 | 942AA3931D396DE8008E39F9 /* LaunchScreen.storyboard */ = { 273 | isa = PBXVariantGroup; 274 | children = ( 275 | 942AA3941D396DE8008E39F9 /* Base */, 276 | ); 277 | name = LaunchScreen.storyboard; 278 | path = .; 279 | sourceTree = ""; 280 | }; 281 | /* End PBXVariantGroup section */ 282 | 283 | /* Begin XCBuildConfiguration section */ 284 | 942AA3971D396DE8008E39F9 /* Debug */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | ALWAYS_SEARCH_USER_PATHS = NO; 288 | CLANG_ANALYZER_NONNULL = YES; 289 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 290 | CLANG_CXX_LIBRARY = "libc++"; 291 | CLANG_ENABLE_MODULES = YES; 292 | CLANG_ENABLE_OBJC_ARC = YES; 293 | CLANG_WARN_BOOL_CONVERSION = YES; 294 | CLANG_WARN_CONSTANT_CONVERSION = YES; 295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INT_CONVERSION = YES; 299 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 300 | CLANG_WARN_UNREACHABLE_CODE = YES; 301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 302 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 303 | COPY_PHASE_STRIP = NO; 304 | DEBUG_INFORMATION_FORMAT = dwarf; 305 | ENABLE_STRICT_OBJC_MSGSEND = YES; 306 | ENABLE_TESTABILITY = YES; 307 | GCC_C_LANGUAGE_STANDARD = gnu99; 308 | GCC_DYNAMIC_NO_PIC = NO; 309 | GCC_NO_COMMON_BLOCKS = YES; 310 | GCC_OPTIMIZATION_LEVEL = 0; 311 | GCC_PREPROCESSOR_DEFINITIONS = ( 312 | "DEBUG=1", 313 | "$(inherited)", 314 | ); 315 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 316 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 317 | GCC_WARN_UNDECLARED_SELECTOR = YES; 318 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 319 | GCC_WARN_UNUSED_FUNCTION = YES; 320 | GCC_WARN_UNUSED_VARIABLE = YES; 321 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 322 | MTL_ENABLE_DEBUG_INFO = YES; 323 | ONLY_ACTIVE_ARCH = YES; 324 | SDKROOT = iphoneos; 325 | }; 326 | name = Debug; 327 | }; 328 | 942AA3981D396DE8008E39F9 /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_ANALYZER_NONNULL = YES; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_CONSTANT_CONVERSION = YES; 339 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 340 | CLANG_WARN_EMPTY_BODY = YES; 341 | CLANG_WARN_ENUM_CONVERSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 347 | COPY_PHASE_STRIP = NO; 348 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 349 | ENABLE_NS_ASSERTIONS = NO; 350 | ENABLE_STRICT_OBJC_MSGSEND = YES; 351 | GCC_C_LANGUAGE_STANDARD = gnu99; 352 | GCC_NO_COMMON_BLOCKS = YES; 353 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 355 | GCC_WARN_UNDECLARED_SELECTOR = YES; 356 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 360 | MTL_ENABLE_DEBUG_INFO = NO; 361 | SDKROOT = iphoneos; 362 | VALIDATE_PRODUCT = YES; 363 | }; 364 | name = Release; 365 | }; 366 | 942AA39A1D396DE8008E39F9 /* Debug */ = { 367 | isa = XCBuildConfiguration; 368 | buildSettings = { 369 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 370 | CODE_SIGN_IDENTITY = "iPhone Developer"; 371 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 372 | DEVELOPMENT_TEAM = 6PHPGAAG6M; 373 | INFOPLIST_FILE = SCPinViewController/Info.plist; 374 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 375 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 376 | PRODUCT_BUNDLE_IDENTIFIER = ru.sugarandcandy.SCPinViewController; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | PROVISIONING_PROFILE = "31637879-d753-41a1-a98d-dfbebd0f3f2a"; 379 | PROVISIONING_PROFILE_SPECIFIER = "iOS Development"; 380 | }; 381 | name = Debug; 382 | }; 383 | 942AA39B1D396DE8008E39F9 /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | CODE_SIGN_IDENTITY = "iPhone Developer"; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; 389 | DEVELOPMENT_TEAM = 6PHPGAAG6M; 390 | INFOPLIST_FILE = SCPinViewController/Info.plist; 391 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 392 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 393 | PRODUCT_BUNDLE_IDENTIFIER = ru.sugarandcandy.SCPinViewController; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | PROVISIONING_PROFILE = "af0a1a07-375d-405c-a8e5-010f0719edb8"; 396 | PROVISIONING_PROFILE_SPECIFIER = "XC Ad Hoc: *"; 397 | }; 398 | name = Release; 399 | }; 400 | /* End XCBuildConfiguration section */ 401 | 402 | /* Begin XCConfigurationList section */ 403 | 942AA37D1D396DE8008E39F9 /* Build configuration list for PBXProject "SCPinViewController" */ = { 404 | isa = XCConfigurationList; 405 | buildConfigurations = ( 406 | 942AA3971D396DE8008E39F9 /* Debug */, 407 | 942AA3981D396DE8008E39F9 /* Release */, 408 | ); 409 | defaultConfigurationIsVisible = 0; 410 | defaultConfigurationName = Release; 411 | }; 412 | 942AA3991D396DE8008E39F9 /* Build configuration list for PBXNativeTarget "SCPinViewController" */ = { 413 | isa = XCConfigurationList; 414 | buildConfigurations = ( 415 | 942AA39A1D396DE8008E39F9 /* Debug */, 416 | 942AA39B1D396DE8008E39F9 /* Release */, 417 | ); 418 | defaultConfigurationIsVisible = 0; 419 | defaultConfigurationName = Release; 420 | }; 421 | /* End XCConfigurationList section */ 422 | }; 423 | rootObject = 942AA37A1D396DE8008E39F9 /* Project object */; 424 | } 425 | -------------------------------------------------------------------------------- /SCPinViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SCPinViewController.xcodeproj/project.xcworkspace/xcuserdata/maxim.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarAndCandy/SCPinViewController/0a9ff6bdc6917961a9f3347bf27f6249be73e696/SCPinViewController.xcodeproj/project.xcworkspace/xcuserdata/maxim.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SCPinViewController.xcodeproj/xcuserdata/maxim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /SCPinViewController.xcodeproj/xcuserdata/maxim.xcuserdatad/xcschemes/SCPinViewController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SCPinViewController.xcodeproj/xcuserdata/maxim.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SCPinViewController.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 942AA3811D396DE8008E39F9 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SCPinViewController/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. 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 | -------------------------------------------------------------------------------- /SCPinViewController/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. 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 | -------------------------------------------------------------------------------- /SCPinViewController/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /SCPinViewController/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 | -------------------------------------------------------------------------------- /SCPinViewController/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /SCPinViewController/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 | 40 | 41 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/Appearance/SCPinAppearance.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCPinAppearance.h 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 16.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCPinAppearance : NSObject 12 | + (instancetype)defaultAppearance; 13 | 14 | @property (nonatomic, strong) UIColor *numberButtonColor; 15 | @property (nonatomic, strong) UIColor *numberButtonTitleColor; 16 | @property (nonatomic, strong) UIColor *numberButtonStrokeColor; 17 | @property (nonatomic, assign) CGFloat numberButtonStrokeWitdh; 18 | @property (nonatomic, assign) BOOL numberButtonstrokeEnabled; 19 | @property (nonatomic, strong) UIFont *numberButtonFont; 20 | 21 | @property (nonatomic, strong) UIColor *deleteButtonColor; 22 | 23 | @property (nonatomic, strong) UIColor *pinFillColor; 24 | @property (nonatomic, strong) UIColor *pinHighlightedColor; 25 | @property (nonatomic, strong) UIColor *pinStrokeColor; 26 | @property (nonatomic, assign) CGFloat pinStrokeWidth; 27 | @property (nonatomic, assign) CGSize pinSize; 28 | 29 | @property (nonatomic, assign) BOOL touchIDButtonEnabled; 30 | @property (nonatomic, strong) UIColor *touchIDButtonColor; 31 | 32 | @property (nonatomic, strong) NSString *titleText; 33 | @property (nonatomic, strong) UIColor *titleTextColor; 34 | @property (nonatomic, strong) UIFont *titleTextFont; 35 | 36 | @property (nonatomic, strong) NSString *confirmText; 37 | 38 | @property (nonatomic, strong) NSString *supportText; 39 | @property (nonatomic, strong) UIColor *supportTextColor; 40 | @property (nonatomic, strong) UIFont *supportTextFont; 41 | 42 | @property (nonatomic, strong) NSString *touchIDText; 43 | @property (nonatomic, strong) NSString *touchIDVerification; 44 | 45 | @property (nonatomic, assign) BOOL cancelButtonEnabled; 46 | @property (nonatomic, strong) NSString *cancelButtonText; 47 | @property (nonatomic, strong) UIColor *cancelButtonTextColor; 48 | @property (nonatomic, strong) UIFont *cancelButtonTextFont; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/Appearance/SCPinAppearance.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCPinAppearance.m 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 16.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import "SCPinAppearance.h" 10 | 11 | @implementation SCPinAppearance 12 | 13 | 14 | + (instancetype)defaultAppearance { 15 | SCPinAppearance *defaultAppearance = [[SCPinAppearance alloc]init]; 16 | return defaultAppearance; 17 | } 18 | 19 | - (instancetype)init { 20 | self = [super init]; 21 | if (self) { 22 | [self setupDefaultAppearance]; 23 | } 24 | return self; 25 | } 26 | 27 | -(void)setupDefaultAppearance { 28 | UIColor *defaultColor = [UIColor colorWithRed:46.0f / 255.0f green:192.0f / 255.0f blue:197.0f / 255.0f alpha:1]; 29 | UIFont *defaultFont = [UIFont fontWithName:@"HelveticaNeue-Thin" size:27.0f]; 30 | 31 | self.numberButtonColor = defaultColor; 32 | self.numberButtonTitleColor = [UIColor blackColor]; 33 | self.numberButtonStrokeColor = defaultColor; 34 | self.numberButtonStrokeWitdh = 0.8f; 35 | self.numberButtonstrokeEnabled = YES; 36 | self.numberButtonFont = defaultFont; 37 | 38 | self.deleteButtonColor = defaultColor; 39 | 40 | self.pinFillColor = [UIColor clearColor]; 41 | self.pinHighlightedColor = defaultColor; 42 | self.pinStrokeColor = defaultColor; 43 | self.pinStrokeWidth = 0.8f; 44 | self.pinSize = CGSizeMake(14.0f, 14.0f); 45 | 46 | self.touchIDButtonEnabled = YES; 47 | self.touchIDButtonColor = defaultColor; 48 | 49 | self.titleText = @"Enter Pin"; 50 | self.titleTextFont = defaultFont; 51 | self.titleTextColor = defaultColor; 52 | 53 | self.confirmText = @"Confirm Pin"; 54 | 55 | self.supportText = nil; 56 | self.supportTextFont = defaultFont; 57 | self.supportTextColor = defaultColor; 58 | 59 | self.touchIDText = @"Put finger"; 60 | self.touchIDVerification = @"Pincode TouchID"; 61 | 62 | self.cancelButtonEnabled = YES; 63 | self.cancelButtonText = @"Cancel"; 64 | self.cancelButtonTextFont = [UIFont fontWithName:@"HelveticaNeue-Thin" size:16.0f];; 65 | self.cancelButtonTextColor = defaultColor; 66 | } 67 | 68 | 69 | /* 70 | #pragma mark - Setters 71 | 72 | -(void)setNumberButtonColor:(UIColor *)numberButtonColor { 73 | _numberButtonColor = numberButtonColor; 74 | for (SCPinButton *button in self.numberButtons) { 75 | [button setTintColor:numberButtonColor]; 76 | } 77 | } 78 | 79 | -(void)setNumberButtonTitleColor:(UIColor *)numberButtonTitleColor { 80 | _numberButtonTitleColor = numberButtonTitleColor; 81 | for (SCPinButton *button in self.numberButtons) { 82 | [button setTitleColor:numberButtonTitleColor forState:UIControlStateNormal]; 83 | [button setTitleColor:numberButtonTitleColor forState:UIControlStateSelected]; 84 | [button setTitleColor:numberButtonTitleColor forState:UIControlStateHighlighted]; 85 | } 86 | } 87 | 88 | -(void)setNumberButtonStrokeColor:(UIColor *)numberButtonStrokeColor { 89 | _numberButtonStrokeColor = numberButtonStrokeColor; 90 | for (SCPinButton *button in self.numberButtons) { 91 | button.strokeColor = numberButtonStrokeColor; 92 | } 93 | } 94 | 95 | -(void)setNumberButtonStrokeWitdh:(CGFloat)numberButtonStrokeWitdh { 96 | _numberButtonStrokeWitdh = numberButtonStrokeWitdh; 97 | for (SCPinButton *button in self.numberButtons) { 98 | button.strokeWidth = numberButtonStrokeWitdh; 99 | } 100 | } 101 | 102 | -(void)setNumberButtonstrokeEnabled:(BOOL)numberButtonstrokeEnabled { 103 | _numberButtonstrokeEnabled = numberButtonstrokeEnabled; 104 | for (SCPinButton *button in self.numberButtons) { 105 | button.strokeColor = numberButtonstrokeEnabled ? [self numberButtonStrokeColor] : [UIColor clearColor]; 106 | } 107 | } 108 | 109 | -(void)setNumberButtonFont:(UIFont *)numberButtonFont { 110 | _numberButtonFont = numberButtonFont; 111 | for (SCPinButton *button in self.numberButtons) { 112 | [button.titleLabel setFont:numberButtonFont]; 113 | [button setNeedsDisplay]; 114 | } 115 | } 116 | 117 | -(void)setDeleteButtonColor:(UIColor *)deleteButtonColor { 118 | _deleteButtonColor = deleteButtonColor; 119 | [self.deleteButton setTintColor:deleteButtonColor]; 120 | //[self.deleteButton.titleLabel setTintColor:deleteButtonColor]; 121 | 122 | } 123 | 124 | -(void)setDeleteButtonText:(NSString *)deleteButtonText { 125 | _deleteButtonText = deleteButtonText; 126 | //[self.deleteButton setTitle:deleteButtonText forState:UIControlStateNormal]; 127 | //[self.deleteButton setTitle:deleteButtonText forState:UIControlStateSelected]; 128 | //[self.deleteButton setTitle:deleteButtonText forState:UIControlStateHighlighted]; 129 | } 130 | 131 | -(void)setDeleteButtonFont:(UIFont *)deleteButtonFont { 132 | _deleteButtonFont = deleteButtonFont; 133 | [self.deleteButton.titleLabel setFont:deleteButtonFont]; 134 | } 135 | 136 | -(void)setPinFillColor:(UIColor *)pinFillColor { 137 | _pinFillColor = pinFillColor; 138 | } 139 | 140 | -(void)setPinHighlightedColor:(UIColor *)pinHighlightedColor { 141 | _pinHighlightedColor = pinHighlightedColor; 142 | } 143 | 144 | -(void)setPinStrokeColor:(UIColor *)pinStrokeColor { 145 | _pinStrokeColor = pinStrokeColor; 146 | } 147 | 148 | -(void)setPinStrokeWidth:(CGFloat)pinStrokeWidth { 149 | _pinStrokeWidth = pinStrokeWidth; 150 | } 151 | 152 | -(void)setPinSize:(CGSize)pinSize { 153 | _pinSize = pinSize; 154 | self.viewForPinsLayoutConstraint.constant = pinSize.height; 155 | } 156 | 157 | -(void)setTouchIDButtonEnabled:(BOOL)touchIDButtonEnabled { 158 | _touchIDButtonEnabled = touchIDButtonEnabled; 159 | } 160 | 161 | -(void)setTouchIDButtonColor:(UIColor *)touchIDButtonColor { 162 | _touchIDButtonColor = touchIDButtonColor; 163 | [self.touchIDButton setTintColor:touchIDButtonColor]; 164 | } 165 | 166 | -(void)setTitleText:(NSString *)titleText { 167 | _titleText = titleText; 168 | self.titleLabel.text = titleText; 169 | } 170 | 171 | -(void)setTitleTextFont:(UIFont *)titleTextFont { 172 | _titleTextFont = titleTextFont; 173 | self.titleLabel.font = titleTextFont; 174 | } 175 | 176 | -(void)setTitleTextColor:(UIColor *)titleTextColor { 177 | _titleTextColor = titleTextColor; 178 | self.titleLabel.textColor = titleTextColor; 179 | 180 | } 181 | -(void)setSupportText:(NSString *)supportText { 182 | _titleText = supportText; 183 | self.supportLabel.text = supportText; 184 | } 185 | 186 | -(void)setSupportTextFont:(UIFont *)supportTextFont { 187 | _titleTextFont = supportTextFont; 188 | self.supportLabel.font = supportTextFont; 189 | } 190 | 191 | -(void)setSupportTextColor:(UIColor *)supportTextColor { 192 | _titleTextColor = supportTextColor; 193 | self.supportLabel.textColor = supportTextColor; 194 | 195 | } */ 196 | 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/SCPinViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCPinViewController.h 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol SCPinViewControllerValidateDelegate; 12 | @protocol SCPinViewControllerCreateDelegate; 13 | @protocol SCPinViewControllerDataSource; 14 | @class SCPinAppearance; 15 | typedef NS_ENUM(NSInteger, SCPinViewControllerScope) { 16 | SCPinViewControllerScopeValidate, 17 | SCPinViewControllerScopeCreate, 18 | SCPinViewControllerScopeConfirm 19 | }; 20 | 21 | @interface SCPinViewController : UIViewController 22 | - (instancetype)initWithScope:(SCPinViewControllerScope)scope; 23 | 24 | + (SCPinAppearance *)appearance; 25 | + (void)setNewAppearance:(SCPinAppearance *)newAppearance; 26 | 27 | 28 | @property (nonatomic, assign, readonly) SCPinViewControllerScope scope; 29 | 30 | @property (nonatomic, weak) id createDelegate; 31 | @property (nonatomic, weak) id validateDelegate; 32 | @property (nonatomic, weak) id dataSource; 33 | 34 | @end 35 | 36 | @protocol SCPinViewControllerValidateDelegate 37 | @required 38 | /** 39 | * when user set wrong pin code calling this delegate method 40 | */ 41 | -(void)pinViewControllerDidSetWrongPin:(SCPinViewController *)pinViewController; 42 | /** 43 | * when user set correct pin code calling this delegate method 44 | */ 45 | -(void)pinViewControllerDidSetСorrectPin:(SCPinViewController *)pinViewController; 46 | 47 | @optional 48 | /** 49 | * when user cancel calling this delegate method 50 | */ 51 | -(void)pinViewControllerDidCancel:(SCPinViewController *)pinViewController; 52 | 53 | 54 | @end 55 | 56 | @protocol SCPinViewControllerCreateDelegate 57 | @required 58 | /** 59 | * when user set new pin code calling this delegate method 60 | */ 61 | -(void)pinViewController:(SCPinViewController *)pinViewController didSetNewPin:(NSString *)pin; 62 | /** 63 | * length for pin 64 | */ 65 | -(NSInteger)lengthForPin; 66 | 67 | @optional 68 | /** 69 | * when user cancel calling this delegate method 70 | */ 71 | -(void)pinViewControllerDidCancel:(SCPinViewController *)pinViewController; 72 | 73 | @end 74 | 75 | @protocol SCPinViewControllerDataSource 76 | @required 77 | /** 78 | * Pin code for controller. Supports from 2 to 8 characters 79 | */ 80 | -(NSString *)codeForPinViewController:(SCPinViewController *)pinViewController; 81 | 82 | @optional 83 | -(BOOL)hideTouchIDButtonIfFingersAreNotEnrolled; 84 | -(BOOL)showTouchIDVerificationImmediately; 85 | @end 86 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/SCPinViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCPinViewController.m 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | 10 | #import "SCPinViewController.h" 11 | #import "SCPinButton.h" 12 | #import "SCPinView.h" 13 | #import "SCPinAppearance.h" 14 | #import 15 | #import 16 | 17 | static SCPinAppearance *appearance; 18 | 19 | 20 | @interface SCPinViewController () 21 | @property (nonatomic, strong) SCPinAppearance *appearance; 22 | 23 | @property (weak, nonatomic) IBOutlet UIButton *cancelButton; 24 | @property (weak, nonatomic) IBOutlet UIButton *touchIDButton; 25 | @property (weak, nonatomic) IBOutlet UILabel *titleLabel; 26 | @property (weak, nonatomic) IBOutlet UILabel *supportLabel; 27 | @property (strong, nonatomic) IBOutletCollection(SCPinButton) NSArray *numberButtons; 28 | @property (weak, nonatomic) IBOutlet UIButton *deleteButton; 29 | @property (weak, nonatomic) IBOutlet UIView *viewForPins; 30 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *viewForPinsLayoutConstraint; 31 | 32 | @property (nonatomic, strong) NSString *currentPin; 33 | @property (nonatomic, strong) NSString *pinToConfirm; 34 | @property (nonatomic, assign) BOOL enable; 35 | @property (nonatomic, assign) BOOL touchIDPassedValidation; 36 | @end 37 | 38 | @implementation SCPinViewController 39 | 40 | #pragma mark - Public 41 | 42 | + (SCPinAppearance *)appearance { 43 | return appearance; 44 | } 45 | 46 | + (void)setNewAppearance:(SCPinAppearance *)newAppearance { 47 | appearance = newAppearance; 48 | } 49 | 50 | #pragma mark - initialization 51 | 52 | - (instancetype)initWithScope:(SCPinViewControllerScope)scope { 53 | NSBundle *budle = [NSBundle bundleForClass:[SCPinViewController class]]; 54 | NSLog(@"%@", budle); 55 | self = [super initWithNibName:NSStringFromClass([SCPinViewController class]) bundle:budle]; 56 | if (self) { 57 | _scope = scope; 58 | if (!appearance) { 59 | appearance = [SCPinAppearance defaultAppearance]; 60 | } 61 | _appearance = appearance; 62 | 63 | } 64 | return self; 65 | } 66 | 67 | #pragma mark - View life cycle 68 | 69 | - (void)viewDidLoad { 70 | [super viewDidLoad]; 71 | [self configureView]; 72 | 73 | [self setupInitialState]; 74 | [self clearCurrentPin]; 75 | [self createPinView]; 76 | 77 | } 78 | 79 | -(void)setupInitialState { 80 | self.enable = YES; 81 | self.touchIDPassedValidation = NO; 82 | 83 | if ([self.dataSource respondsToSelector:@selector(hideTouchIDButtonIfFingersAreNotEnrolled)]) { 84 | BOOL hideTouchIDButton = [self.dataSource hideTouchIDButtonIfFingersAreNotEnrolled]; 85 | NSError *error = nil; 86 | LAContext *context = [[LAContext alloc] init]; 87 | if (hideTouchIDButton) { 88 | if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) { 89 | [self.touchIDButton setHidden:NO]; 90 | } else { 91 | [self.touchIDButton setHidden:YES]; 92 | } 93 | } else { 94 | [self.touchIDButton setHidden:NO]; 95 | } 96 | } 97 | 98 | if (!self.touchIDButton.isHidden) { 99 | self.touchIDButton.hidden = !appearance.touchIDButtonEnabled; 100 | } 101 | if ([self.dataSource respondsToSelector:@selector(showTouchIDVerificationImmediately)]) { 102 | BOOL immediately = [self.dataSource showTouchIDVerificationImmediately]; 103 | if (immediately) { 104 | [self touchIDVerification]; 105 | } 106 | } 107 | if (self.scope == SCPinViewControllerScopeCreate) { 108 | [self.touchIDButton setHidden:YES]; 109 | } 110 | 111 | 112 | } 113 | 114 | -(void)configureView { 115 | for (SCPinButton *button in self.numberButtons) { 116 | [button setTintColor:_appearance.numberButtonColor]; 117 | [button setTitleColor:_appearance.numberButtonTitleColor forState:UIControlStateNormal]; 118 | [button setTitleColor:_appearance.numberButtonTitleColor forState:UIControlStateSelected]; 119 | [button setTitleColor:_appearance.numberButtonTitleColor forState:UIControlStateHighlighted]; 120 | button.strokeColor = _appearance.numberButtonStrokeColor; 121 | button.strokeWidth = _appearance.numberButtonStrokeWitdh; 122 | button.strokeColor = _appearance.numberButtonstrokeEnabled ? [_appearance numberButtonStrokeColor] : [UIColor clearColor]; 123 | [button.titleLabel setFont:_appearance.numberButtonFont]; 124 | [button setNeedsDisplay]; 125 | } 126 | 127 | NSBundle *bundle = [NSBundle bundleForClass:[SCPinViewController class]]; 128 | 129 | UIImage *deleteButtonImage = [UIImage imageNamed:@"sc_img_delete" inBundle:bundle compatibleWithTraitCollection:nil]; 130 | [self.deleteButton setImage:deleteButtonImage forState:UIControlStateNormal]; 131 | 132 | UIImage *touchIDButtonImage = [UIImage imageNamed:@"sc_img_touchid" inBundle:bundle compatibleWithTraitCollection:nil]; 133 | [self.touchIDButton setImage:touchIDButtonImage forState:UIControlStateNormal]; 134 | 135 | [self.deleteButton setTintColor:_appearance.deleteButtonColor]; 136 | [self.touchIDButton setTintColor:_appearance.touchIDButtonColor]; 137 | 138 | self.titleLabel.text = _appearance.titleText; 139 | self.titleLabel.font = _appearance.titleTextFont; 140 | self.titleLabel.textColor = _appearance.titleTextColor; 141 | 142 | self.supportLabel.text = _appearance.supportText; 143 | self.supportLabel.font = _appearance.supportTextFont; 144 | self.supportLabel.textColor = _appearance.supportTextColor; 145 | 146 | self.touchIDButton.hidden = !appearance.touchIDButtonEnabled; 147 | 148 | self.cancelButton.hidden = !appearance.cancelButtonEnabled; 149 | [self.cancelButton setTitle:_appearance.cancelButtonText forState:UIControlStateNormal]; 150 | [self.cancelButton setTitleColor:_appearance.cancelButtonTextColor forState:UIControlStateNormal]; 151 | self.cancelButton.titleLabel.font = _appearance.cancelButtonTextFont; 152 | } 153 | 154 | 155 | -(void)clearCurrentPin { 156 | self.currentPin = @""; 157 | } 158 | 159 | - (void)didReceiveMemoryWarning { 160 | [super didReceiveMemoryWarning]; 161 | } 162 | 163 | 164 | -(void)viewWillLayoutSubviews { 165 | [super viewWillLayoutSubviews]; 166 | [self createPinView]; 167 | 168 | } 169 | 170 | -(void)viewDidLayoutSubviews { 171 | [super viewDidLayoutSubviews]; 172 | } 173 | 174 | #pragma mark - Controller logic 175 | 176 | -(void)createPinView { 177 | NSInteger length = 0; 178 | NSInteger currentPinLength = [self.currentPin length]; 179 | switch (self.scope) { 180 | case SCPinViewControllerScopeCreate: 181 | case SCPinViewControllerScopeConfirm:{ 182 | length = [self.createDelegate lengthForPin]; 183 | break; 184 | } 185 | case SCPinViewControllerScopeValidate: { 186 | NSString *pin = [self.dataSource codeForPinViewController:self]; 187 | length = [pin length]; 188 | break; 189 | } 190 | default: 191 | break; 192 | } 193 | 194 | CGFloat width = _appearance.pinSize.width; 195 | CGFloat distance = 15.0f; 196 | 197 | CGFloat pinWidth = (length * width) + ((length - 1) * distance); 198 | CGFloat center = CGRectGetWidth(self.viewForPins.frame) / 2; 199 | CGFloat x = center - (pinWidth / 2); 200 | 201 | for (UIView *view in self.viewForPins.subviews) { 202 | [view removeFromSuperview]; 203 | } 204 | 205 | for (NSInteger index = 0; index index) { 250 | self.self.currentPin = [self.currentPin substringToIndex:index]; 251 | } 252 | [self createPinView]; 253 | } 254 | 255 | -(void)setCurrentPin:(NSString *)currentPin { 256 | _currentPin = currentPin; 257 | 258 | switch (self.scope) { 259 | case SCPinViewControllerScopeValidate:{ 260 | BOOL equalLength = ([currentPin length] == [[self.dataSource codeForPinViewController:self] length]); 261 | if (equalLength) { 262 | [self validatePin:currentPin]; 263 | } 264 | break; 265 | } 266 | case SCPinViewControllerScopeCreate: { 267 | if ([currentPin length] == [self.createDelegate lengthForPin]) { 268 | // [self.createDelegate pinViewController:self didSetNewPin:currentPin]; 269 | _scope = SCPinViewControllerScopeConfirm; 270 | _titleLabel.text = _appearance.confirmText; 271 | _pinToConfirm = _currentPin; 272 | [self clearCurrentPin]; 273 | [self createPinView]; 274 | } 275 | break; 276 | } 277 | case SCPinViewControllerScopeConfirm: { 278 | if ([currentPin length] == [self.createDelegate lengthForPin]) { 279 | 280 | if ([_pinToConfirm isEqualToString:_currentPin]) { 281 | [self.createDelegate pinViewController:self didSetNewPin:currentPin]; 282 | } else { 283 | // wrong confirmation pin 284 | [self wrongConfirmPin]; 285 | } 286 | 287 | 288 | } 289 | break; 290 | } 291 | default: 292 | break; 293 | } 294 | } 295 | 296 | -(void)validatePin:(NSString *)pin { 297 | if ([[self.dataSource codeForPinViewController:self] isEqualToString:pin]) { 298 | [self correctPin]; 299 | 300 | } else { 301 | [self wrongPin]; 302 | } 303 | } 304 | 305 | -(void)correctPin { 306 | if ([self.validateDelegate respondsToSelector:@selector(pinViewControllerDidSetСorrectPin:)]) { 307 | [self.validateDelegate pinViewControllerDidSetСorrectPin:self]; 308 | } 309 | } 310 | 311 | - (void)wrongPin { 312 | __weak SCPinViewController *weakSelf = self; 313 | self.enable = NO; 314 | NSTimeInterval delay = 0.25f; 315 | dispatch_time_t delayInSeconds = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)); 316 | dispatch_after(delayInSeconds, dispatch_get_main_queue(), ^(void){ 317 | __strong SCPinViewController *strongSelf = weakSelf; 318 | AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); 319 | CAAnimation * shake = [self makeShakeAnimation]; 320 | [strongSelf.viewForPins.layer addAnimation:shake forKey:@"shake"]; 321 | if ([strongSelf.validateDelegate respondsToSelector:@selector(pinViewControllerDidSetWrongPin:)]) { 322 | [strongSelf.validateDelegate pinViewControllerDidSetWrongPin:strongSelf]; 323 | } 324 | [strongSelf clearCurrentPin]; 325 | [strongSelf createPinView]; 326 | strongSelf.enable = YES; 327 | }); 328 | 329 | } 330 | 331 | - (void)wrongConfirmPin { 332 | __weak SCPinViewController *weakSelf = self; 333 | self.enable = NO; 334 | NSTimeInterval delay = 0.25f; 335 | dispatch_time_t delayInSeconds = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)); 336 | dispatch_after(delayInSeconds, dispatch_get_main_queue(), ^(void){ 337 | __strong SCPinViewController *strongSelf = weakSelf; 338 | AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); 339 | CAAnimation * shake = [self makeShakeAnimation]; 340 | [strongSelf.viewForPins.layer addAnimation:shake forKey:@"shake"]; 341 | [strongSelf clearCurrentPin]; 342 | [strongSelf createPinView]; 343 | strongSelf.enable = YES; 344 | }); 345 | 346 | } 347 | 348 | - (CAAnimation *)makeShakeAnimation { 349 | CAKeyframeAnimation * shake = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.x"]; 350 | [shake setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]]; 351 | [shake setDuration:0.5f]; 352 | [shake setValues:@[ @(-20), @(20), @(-15), @(15), @(-10), @(10), @(-5), @(5), @(0) ]]; 353 | return shake; 354 | } 355 | 356 | 357 | - (void)touchIDVerification { 358 | 359 | NSError * error = nil; 360 | LAContext * context = [[LAContext alloc] init]; 361 | 362 | if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) { 363 | __weak SCPinViewController *weakSelf = self; 364 | [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics 365 | localizedReason:_appearance.touchIDVerification 366 | reply:^(BOOL success, NSError * authenticationError) { 367 | if (success) { 368 | __strong SCPinViewController *strongSelf = weakSelf; 369 | 370 | dispatch_async(dispatch_get_main_queue(), ^{ 371 | strongSelf.touchIDPassedValidation = YES; 372 | [strongSelf createPinView]; 373 | if ([strongSelf.validateDelegate respondsToSelector:@selector(pinViewControllerDidSetСorrectPin:)]) { 374 | [strongSelf.validateDelegate pinViewControllerDidSetСorrectPin:strongSelf]; 375 | } 376 | }); 377 | } else { 378 | NSLog(@"Authentication Error: %@", authenticationError); 379 | } 380 | }]; 381 | } else { 382 | NSLog(@"Policy Error: %@", [error localizedDescription]); 383 | } 384 | 385 | } 386 | 387 | #pragma mark - Actions 388 | 389 | - (IBAction)tapPinButtonAction:(SCPinButton *)sender { 390 | if (!self.enable) { 391 | return; 392 | } 393 | NSInteger number = [sender tag]; 394 | [self appendingPincode:[@(number) description]]; 395 | AudioServicesPlaySystemSound(0x450); 396 | 397 | } 398 | 399 | - (IBAction)touchIDButtonAction:(UIButton *)sender { 400 | [self touchIDVerification]; 401 | } 402 | 403 | - (IBAction)deleteButtonAction:(UIButton *)sender { 404 | [self removeLastPincode]; 405 | AudioServicesPlaySystemSound(0x450); 406 | 407 | } 408 | 409 | - (IBAction)cancelButtonAction:(id)sender { 410 | 411 | if ((self.scope == SCPinViewControllerScopeCreate || self.scope == SCPinViewControllerScopeConfirm) && [self.createDelegate respondsToSelector:@selector(pinViewControllerDidCancel:)]) { 412 | [self.createDelegate pinViewControllerDidCancel:self]; 413 | } 414 | 415 | if (self.scope == SCPinViewControllerScopeValidate && [self.validateDelegate respondsToSelector:@selector(pinViewControllerDidCancel:)]) { 416 | [self.validateDelegate pinViewControllerDidCancel:self]; 417 | } 418 | } 419 | 420 | @end 421 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/SCPinViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 54 | 66 | 78 | 90 | 102 | 114 | 126 | 138 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 208 | 220 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 241 | 247 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/View/PinButton/SCPinButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCPinButton.h 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCPinButton : UIButton 12 | @property (nonatomic, strong) UIColor *strokeColor; 13 | @property (nonatomic, assign) CGFloat strokeWidth; 14 | 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/View/PinButton/SCPinButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCPinButton.m 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import "SCPinButton.h" 10 | 11 | 12 | @implementation SCPinButton 13 | 14 | - (void)setHighlighted:(BOOL)highlighted { 15 | if (super.highlighted != highlighted) { 16 | super.highlighted = highlighted; 17 | 18 | [self setNeedsDisplay]; 19 | } 20 | } 21 | 22 | -(void)setStrokeColor:(UIColor *)strokeColor { 23 | _strokeColor = strokeColor; 24 | [self setNeedsDisplay]; 25 | } 26 | 27 | -(void)setStrokeWidth:(CGFloat)strokeWidth { 28 | _strokeWidth = strokeWidth; 29 | [self setNeedsDisplay]; 30 | } 31 | 32 | - (void)drawRect:(CGRect)rect { 33 | // Drawing code 34 | [super drawRect:rect]; 35 | /* 36 | @property (nonatomic, strong) UIColor *strokeColor; 37 | @property (nonatomic, assign) CGFloat strokeWidth; 38 | 39 | */ 40 | CGFloat height = CGRectGetHeight(rect); 41 | CGRect inset = CGRectInset(CGRectMake(0, 0, height, height), 1, 1); 42 | 43 | CGContextRef context = UIGraphicsGetCurrentContext(); 44 | 45 | CGColorRef colorRef = [self tintColor].CGColor; 46 | CGColorRef strokecolorRef = [self strokeColor].CGColor; 47 | UIControlState state = [self state]; 48 | 49 | CGContextSetLineWidth(context, self.strokeWidth); 50 | if (state == UIControlStateHighlighted) { 51 | CGContextSetFillColorWithColor(context, colorRef); 52 | CGContextFillEllipseInRect (context, inset); 53 | CGContextFillPath(context); 54 | } 55 | else { 56 | CGContextSetStrokeColorWithColor(context, strokecolorRef); 57 | CGContextAddEllipseInRect(context, inset); 58 | CGContextStrokePath(context); 59 | } 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/View/PinView/SCPinView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCPinView.h 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 16.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCPinView : UIView 12 | @property (nonatomic, strong) UIColor *fillColor; 13 | @property (nonatomic, strong) UIColor *highlightedColor; 14 | @property (nonatomic, strong) UIColor *strokeColor; 15 | @property (nonatomic, assign) CGFloat strokeWidth; 16 | @property (nonatomic, assign) BOOL highlighted; 17 | @end 18 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/View/PinView/SCPinView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCPinView.m 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 16.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import "SCPinView.h" 10 | 11 | @implementation SCPinView 12 | -(instancetype)init { 13 | self = [super init]; 14 | if (self) { 15 | [self applyDefautlAttributes]; 16 | } 17 | return self; 18 | } 19 | 20 | -(instancetype)initWithCoder:(NSCoder *)aDecoder { 21 | self = [super initWithCoder:aDecoder]; 22 | if (self) { 23 | [self applyDefautlAttributes]; 24 | } 25 | return self; 26 | } 27 | 28 | -(instancetype)initWithFrame:(CGRect)frame { 29 | self = [super initWithFrame:frame]; 30 | if (self) { 31 | [self applyDefautlAttributes]; 32 | } 33 | return self; 34 | } 35 | 36 | -(void)applyDefautlAttributes { 37 | self.backgroundColor = [UIColor clearColor]; 38 | self.fillColor = [UIColor redColor]; 39 | self.strokeColor = [UIColor blueColor]; 40 | self.strokeWidth = 0.5f; 41 | self.highlighted = YES; 42 | self.highlightedColor = [UIColor greenColor]; 43 | } 44 | 45 | - (void)drawRect:(CGRect)rect { 46 | 47 | CGRect bezierPathRect = CGRectMake(self.strokeWidth, self.strokeWidth, CGRectGetWidth(rect) - (self.strokeWidth * 2), CGRectGetHeight(rect) - (self.strokeWidth * 2)); 48 | UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: bezierPathRect]; 49 | if (self.highlighted) { 50 | [self.highlightedColor setFill]; 51 | } else { 52 | [self.fillColor setFill]; 53 | } 54 | [ovalPath fill]; 55 | [self.strokeColor setStroke]; 56 | ovalPath.lineWidth = self.strokeWidth; 57 | [ovalPath stroke]; 58 | } 59 | 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/images/sc_img_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarAndCandy/SCPinViewController/0a9ff6bdc6917961a9f3347bf27f6249be73e696/SCPinViewController/SCPinViewController/images/sc_img_delete.png -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/images/sc_img_delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarAndCandy/SCPinViewController/0a9ff6bdc6917961a9f3347bf27f6249be73e696/SCPinViewController/SCPinViewController/images/sc_img_delete@2x.png -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/images/sc_img_delete@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarAndCandy/SCPinViewController/0a9ff6bdc6917961a9f3347bf27f6249be73e696/SCPinViewController/SCPinViewController/images/sc_img_delete@3x.png -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/images/sc_img_touchid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarAndCandy/SCPinViewController/0a9ff6bdc6917961a9f3347bf27f6249be73e696/SCPinViewController/SCPinViewController/images/sc_img_touchid.png -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/images/sc_img_touchid@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarAndCandy/SCPinViewController/0a9ff6bdc6917961a9f3347bf27f6249be73e696/SCPinViewController/SCPinViewController/images/sc_img_touchid@2x.png -------------------------------------------------------------------------------- /SCPinViewController/SCPinViewController/images/sc_img_touchid@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarAndCandy/SCPinViewController/0a9ff6bdc6917961a9f3347bf27f6249be73e696/SCPinViewController/SCPinViewController/images/sc_img_touchid@3x.png -------------------------------------------------------------------------------- /SCPinViewController/Supporting Files/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. 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 | -------------------------------------------------------------------------------- /SCPinViewController/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /SCPinViewController/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // SCPinViewController 4 | // 5 | // Created by Maxim Kolesnik on 15.07.16. 6 | // Copyright © 2016 Sugar and Candy. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "SCPinViewController.h" 11 | #import "SCPinAppearance.h" 12 | 13 | NSString * const kViewControllerPin = @"kViewControllerPin"; 14 | 15 | @interface ViewController () 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | } 24 | 25 | - (IBAction)showPinAction:(UIButton *)sender { 26 | SCPinViewController *vc; 27 | 28 | SCPinAppearance *appearance = [SCPinAppearance defaultAppearance]; 29 | appearance.numberButtonstrokeEnabled = NO; 30 | appearance.titleText = @"Enter PIN"; 31 | 32 | appearance.cancelButtonText = @"Close"; 33 | 34 | [SCPinViewController setNewAppearance:appearance]; 35 | vc = [[SCPinViewController alloc] initWithScope:SCPinViewControllerScopeValidate]; 36 | 37 | vc.dataSource = self; 38 | vc.validateDelegate = self; 39 | [self presentViewController:vc animated:YES completion:nil]; 40 | } 41 | 42 | - (IBAction)createPinAction:(UIButton *)sender { 43 | SCPinAppearance *appearance = [SCPinAppearance defaultAppearance]; 44 | SCPinViewController *vc; 45 | 46 | appearance.titleText = @"Create PIN"; 47 | [SCPinViewController setNewAppearance:appearance]; 48 | vc = [[SCPinViewController alloc] initWithScope:SCPinViewControllerScopeCreate]; 49 | 50 | vc.createDelegate = self; 51 | UINavigationController *navigation = [[UINavigationController alloc]initWithRootViewController:vc]; 52 | UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:self action:@selector(cancel)]; 53 | [vc.navigationItem setLeftBarButtonItems:@[item]]; 54 | [self presentViewController:navigation animated:YES completion:nil]; 55 | } 56 | 57 | -(void)cancel { 58 | [self dismissViewControllerAnimated:YES completion:nil]; 59 | } 60 | 61 | - (void)didReceiveMemoryWarning { 62 | [super didReceiveMemoryWarning]; 63 | } 64 | 65 | -(void)pinViewController:(SCPinViewController *)pinViewController didSetNewPin:(NSString *)pin { 66 | NSLog(@"pinViewController: %@",pinViewController); 67 | [[NSUserDefaults standardUserDefaults] setObject:pin forKey:kViewControllerPin]; 68 | [[NSUserDefaults standardUserDefaults] synchronize]; 69 | [self dismissViewControllerAnimated:YES completion:nil]; 70 | } 71 | 72 | -(NSInteger)lengthForPin { 73 | return 4; 74 | } 75 | 76 | -(NSString *)codeForPinViewController:(SCPinViewController *)pinViewController { 77 | NSString *pin = [[NSUserDefaults standardUserDefaults] objectForKey:kViewControllerPin]; 78 | return pin; 79 | } 80 | 81 | -(BOOL)hideTouchIDButtonIfFingersAreNotEnrolled { 82 | return YES; 83 | } 84 | 85 | -(BOOL)showTouchIDVerificationImmediately { 86 | return NO; 87 | } 88 | 89 | -(void)pinViewControllerDidSetWrongPin:(SCPinViewController *)pinViewController { 90 | 91 | } 92 | 93 | -(void)pinViewControllerDidSetСorrectPin:(SCPinViewController *)pinViewController{ 94 | [self dismissViewControllerAnimated:YES completion:nil]; 95 | 96 | } 97 | 98 | - (void)pinViewControllerDidCancel:(SCPinViewController *)pinViewController { 99 | [self dismissViewControllerAnimated:YES completion:nil]; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarAndCandy/SCPinViewController/0a9ff6bdc6917961a9f3347bf27f6249be73e696/Screen.png --------------------------------------------------------------------------------