├── .gitignore ├── BKPasscodeView.podspec ├── BKPasscodeView ├── BKPasscodeDummyViewController.h ├── BKPasscodeDummyViewController.m ├── BKPasscodeField.h ├── BKPasscodeField.m ├── BKPasscodeInputView.h ├── BKPasscodeInputView.m ├── BKPasscodeLockScreenManager.h ├── BKPasscodeLockScreenManager.m ├── BKPasscodeUtils.h ├── BKPasscodeViewController.h ├── BKPasscodeViewController.m ├── BKShiftingView.h ├── BKShiftingView.m ├── BKTouchIDManager.h ├── BKTouchIDManager.m ├── BKTouchIDSwitchView.h └── BKTouchIDSwitchView.m ├── BKPasscodeViewDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── BKPasscodeViewDemo.xccheckout └── xcuserdata │ └── bkook.xcuserdatad │ └── xcschemes │ ├── BKPasscodeViewDemo.xcscheme │ └── xcschememanagement.plist ├── BKPasscodeViewDemo.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── BKPasscodeViewDemo.xccheckout └── xcuserdata │ └── bkook.xcuserdatad │ └── xcdebugger │ └── Breakpoints_v2.xcbkptlist ├── BKPasscodeViewDemo ├── AppDelegate.h ├── AppDelegate.m ├── BKCustomPasscodeViewController.h ├── BKCustomPasscodeViewController.m ├── BKPasscodeViewDemo-Info.plist ├── BKPasscodeViewDemo-Prefix.pch ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── LaunchImage.launchimage │ │ └── Contents.json │ ├── star_empty.imageset │ │ ├── Contents.json │ │ └── star_empty@2x.png │ └── star_full.imageset │ │ ├── Contents.json │ │ └── star_full@2x.png ├── MainViewController.h ├── MainViewController.m ├── en.lproj │ ├── BKPasscodeView.strings │ └── InfoPlist.strings ├── ja.lproj │ ├── BKPasscodeView.strings │ └── InfoPlist.strings ├── ko.lproj │ ├── BKPasscodeView.strings │ └── InfoPlist.strings └── main.m ├── BKPasscodeViewDemoTests ├── BKPasscodeViewDemoTests-Info.plist ├── BKPasscodeViewDemoTests.m ├── en.lproj │ └── InfoPlist.strings └── ko.lproj │ └── InfoPlist.strings ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md └── Screenshots ├── passcode_01.png ├── passcode_02.png ├── passcode_03.png ├── passcode_04.png ├── passcode_05.png ├── passcode_06.png └── passcode_07.png /.gitignore: -------------------------------------------------------------------------------- 1 | # CocoaPods 2 | # 3 | # We recommend against adding the Pods directory to your .gitignore. However 4 | # you should judge for yourself, the pros and cons are mentioned at: 5 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 6 | # 7 | Pods/ 8 | 9 | BKPasscodeViewDemo.xcodeproj/xcuserdata 10 | -------------------------------------------------------------------------------- /BKPasscodeView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 4 | 5 | s.name = "BKPasscodeView" 6 | s.version = "0.2.4" 7 | s.summary = "Customizable passcode view controller for iOS." 8 | s.description = "It supports for setting, changing and authenticating a passcode. Simple numeric passcode or normal passcode can be used." 9 | s.homepage = "https://github.com/bkook/BKPasscodeView" 10 | s.screenshots = "https://raw.githubusercontent.com/bkook/BKPasscodeView/master/Screenshots/passcode_01.png", "https://raw.githubusercontent.com/bkook/BKPasscodeView/master/Screenshots/passcode_02.png" 11 | 12 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 13 | 14 | s.license = { :type => "MIT", :file => "LICENSE" } 15 | 16 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 17 | 18 | s.author = "Byungkook Jang" 19 | s.social_media_url = "http://twitter.com/bkook" 20 | 21 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 22 | 23 | s.platform = :ios, "5.0" 24 | 25 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 26 | 27 | s.source = { :git => "https://github.com/bkook/BKPasscodeView.git", :tag => s.version.to_s } 28 | 29 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 30 | 31 | s.source_files = "BKPasscodeView/**/*.{h,m}" 32 | s.exclude_files = "Pods" 33 | 34 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 35 | 36 | s.requires_arc = true 37 | s.dependency "AFViewShaker", "~> 0.0" 38 | 39 | end 40 | -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeDummyViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeDummyViewController.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 8. 3.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol BKPasscodeDummyViewControllerDelegate; 12 | 13 | 14 | @interface BKPasscodeDummyViewController : UIViewController 15 | 16 | @property (nonatomic, weak) id delegate; 17 | 18 | @end 19 | 20 | 21 | @protocol BKPasscodeDummyViewControllerDelegate 22 | 23 | - (void)dummyViewControllerWillAppear:(BKPasscodeDummyViewController *)aViewController; 24 | - (void)dummyViewControllerDidAppear:(BKPasscodeDummyViewController *)aViewController; 25 | 26 | @end -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeDummyViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeDummyViewController.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 8. 3.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKPasscodeDummyViewController.h" 10 | 11 | @interface BKPasscodeDummyViewController () 12 | 13 | @end 14 | 15 | @implementation BKPasscodeDummyViewController 16 | 17 | - (void)viewDidLoad 18 | { 19 | [super viewDidLoad]; 20 | 21 | self.view.backgroundColor = [UIColor clearColor]; 22 | } 23 | 24 | - (void)viewWillAppear:(BOOL)animated 25 | { 26 | [super viewWillAppear:animated]; 27 | 28 | [self.delegate dummyViewControllerWillAppear:self]; 29 | } 30 | 31 | - (void)viewDidAppear:(BOOL)animated 32 | { 33 | [super viewDidAppear:animated]; 34 | 35 | if (self.presentedViewController == nil) { 36 | // only calls delegate when presented view controller(modal view controller) does not exists. 37 | [self.delegate dummyViewControllerDidAppear:self]; 38 | } 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeField.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeField.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol BKPasscodeFieldDelegate; 12 | @protocol BKPasscodeFieldImageSource; 13 | 14 | @interface BKPasscodeField : UIControl 15 | 16 | // delegate 17 | @property (nonatomic, weak) id delegate; 18 | @property (nonatomic, weak) id imageSource; 19 | 20 | // passcode 21 | @property (nonatomic, strong) NSString *passcode; 22 | 23 | // configurations 24 | @property (nonatomic) NSUInteger maximumLength; 25 | @property (nonatomic) CGSize dotSize; 26 | @property (nonatomic) CGFloat lineHeight; 27 | @property (nonatomic) CGFloat dotSpacing; 28 | @property (nonatomic, strong) UIColor *dotColor; 29 | 30 | @property (nonatomic) UIKeyboardType keyboardType; 31 | 32 | @end 33 | 34 | 35 | @protocol BKPasscodeFieldDelegate 36 | 37 | @optional 38 | /** 39 | * Ask the delegate that whether passcode field accepts text. 40 | * If you want to accept entering text, return YES. 41 | */ 42 | - (BOOL)passcodeField:(BKPasscodeField *)aPasscodeField shouldInsertText:(NSString *)aText; 43 | 44 | /** 45 | * Ask the delegate that whether passcode can be deleted. 46 | * If you want to accept deleting passcode, return YES. 47 | */ 48 | - (BOOL)passcodeFieldShouldDeleteBackward:(BKPasscodeField *)aPasscodeField; 49 | 50 | @end 51 | 52 | 53 | @protocol BKPasscodeFieldImageSource 54 | 55 | @optional 56 | 57 | /** 58 | * Ask the image source for a image to display passcode digit at index. 59 | * If you don't implement this, default shape (line for blank digit and circule for filled digit) will be displayed. 60 | */ 61 | - (UIImage *)passcodeField:(BKPasscodeField *)aPasscodeField dotImageAtIndex:(NSInteger)aIndex filled:(BOOL)aFilled; 62 | 63 | @end -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeField.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeField.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKPasscodeField.h" 10 | 11 | @interface BKPasscodeField () 12 | 13 | @property (strong, nonatomic) NSMutableString *mutablePasscode; 14 | @property (strong, nonatomic) NSRegularExpression *nonDigitRegularExpression; 15 | 16 | @end 17 | 18 | @implementation BKPasscodeField 19 | 20 | - (id)initWithFrame:(CGRect)frame 21 | { 22 | self = [super initWithFrame:frame]; 23 | if (self) { 24 | [self _initialize]; 25 | } 26 | return self; 27 | } 28 | 29 | - (id)initWithCoder:(NSCoder *)aDecoder 30 | { 31 | self = [super initWithCoder:aDecoder]; 32 | if (self) { 33 | [self _initialize]; 34 | } 35 | return self; 36 | } 37 | 38 | - (id)init 39 | { 40 | self = [super init]; 41 | if (self) { 42 | [self _initialize]; 43 | } 44 | return self; 45 | } 46 | 47 | - (void)_initialize 48 | { 49 | _maximumLength = 4; 50 | _dotSize = CGSizeMake(18.0f, 19.0f); 51 | _dotSpacing = 25.0f; 52 | _lineHeight = 3.0f; 53 | _dotColor = [UIColor blackColor]; 54 | 55 | self.backgroundColor = [UIColor clearColor]; 56 | 57 | _mutablePasscode = [[NSMutableString alloc] initWithCapacity:4]; 58 | 59 | [self addTarget:self action:@selector(didTouchUpInside:) forControlEvents:UIControlEventTouchUpInside]; 60 | } 61 | 62 | - (NSRegularExpression *)nonDigitRegularExpression 63 | { 64 | if (nil == _nonDigitRegularExpression) { 65 | _nonDigitRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"[^0-9]+" options:0 error:nil]; 66 | } 67 | return _nonDigitRegularExpression; 68 | } 69 | 70 | - (NSString *)passcode 71 | { 72 | return self.mutablePasscode; 73 | } 74 | 75 | - (void)setPasscode:(NSString *)passcode 76 | { 77 | if (passcode) { 78 | if (passcode.length > self.maximumLength) { 79 | passcode = [passcode substringWithRange:NSMakeRange(0, self.maximumLength)]; 80 | } 81 | self.mutablePasscode = [NSMutableString stringWithString:passcode]; 82 | } else { 83 | self.mutablePasscode = [NSMutableString string]; 84 | } 85 | 86 | [self setNeedsDisplay]; 87 | } 88 | 89 | #pragma mark - UIKeyInput 90 | 91 | - (BOOL)hasText 92 | { 93 | return (self.mutablePasscode.length > 0); 94 | } 95 | 96 | - (void)insertText:(NSString *)text 97 | { 98 | if (self.enabled == NO) { 99 | return; 100 | } 101 | 102 | if (self.keyboardType == UIKeyboardTypeNumberPad) { 103 | text = [self.nonDigitRegularExpression stringByReplacingMatchesInString:text options:0 range:NSMakeRange(0, text.length) withTemplate:@""]; 104 | } 105 | 106 | if (text.length == 0) { 107 | return; 108 | } 109 | 110 | NSInteger newLength = self.mutablePasscode.length + text.length; 111 | if (newLength > self.maximumLength) { 112 | return; 113 | } 114 | 115 | if ([self.delegate respondsToSelector:@selector(passcodeField:shouldInsertText:)]) { 116 | if (NO == [self.delegate passcodeField:self shouldInsertText:text]) { 117 | return; 118 | } 119 | } 120 | 121 | [self.mutablePasscode appendString:text]; 122 | 123 | [self setNeedsDisplay]; 124 | 125 | [self sendActionsForControlEvents:UIControlEventEditingChanged]; 126 | } 127 | 128 | - (void)deleteBackward 129 | { 130 | if (self.enabled == NO) { 131 | return; 132 | } 133 | 134 | if ([self.delegate respondsToSelector:@selector(passcodeFieldShouldDeleteBackward:)]) { 135 | if (NO == [self.delegate passcodeFieldShouldDeleteBackward:self]) { 136 | return; 137 | } 138 | } 139 | 140 | if (self.mutablePasscode.length == 0) { 141 | return; 142 | } 143 | 144 | [self.mutablePasscode deleteCharactersInRange:NSMakeRange(self.mutablePasscode.length - 1, 1)]; 145 | 146 | [self setNeedsDisplay]; 147 | 148 | [self sendActionsForControlEvents:UIControlEventEditingChanged]; 149 | } 150 | 151 | - (UITextAutocapitalizationType)autocapitalizationType 152 | { 153 | return UITextAutocapitalizationTypeNone; 154 | } 155 | 156 | - (UITextAutocorrectionType)autocorrectionType 157 | { 158 | return UITextAutocorrectionTypeNo; 159 | } 160 | 161 | - (UITextSpellCheckingType)spellCheckingType 162 | { 163 | return UITextSpellCheckingTypeNo; 164 | } 165 | 166 | - (BOOL)enablesReturnKeyAutomatically 167 | { 168 | return YES; 169 | } 170 | 171 | - (UIKeyboardAppearance)keyboardAppearance 172 | { 173 | return UIKeyboardAppearanceDefault; 174 | } 175 | 176 | - (UIReturnKeyType)returnKeyType 177 | { 178 | return UIReturnKeyDone; 179 | } 180 | 181 | - (BOOL)isSecureTextEntry 182 | { 183 | return YES; 184 | } 185 | 186 | #pragma mark - UIView 187 | 188 | - (CGSize)contentSize 189 | { 190 | return CGSizeMake(self.maximumLength * _dotSize.width + (self.maximumLength - 1) * _dotSpacing, 191 | _dotSize.height); 192 | } 193 | 194 | - (void)setFrame:(CGRect)frame 195 | { 196 | [super setFrame:frame]; 197 | [self setNeedsDisplay]; 198 | } 199 | 200 | - (void)drawRect:(CGRect)rect 201 | { 202 | CGSize contentSize = [self contentSize]; 203 | 204 | CGPoint origin = CGPointMake(floorf((self.frame.size.width - contentSize.width) * 0.5f), 205 | floorf((self.frame.size.height - contentSize.height) * 0.5f)); 206 | 207 | if ([self.imageSource respondsToSelector:@selector(passcodeField:dotImageAtIndex:filled:)]) { 208 | 209 | for (NSUInteger i = 0; i < self.maximumLength; i++) { 210 | 211 | UIImage *image = nil; 212 | 213 | if (i < self.mutablePasscode.length) { 214 | // draw filled image 215 | image = [self.imageSource passcodeField:self dotImageAtIndex:i filled:YES]; 216 | } else { 217 | // draw blank image 218 | image = [self.imageSource passcodeField:self dotImageAtIndex:i filled:NO]; 219 | } 220 | 221 | if (image) { 222 | CGRect imageFrame = CGRectMake(origin.x, origin.y, self.dotSize.width, self.dotSize.height); 223 | [image drawInRect:imageFrame]; 224 | } 225 | 226 | origin.x += (self.dotSize.width + self.dotSpacing); 227 | } 228 | 229 | } else { 230 | 231 | CGContextRef context = UIGraphicsGetCurrentContext(); 232 | CGContextSetFillColorWithColor(context, self.dotColor.CGColor); 233 | 234 | for (NSUInteger i = 0; i < self.maximumLength; i++) { 235 | 236 | if (i < self.mutablePasscode.length) { 237 | // draw circle 238 | CGRect circleFrame = CGRectMake(origin.x, origin.y, self.dotSize.width, self.dotSize.height); 239 | CGContextFillEllipseInRect(context, circleFrame); 240 | } else { 241 | // draw line 242 | CGRect lineFrame = CGRectMake(origin.x, origin.y + floorf((self.dotSize.height - self.lineHeight) * 0.5f), 243 | self.dotSize.width, self.lineHeight); 244 | CGContextFillRect(context, lineFrame); 245 | } 246 | 247 | origin.x += (self.dotSize.width + self.dotSpacing); 248 | } 249 | } 250 | } 251 | 252 | - (CGSize)sizeThatFits:(CGSize)size 253 | { 254 | return [self contentSize]; 255 | } 256 | 257 | #pragma mark - UIResponder 258 | 259 | - (BOOL)canBecomeFirstResponder 260 | { 261 | return YES; 262 | } 263 | 264 | #pragma mark - Actions 265 | 266 | - (void)didTouchUpInside:(id)sender 267 | { 268 | [self becomeFirstResponder]; 269 | } 270 | 271 | @end 272 | -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeInputView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeInputView.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "BKPasscodeField.h" 12 | 13 | typedef enum : NSUInteger { 14 | BKPasscodeInputViewNumericPasscodeStyle, 15 | BKPasscodeInputViewNormalPasscodeStyle, 16 | } BKPasscodeInputViewPasscodeStyle; 17 | 18 | 19 | @protocol BKPasscodeInputViewDelegate; 20 | 21 | 22 | @interface BKPasscodeInputView : UIView 23 | 24 | @property (nonatomic, weak) id delegate; 25 | 26 | @property (nonatomic) BKPasscodeInputViewPasscodeStyle passcodeStyle; 27 | @property (nonatomic) UIKeyboardType keyboardType; 28 | @property (nonatomic) NSUInteger maximumLength; 29 | 30 | @property (nonatomic, strong) NSString *title; 31 | @property (nonatomic, strong) NSString *message; 32 | @property (nonatomic, strong) NSString *errorMessage; 33 | @property (nonatomic, getter = isEnabled) BOOL enabled; 34 | @property (nonatomic, strong) NSString *passcode; 35 | 36 | @property (nonatomic, strong, readonly) UIControl *passcodeField; 37 | 38 | @property (nonatomic, strong) UIFont *titleFont; 39 | @property (nonatomic, strong) UIColor *titleColor; 40 | @property (nonatomic, strong) UIFont *messageFont; 41 | @property (nonatomic, strong) UIColor *messageColor; 42 | @property (nonatomic, strong) UIFont *errorMessageFont; 43 | @property (nonatomic, strong) UIColor *errorMessageColor; 44 | 45 | // You can override these methods to customize message label appearance. 46 | + (void)configureTitleLabel:(UILabel *)aLabel; 47 | + (void)configureMessageLabel:(UILabel *)aLabel; 48 | + (void)configureErrorMessageLabel:(UILabel *)aLabel; 49 | 50 | @end 51 | 52 | 53 | @protocol BKPasscodeInputViewDelegate 54 | 55 | /** 56 | * Tells the delegate that maximum length of passcode is entered or user tapped Done button in the keyboard (in case of BKPasscodeInputViewNormalPasscodeStyle). 57 | */ 58 | - (void)passcodeInputViewDidFinish:(BKPasscodeInputView *)aInputView; 59 | 60 | @end -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeInputView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeInputView.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKPasscodeInputView.h" 10 | 11 | #define kLabelPasscodeSpacePortrait (30.0f) 12 | #define kLabelPasscodeSpaceLandscape (10.0f) 13 | 14 | #define kTextLeftRightSpace (20.0f) 15 | 16 | #define kErrorMessageLeftRightPadding (10.0f) 17 | #define kErrorMessageTopBottomPadding (5.0f) 18 | 19 | #define kDefaultNumericPasscodeMaximumLength (4) 20 | #define kDefaultNormalPasscodeMaximumLength (20) 21 | 22 | @interface BKPasscodeInputView () { 23 | BOOL _isKeyboardTypeSet; 24 | } 25 | 26 | @property (nonatomic, strong) UILabel *titleLabel; 27 | @property (nonatomic, strong) UILabel *messageLabel; 28 | @property (nonatomic, strong) UILabel *errorMessageLabel; 29 | @property (nonatomic, strong) UIControl *passcodeField; 30 | 31 | @end 32 | 33 | @implementation BKPasscodeInputView 34 | 35 | @synthesize maximumLength = _maximumLength; 36 | @synthesize keyboardType = _keyboardType; 37 | @synthesize passcodeField = _passcodeField; 38 | 39 | - (instancetype)initWithFrame:(CGRect)frame 40 | { 41 | self = [super initWithFrame:frame]; 42 | if (self) { 43 | [self _initialize]; 44 | } 45 | return self; 46 | } 47 | 48 | - (instancetype)initWithCoder:(NSCoder *)coder 49 | { 50 | self = [super initWithCoder:coder]; 51 | if (self) { 52 | [self _initialize]; 53 | } 54 | return self; 55 | } 56 | 57 | - (void)_initialize 58 | { 59 | self.backgroundColor = [UIColor clearColor]; 60 | 61 | _enabled = YES; 62 | _passcodeStyle = BKPasscodeInputViewNumericPasscodeStyle; 63 | _keyboardType = UIKeyboardTypeNumberPad; 64 | _maximumLength = 0; 65 | 66 | _titleLabel = [[UILabel alloc] init]; 67 | [[self class] configureTitleLabel:_titleLabel]; 68 | [self addSubview:_titleLabel]; 69 | 70 | _messageLabel = [[UILabel alloc] init]; 71 | [[self class] configureMessageLabel:_messageLabel]; 72 | [self addSubview:_messageLabel]; 73 | 74 | _errorMessageLabel = [[UILabel alloc] init]; 75 | [[self class] configureErrorMessageLabel:_errorMessageLabel]; 76 | _errorMessageLabel.hidden = YES; 77 | [self addSubview:_errorMessageLabel]; 78 | } 79 | 80 | + (void)configureTitleLabel:(UILabel *)aLabel 81 | { 82 | aLabel.backgroundColor = [UIColor clearColor]; 83 | aLabel.numberOfLines = 1; 84 | aLabel.textAlignment = NSTextAlignmentCenter; 85 | aLabel.lineBreakMode = NSLineBreakByTruncatingTail; 86 | aLabel.font = [UIFont boldSystemFontOfSize:15.0f]; 87 | } 88 | 89 | + (void)configureMessageLabel:(UILabel *)aLabel 90 | { 91 | aLabel.backgroundColor = [UIColor clearColor]; 92 | aLabel.numberOfLines = 0; 93 | aLabel.textAlignment = NSTextAlignmentCenter; 94 | aLabel.lineBreakMode = NSLineBreakByWordWrapping; 95 | aLabel.font = [UIFont systemFontOfSize:15.0f]; 96 | } 97 | 98 | + (void)configureErrorMessageLabel:(UILabel *)aLabel 99 | { 100 | aLabel.backgroundColor = [UIColor clearColor]; 101 | aLabel.numberOfLines = 0; 102 | aLabel.textAlignment = NSTextAlignmentCenter; 103 | aLabel.lineBreakMode = NSLineBreakByWordWrapping; 104 | aLabel.backgroundColor = [UIColor colorWithRed:0.63 green:0.2 blue:0.13 alpha:1]; 105 | aLabel.textColor = [UIColor whiteColor]; 106 | aLabel.font = [UIFont systemFontOfSize:15.0f]; 107 | 108 | aLabel.layer.cornerRadius = 10.0f; 109 | aLabel.layer.masksToBounds = YES; 110 | } 111 | 112 | - (void)setPasscodeStyle:(BKPasscodeInputViewPasscodeStyle)passcodeStyle 113 | { 114 | if (_passcodeStyle != passcodeStyle) { 115 | _passcodeStyle = passcodeStyle; 116 | 117 | if (_passcodeField) { 118 | _passcodeField = nil; 119 | [self passcodeField]; // load passcode field immediately if already exists before. 120 | } 121 | } 122 | } 123 | 124 | - (UIControl *)passcodeField 125 | { 126 | if (nil == _passcodeField) { 127 | 128 | switch (_passcodeStyle) { 129 | case BKPasscodeInputViewNumericPasscodeStyle: 130 | { 131 | if (_maximumLength == 0) { 132 | _maximumLength = kDefaultNumericPasscodeMaximumLength; 133 | } 134 | 135 | if (NO == _isKeyboardTypeSet) { 136 | _keyboardType = UIKeyboardTypeNumberPad; 137 | } 138 | 139 | BKPasscodeField *passcodeField = [[BKPasscodeField alloc] init]; 140 | passcodeField.delegate = self; 141 | passcodeField.keyboardType = _keyboardType; 142 | passcodeField.maximumLength = _maximumLength; 143 | [passcodeField addTarget:self action:@selector(passcodeControlEditingChanged:) forControlEvents:UIControlEventEditingChanged]; 144 | 145 | [self setPasscodeField:passcodeField]; 146 | break; 147 | } 148 | 149 | case BKPasscodeInputViewNormalPasscodeStyle: 150 | { 151 | if (_maximumLength == 0) { 152 | _maximumLength = kDefaultNormalPasscodeMaximumLength; 153 | } 154 | 155 | if (NO == _isKeyboardTypeSet) { 156 | _keyboardType = UIKeyboardTypeASCIICapable; 157 | } 158 | 159 | UITextField *textField = [[UITextField alloc] init]; 160 | textField.delegate = self; 161 | textField.borderStyle = UITextBorderStyleRoundedRect; 162 | textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 163 | textField.autocorrectionType = UITextAutocorrectionTypeNo; 164 | textField.spellCheckingType = UITextSpellCheckingTypeNo; 165 | textField.enablesReturnKeyAutomatically = YES; 166 | textField.keyboardType = _keyboardType; 167 | textField.secureTextEntry = YES; 168 | textField.font = [UIFont systemFontOfSize:25.0f]; 169 | textField.clearButtonMode = UITextFieldViewModeWhileEditing; 170 | textField.returnKeyType = UIReturnKeyDone; 171 | 172 | [self setPasscodeField:textField]; 173 | break; 174 | } 175 | } 176 | } 177 | 178 | return _passcodeField; 179 | } 180 | 181 | - (void)setPasscodeField:(UIControl *)passcodeField 182 | { 183 | if (_passcodeField != passcodeField) { 184 | 185 | [_passcodeField removeFromSuperview]; 186 | _passcodeField = passcodeField; 187 | if (_passcodeField) { 188 | [self addSubview:_passcodeField]; 189 | } 190 | [self setNeedsLayout]; 191 | } 192 | } 193 | 194 | - (void)setMaximumLength:(NSUInteger)maximumLength 195 | { 196 | _maximumLength = maximumLength; 197 | 198 | if ([_passcodeField isKindOfClass:[BKPasscodeField class]]) { 199 | [(BKPasscodeField *)_passcodeField setMaximumLength:maximumLength]; 200 | } 201 | } 202 | 203 | - (void)setKeyboardType:(UIKeyboardType)keyboardType 204 | { 205 | _isKeyboardTypeSet = YES; 206 | _keyboardType = keyboardType; 207 | [(id)_passcodeField setKeyboardType:keyboardType]; 208 | } 209 | 210 | - (void)setTitle:(NSString *)title 211 | { 212 | self.titleLabel.text = title; 213 | [self setNeedsLayout]; 214 | } 215 | 216 | - (NSString *)title 217 | { 218 | return self.titleLabel.text; 219 | } 220 | 221 | - (void)setMessage:(NSString *)message 222 | { 223 | self.messageLabel.text = message; 224 | self.messageLabel.hidden = NO; 225 | 226 | self.errorMessageLabel.text = nil; 227 | self.errorMessageLabel.hidden = YES; 228 | 229 | [self setNeedsLayout]; 230 | } 231 | 232 | - (NSString *)message 233 | { 234 | return self.messageLabel.text; 235 | } 236 | 237 | - (void)setErrorMessage:(NSString *)errorMessage 238 | { 239 | self.errorMessageLabel.text = errorMessage; 240 | self.errorMessageLabel.hidden = NO; 241 | 242 | self.messageLabel.text = nil; 243 | self.messageLabel.hidden = YES; 244 | 245 | [self setNeedsLayout]; 246 | } 247 | 248 | - (NSString *)errorMessage 249 | { 250 | return self.errorMessageLabel.text; 251 | } 252 | 253 | - (NSString *)passcode 254 | { 255 | switch (self.passcodeStyle) { 256 | case BKPasscodeInputViewNumericPasscodeStyle: 257 | return [(BKPasscodeField *)self.passcodeField passcode]; 258 | case BKPasscodeInputViewNormalPasscodeStyle: 259 | return [(UITextField *)self.passcodeField text]; 260 | } 261 | } 262 | 263 | - (void)setPasscode:(NSString *)passcode 264 | { 265 | switch (self.passcodeStyle) { 266 | case BKPasscodeInputViewNumericPasscodeStyle: 267 | [(BKPasscodeField *)self.passcodeField setPasscode:passcode]; 268 | break; 269 | case BKPasscodeInputViewNormalPasscodeStyle: 270 | [(UITextField *)self.passcodeField setText:passcode]; 271 | break; 272 | } 273 | } 274 | 275 | #pragma mark - Customizations 276 | 277 | - (UIFont *)titleFont 278 | { 279 | return self.titleLabel.font; 280 | } 281 | 282 | - (void)setTitleFont:(UIFont *)font 283 | { 284 | self.titleLabel.font = font; 285 | } 286 | 287 | - (UIFont *)messageFont 288 | { 289 | return self.messageLabel.font; 290 | } 291 | 292 | - (void)setMessageFont:(UIFont *)font 293 | { 294 | self.messageLabel.font = font; 295 | } 296 | 297 | - (UIFont *)errorMessageFont 298 | { 299 | return self.errorMessageLabel.font; 300 | } 301 | 302 | - (void)setErrorMessageFont:(UIFont *)font 303 | { 304 | self.errorMessageLabel.font = font; 305 | } 306 | 307 | - (UIColor *)titleColor 308 | { 309 | return self.titleLabel.textColor; 310 | } 311 | 312 | - (void)setTitleColor:(UIColor *)color 313 | { 314 | self.titleLabel.textColor = color; 315 | } 316 | 317 | - (UIColor *)messageColor 318 | { 319 | return self.messageLabel.textColor; 320 | } 321 | 322 | - (void)setMessageColor:(UIColor *)color 323 | { 324 | self.messageLabel.textColor = color; 325 | } 326 | 327 | - (UIColor *)errorMessageColor 328 | { 329 | return self.errorMessageLabel.textColor; 330 | } 331 | 332 | - (void)setErrorMessageColor:(UIColor *)color 333 | { 334 | self.errorMessageLabel.textColor = color; 335 | } 336 | 337 | #pragma mark - UIView 338 | 339 | - (CGFloat)labelPasscodeSpace 340 | { 341 | return UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) ? kLabelPasscodeSpacePortrait : kLabelPasscodeSpaceLandscape; 342 | } 343 | 344 | - (void)layoutSubviews 345 | { 346 | [super layoutSubviews]; 347 | 348 | // layout passcode control to center 349 | [self.passcodeField sizeToFit]; 350 | 351 | if ([self.passcodeField isKindOfClass:[UITextField class]]) { 352 | self.passcodeField.frame = CGRectMake(0, 0, self.frame.size.width - kTextLeftRightSpace * 2.0f, CGRectGetHeight(self.passcodeField.frame) + 10.0f); 353 | } 354 | 355 | self.passcodeField.center = CGPointMake(CGRectGetWidth(self.frame) * 0.5f, CGRectGetHeight(self.frame) * 0.5f); 356 | 357 | CGFloat maxTextWidth = self.frame.size.width - (kTextLeftRightSpace * 2.0f); 358 | CGFloat labelPasscodeSpace = [self labelPasscodeSpace]; 359 | 360 | // layout title label 361 | _titleLabel.frame = CGRectMake(kTextLeftRightSpace, 0, maxTextWidth, self.frame.size.height); 362 | [_titleLabel sizeToFit]; 363 | 364 | CGRect rect = _titleLabel.frame; 365 | rect.origin.x = floorf((self.frame.size.width - CGRectGetWidth(rect)) * 0.5f); 366 | rect.origin.y = CGRectGetMinY(self.passcodeField.frame) - labelPasscodeSpace - CGRectGetHeight(_titleLabel.frame); 367 | 368 | _titleLabel.frame = rect; 369 | 370 | // layout message label 371 | if (!_messageLabel.hidden) { 372 | _messageLabel.frame = CGRectMake(kTextLeftRightSpace, CGRectGetMaxY(self.passcodeField.frame) + labelPasscodeSpace, maxTextWidth, self.frame.size.height); 373 | [_messageLabel sizeToFit]; 374 | 375 | rect = _messageLabel.frame; 376 | rect.origin.x = floorf((self.frame.size.width - CGRectGetWidth(rect)) * 0.5f); 377 | _messageLabel.frame = rect; 378 | } 379 | 380 | // layout error message label 381 | if (!_errorMessageLabel.hidden) { 382 | _errorMessageLabel.frame = CGRectMake(0, CGRectGetMaxY(self.passcodeField.frame) + labelPasscodeSpace, 383 | maxTextWidth - kErrorMessageLeftRightPadding * 2.0f, 384 | self.frame.size.height); 385 | [_errorMessageLabel sizeToFit]; 386 | 387 | rect = _errorMessageLabel.frame; 388 | rect.size.width += (kErrorMessageLeftRightPadding * 2.0f); 389 | rect.size.height += (kErrorMessageTopBottomPadding * 2.0f); 390 | rect.origin.x = floorf((self.frame.size.width - rect.size.width) * 0.5f); 391 | 392 | _errorMessageLabel.frame = rect; 393 | } 394 | } 395 | 396 | #pragma mark - UIResponder 397 | 398 | - (BOOL)canBecomeFirstResponder 399 | { 400 | return [self.passcodeField canBecomeFirstResponder]; 401 | } 402 | 403 | - (BOOL)becomeFirstResponder 404 | { 405 | return [self.passcodeField becomeFirstResponder]; 406 | } 407 | 408 | - (BOOL)canResignFirstResponder 409 | { 410 | return [self.passcodeField canResignFirstResponder]; 411 | } 412 | 413 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 414 | { 415 | [super touchesBegan:touches withEvent:event]; 416 | [self.passcodeField becomeFirstResponder]; 417 | } 418 | 419 | #pragma mark - Actions 420 | 421 | - (void)passcodeControlEditingChanged:(id)sender 422 | { 423 | if (![self.passcodeField isKindOfClass:[BKPasscodeField class]]) { 424 | return; 425 | } 426 | 427 | BKPasscodeField *passcodeField = (BKPasscodeField *)self.passcodeField; 428 | 429 | if (passcodeField.passcode.length == passcodeField.maximumLength) { 430 | if ([self.delegate respondsToSelector:@selector(passcodeInputViewDidFinish:)]) { 431 | [self.delegate passcodeInputViewDidFinish:self]; 432 | } 433 | } 434 | } 435 | 436 | #pragma mark - BKPasscodeFieldDelegate 437 | 438 | - (BOOL)passcodeField:(BKPasscodeField *)aPasscodeField shouldInsertText:(NSString *)aText 439 | { 440 | return self.isEnabled; 441 | } 442 | 443 | - (BOOL)passcodeFieldShouldDeleteBackward:(BKPasscodeField *)aPasscodeField 444 | { 445 | return self.isEnabled; 446 | } 447 | 448 | #pragma mark - UITextFieldDelegate 449 | 450 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 451 | { 452 | if (self.isEnabled == NO) { 453 | return NO; 454 | } 455 | 456 | NSUInteger length = textField.text.length - range.length + string.length; 457 | if (length > self.maximumLength) { 458 | return NO; 459 | } 460 | 461 | return YES; 462 | } 463 | 464 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 465 | { 466 | if (self.isEnabled == NO) { 467 | return NO; 468 | } 469 | 470 | if ([self.delegate respondsToSelector:@selector(passcodeInputViewDidFinish:)]) { 471 | [self.delegate passcodeInputViewDidFinish:self]; 472 | return NO; 473 | } else { 474 | return YES; // default behavior 475 | } 476 | } 477 | 478 | #pragma mark - NSCopying 479 | 480 | - (id)copyWithZone:(NSZone *)zone 481 | { 482 | BKPasscodeInputView *view = [[[self class] alloc] initWithFrame:self.bounds]; 483 | view.delegate = self.delegate; 484 | view.autoresizingMask = self.autoresizingMask; 485 | view.passcodeStyle = self.passcodeStyle; 486 | view.keyboardType = self.keyboardType; 487 | view.maximumLength = self.maximumLength; 488 | 489 | return view; 490 | } 491 | 492 | @end 493 | -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeLockScreenManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeLockScreenManager.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 8. 2.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BKPasscodeViewController.h" 11 | #import "BKPasscodeDummyViewController.h" 12 | 13 | @protocol BKPasscodeLockScreenManagerDelegate; 14 | 15 | 16 | @interface BKPasscodeLockScreenManager : NSObject 17 | 18 | @property (weak, nonatomic) id delegate; 19 | 20 | /** 21 | * Shared(singleton) instance. 22 | */ 23 | + (BKPasscodeLockScreenManager *)sharedManager; 24 | 25 | /** 26 | * Shows lock screen. You should call this method at applicationDidEnterBackground: in app delegate. 27 | */ 28 | - (void)showLockScreen:(BOOL)animated; 29 | 30 | @end 31 | 32 | 33 | @protocol BKPasscodeLockScreenManagerDelegate 34 | 35 | /** 36 | * Ask the delegate a view controller that should be displayed as lock screen. 37 | */ 38 | - (UIViewController *)lockScreenManagerPasscodeViewController:(BKPasscodeLockScreenManager *)aManager; 39 | 40 | @optional 41 | /** 42 | * Ask the delegate that lock screen should be displayed or not. 43 | * If you prevent displaying lock screen, return NO. 44 | * If delegate does not implement this method, the lock screen will be shown everytime when application did enter background. 45 | */ 46 | - (BOOL)lockScreenManagerShouldShowLockScreen:(BKPasscodeLockScreenManager *)aManager; 47 | 48 | /** 49 | * Ask the delegate for the view that will be used as snapshot. 50 | */ 51 | - (UIView *)lockScreenManagerBlindView:(BKPasscodeLockScreenManager *)aManager; 52 | 53 | @end -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeLockScreenManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeLockScreenManager.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 8. 2.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKPasscodeLockScreenManager.h" 10 | #import "BKPasscodeViewController.h" 11 | 12 | static BKPasscodeLockScreenManager *_sharedManager; 13 | 14 | @interface BKPasscodeLockScreenManager () 15 | 16 | @property (strong, nonatomic) UIWindow *mainWindow; 17 | @property (strong, nonatomic) UIWindow *lockScreenWindow; 18 | @property (strong, nonatomic) UIView *blindView; 19 | 20 | @end 21 | 22 | @implementation BKPasscodeLockScreenManager 23 | 24 | + (BKPasscodeLockScreenManager *)sharedManager 25 | { 26 | static dispatch_once_t onceToken; 27 | dispatch_once(&onceToken, ^{ 28 | _sharedManager = [[BKPasscodeLockScreenManager alloc] init]; 29 | }); 30 | return _sharedManager; 31 | } 32 | 33 | - (void)showLockScreen:(BOOL)animated 34 | { 35 | NSAssert(self.delegate, @"delegate is not assigned."); 36 | 37 | if (self.lockScreenWindow && self.lockScreenWindow.rootViewController) { 38 | return; 39 | } 40 | 41 | if ([self.delegate respondsToSelector:@selector(lockScreenManagerShouldShowLockScreen:)]) { 42 | if (NO == [self.delegate lockScreenManagerShouldShowLockScreen:self]) { 43 | return; 44 | } 45 | } 46 | 47 | // get the main window 48 | self.mainWindow = [[UIApplication sharedApplication] keyWindow]; 49 | 50 | // dismiss keyboard before showing lock screen 51 | [self.mainWindow.rootViewController.view endEditing:YES]; 52 | 53 | // add blind view 54 | UIView *blindView; 55 | 56 | if ([self.delegate respondsToSelector:@selector(lockScreenManagerBlindView:)]) { 57 | blindView = [self.delegate lockScreenManagerBlindView:self]; 58 | } 59 | 60 | if (nil == blindView) { 61 | blindView = [[UIView alloc] init]; 62 | blindView.backgroundColor = [UIColor whiteColor]; 63 | } 64 | 65 | blindView.frame = self.mainWindow.bounds; 66 | blindView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 67 | 68 | [self.mainWindow addSubview:blindView]; 69 | 70 | self.blindView = blindView; 71 | 72 | // set dummy view controller as root view controller 73 | BKPasscodeDummyViewController *dummyViewController = [[BKPasscodeDummyViewController alloc] init]; 74 | 75 | UIWindow *lockScreenWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 76 | lockScreenWindow.windowLevel = self.mainWindow.windowLevel + 1; 77 | lockScreenWindow.rootViewController = dummyViewController; 78 | lockScreenWindow.backgroundColor = [UIColor clearColor]; 79 | [lockScreenWindow makeKeyAndVisible]; 80 | 81 | // present lock screen 82 | UIViewController *lockScreenViewController = [self.delegate lockScreenManagerPasscodeViewController:self]; 83 | 84 | if (animated) { 85 | blindView.hidden = YES; 86 | } 87 | 88 | [lockScreenWindow.rootViewController presentViewController:lockScreenViewController animated:animated completion:^{ 89 | blindView.hidden = NO; 90 | }]; 91 | 92 | self.lockScreenWindow = lockScreenWindow; 93 | 94 | [lockScreenViewController.view.superview bringSubviewToFront:lockScreenViewController.view]; 95 | 96 | dummyViewController.delegate = self; 97 | } 98 | 99 | - (void)dummyViewControllerWillAppear:(BKPasscodeDummyViewController *)aViewController 100 | { 101 | // remove blind view 102 | [self.blindView removeFromSuperview]; 103 | self.blindView = nil; 104 | } 105 | 106 | - (void)dummyViewControllerDidAppear:(BKPasscodeDummyViewController *)aViewController 107 | { 108 | if ([UIView instancesRespondToSelector:@selector(tintColor)]) { 109 | self.lockScreenWindow = nil; 110 | } else { 111 | [self performSelector:@selector(setLockScreenWindow:) withObject:nil afterDelay:0.1f]; // workaround for wired dealloc on iOS 6 112 | } 113 | 114 | [self.mainWindow makeKeyAndVisible]; 115 | self.mainWindow = nil; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeUtils.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 10. 4.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | /* 10 | * System Versioning Preprocessor Macros 11 | */ 12 | 13 | #define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) 14 | #define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) 15 | #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) 16 | #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 17 | #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) 18 | -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeViewController.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BKPasscodeInputView.h" 11 | #import "BKTouchIDSwitchView.h" 12 | #import "BKTouchIDManager.h" 13 | 14 | 15 | typedef enum : NSUInteger { 16 | BKPasscodeViewControllerNewPasscodeType, 17 | BKPasscodeViewControllerChangePasscodeType, 18 | BKPasscodeViewControllerCheckPasscodeType 19 | } BKPasscodeViewControllerType; 20 | 21 | @protocol BKPasscodeViewControllerDelegate; 22 | 23 | @interface BKPasscodeViewController : UIViewController 24 | 25 | @property (nonatomic, weak) id delegate; 26 | 27 | @property (nonatomic) BKPasscodeViewControllerType type; 28 | @property (nonatomic) BKPasscodeInputViewPasscodeStyle passcodeStyle; 29 | @property (nonatomic) UIKeyboardType keyboardType; 30 | @property (nonatomic, strong, readonly) BKPasscodeInputView *passcodeInputView; 31 | @property (nonatomic, strong) BKTouchIDManager *touchIDManager; 32 | 33 | /** 34 | * Customize passcode input view 35 | * You may override to customize passcode input view appearance. 36 | */ 37 | - (void)customizePasscodeInputView:(BKPasscodeInputView *)aPasscodeInputView; 38 | 39 | /** 40 | * Instantiate passcode input view. 41 | * You may override to use custom passcode input view. 42 | */ 43 | - (BKPasscodeInputView *)instantiatePasscodeInputView; 44 | 45 | /** 46 | * Prompts Touch ID view to scan fingerprint. 47 | */ 48 | - (void)startTouchIDAuthenticationIfPossible; 49 | 50 | /** 51 | * Prompts Touch ID view to scan fingerprint. 52 | * If Touch ID is disabled or unavailable, value of 'prompted' will be NO. 53 | */ 54 | - (void)startTouchIDAuthenticationIfPossible:(void(^)(BOOL prompted))aCompletionBlock; 55 | 56 | @end 57 | 58 | @protocol BKPasscodeViewControllerDelegate 59 | 60 | /** 61 | * Tells the delegate that passcode is created or authenticated successfully. 62 | */ 63 | - (void)passcodeViewController:(BKPasscodeViewController *)aViewController didFinishWithPasscode:(NSString *)aPasscode; 64 | 65 | @optional 66 | 67 | /** 68 | * Tells the delegate that Touch ID error occured. 69 | */ 70 | - (void)passcodeViewControllerDidFailTouchIDKeychainOperation:(BKPasscodeViewController *)aViewController; 71 | 72 | /** 73 | * Ask the delegate to verify that a passcode is correct. You must call the resultHandler with result. 74 | * You can check passcode asynchronously and show progress view (e.g. UIActivityIndicator) in the view controller if authentication takes too long. 75 | * You must call result handler in main thread. 76 | */ 77 | - (void)passcodeViewController:(BKPasscodeViewController *)aViewController authenticatePasscode:(NSString *)aPasscode resultHandler:(void(^)(BOOL succeed))aResultHandler; 78 | 79 | /** 80 | * Tells the delegate that user entered incorrect passcode. 81 | * You should manage failed attempts yourself and it should be returned by -[BKPasscodeViewControllerDelegate passcodeViewControllerNumberOfFailedAttempts:] method. 82 | */ 83 | - (void)passcodeViewControllerDidFailAttempt:(BKPasscodeViewController *)aViewController; 84 | 85 | /** 86 | * Ask the delegate that how many times incorrect passcode entered to display failed attempt count. 87 | */ 88 | - (NSUInteger)passcodeViewControllerNumberOfFailedAttempts:(BKPasscodeViewController *)aViewController; 89 | 90 | /** 91 | * Ask the delegate that whether passcode view should lock or unlock. 92 | * If you return nil, passcode view will unlock otherwise it will lock until the date. 93 | */ 94 | - (NSDate *)passcodeViewControllerLockUntilDate:(BKPasscodeViewController *)aViewController; 95 | 96 | @end -------------------------------------------------------------------------------- /BKPasscodeView/BKPasscodeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeViewController.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKPasscodeViewController.h" 10 | #import "BKShiftingView.h" 11 | #import "AFViewShaker.h" 12 | #import "BKPasscodeUtils.h" 13 | 14 | typedef enum : NSUInteger { 15 | BKPasscodeViewControllerStateUnknown, 16 | BKPasscodeViewControllerStateCheckPassword, 17 | BKPasscodeViewControllerStateInputPassword, 18 | BKPasscodeViewControllerStateReinputPassword 19 | } BKPasscodeViewControllerState; 20 | 21 | #define kBKPasscodeOneMinuteInSeconds (60) 22 | #define kBKPasscodeDefaultKeyboardHeight (216) 23 | 24 | @interface BKPasscodeViewController () 25 | 26 | @property (nonatomic, strong) BKShiftingView *shiftingView; 27 | 28 | @property (nonatomic) BKPasscodeViewControllerState currentState; 29 | @property (nonatomic, strong) NSString *oldPasscode; 30 | @property (nonatomic, strong) NSString *theNewPasscode; 31 | @property (nonatomic, strong) NSTimer *lockStateUpdateTimer; 32 | @property (nonatomic) CGFloat keyboardHeight; 33 | @property (nonatomic, strong) AFViewShaker *viewShaker; 34 | 35 | @property (nonatomic) BOOL promptingTouchID; 36 | 37 | @end 38 | 39 | @implementation BKPasscodeViewController 40 | 41 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 42 | { 43 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 44 | if (self) { 45 | // init state 46 | _type = BKPasscodeViewControllerNewPasscodeType; 47 | _currentState = BKPasscodeViewControllerStateInputPassword; 48 | 49 | // create shifting view 50 | self.shiftingView = [[BKShiftingView alloc] init]; 51 | self.shiftingView.autoresizingMask = UIViewAutoresizingFlexibleWidth; 52 | self.shiftingView.currentView = [self instantiatePasscodeInputView]; 53 | 54 | // keyboard notifications 55 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveKeyboardWillShowHideNotification:) name:UIKeyboardWillShowNotification object:nil]; 56 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveKeyboardWillShowHideNotification:) name:UIKeyboardWillHideNotification object:nil]; 57 | 58 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveApplicationWillEnterForegroundNotification:) 59 | name:UIApplicationWillEnterForegroundNotification 60 | object:nil]; 61 | 62 | self.keyboardHeight = kBKPasscodeDefaultKeyboardHeight; // sometimes keyboard notification is not posted at all. so setting default value. 63 | } 64 | return self; 65 | } 66 | 67 | - (void)dealloc 68 | { 69 | [self.lockStateUpdateTimer invalidate]; 70 | self.lockStateUpdateTimer = nil; 71 | 72 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 73 | } 74 | 75 | - (void)setType:(BKPasscodeViewControllerType)type 76 | { 77 | if (_type == type) { 78 | return; 79 | } 80 | 81 | _type = type; 82 | 83 | switch (type) { 84 | case BKPasscodeViewControllerNewPasscodeType: 85 | self.currentState = BKPasscodeViewControllerStateInputPassword; 86 | break; 87 | default: 88 | self.currentState = BKPasscodeViewControllerStateCheckPassword; 89 | break; 90 | } 91 | } 92 | 93 | - (BKPasscodeInputView *)passcodeInputView 94 | { 95 | if (NO == [self.shiftingView.currentView isKindOfClass:[BKPasscodeInputView class]]) { 96 | return nil; 97 | } 98 | 99 | return (BKPasscodeInputView *)self.shiftingView.currentView; 100 | } 101 | 102 | - (BKPasscodeInputView *)instantiatePasscodeInputView 103 | { 104 | BKPasscodeInputView *view = [[BKPasscodeInputView alloc] init]; 105 | view.delegate = self; 106 | view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 107 | 108 | return view; 109 | } 110 | 111 | - (void)customizePasscodeInputView:(BKPasscodeInputView *)aPasscodeInputView 112 | { 113 | } 114 | 115 | 116 | - (void)viewDidLoad 117 | { 118 | [super viewDidLoad]; 119 | 120 | [self.view setBackgroundColor:[UIColor colorWithRed:0.94 green:0.94 blue:0.96 alpha:1]]; 121 | 122 | [self updatePasscodeInputViewTitle:self.passcodeInputView]; 123 | 124 | [self customizePasscodeInputView:self.passcodeInputView]; 125 | 126 | [self.view addSubview:self.shiftingView]; 127 | 128 | [self lockIfNeeded]; 129 | } 130 | 131 | - (void)viewWillAppear:(BOOL)animated 132 | { 133 | [super viewWillAppear:animated]; 134 | 135 | if (self.passcodeInputView.isEnabled) { 136 | [self startTouchIDAuthenticationIfPossible]; 137 | } 138 | 139 | [self.passcodeInputView becomeFirstResponder]; 140 | } 141 | 142 | - (void)viewWillDisappear:(BOOL)animated 143 | { 144 | [super viewWillDisappear:animated]; 145 | 146 | [self.view endEditing:YES]; 147 | } 148 | 149 | - (void)viewDidLayoutSubviews 150 | { 151 | [super viewDidLayoutSubviews]; 152 | 153 | CGRect frame = self.view.bounds; 154 | 155 | CGFloat topBarOffset = 0; 156 | if ([self respondsToSelector:@selector(topLayoutGuide)]) { 157 | topBarOffset = [self.topLayoutGuide length]; 158 | } 159 | 160 | frame.origin.y += topBarOffset; 161 | frame.size.height -= (topBarOffset + self.keyboardHeight); 162 | 163 | self.shiftingView.frame = frame; 164 | } 165 | 166 | #pragma mark - Public methods 167 | 168 | - (void)setPasscodeStyle:(BKPasscodeInputViewPasscodeStyle)passcodeStyle 169 | { 170 | self.passcodeInputView.passcodeStyle = passcodeStyle; 171 | } 172 | 173 | - (BKPasscodeInputViewPasscodeStyle)passcodeStyle 174 | { 175 | return self.passcodeInputView.passcodeStyle; 176 | } 177 | 178 | - (void)setKeyboardType:(UIKeyboardType)keyboardType 179 | { 180 | self.passcodeInputView.keyboardType = keyboardType; 181 | } 182 | 183 | - (UIKeyboardType)keyboardType 184 | { 185 | return self.passcodeInputView.keyboardType; 186 | } 187 | 188 | - (void)showLockMessageWithLockUntilDate:(NSDate *)lockUntil 189 | { 190 | NSTimeInterval timeInterval = [lockUntil timeIntervalSinceNow]; 191 | NSUInteger minutes = ceilf(timeInterval / 60.0f); 192 | 193 | BKPasscodeInputView *inputView = self.passcodeInputView; 194 | inputView.enabled = NO; 195 | 196 | if (minutes == 1) { 197 | inputView.title = NSLocalizedStringFromTable(@"Try again in 1 minute", @"BKPasscodeView", @"1분 후에 다시 시도"); 198 | } else { 199 | inputView.title = [NSString stringWithFormat:NSLocalizedStringFromTable(@"Try again in %d minutes", @"BKPasscodeView", @"%d분 후에 다시 시도"), minutes]; 200 | } 201 | 202 | NSUInteger numberOfFailedAttempts = [self.delegate passcodeViewControllerNumberOfFailedAttempts:self]; 203 | 204 | [self showFailedAttemptsCount:numberOfFailedAttempts inputView:inputView]; 205 | 206 | if (self.lockStateUpdateTimer == nil) { 207 | 208 | NSTimeInterval delay = timeInterval + kBKPasscodeOneMinuteInSeconds - (kBKPasscodeOneMinuteInSeconds * (NSTimeInterval)minutes); 209 | 210 | self.lockStateUpdateTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:delay] 211 | interval:60.f 212 | target:self 213 | selector:@selector(lockStateUpdateTimerFired:) 214 | userInfo:nil 215 | repeats:YES]; 216 | 217 | [[NSRunLoop currentRunLoop] addTimer:self.lockStateUpdateTimer forMode:NSDefaultRunLoopMode]; 218 | } 219 | } 220 | 221 | - (BOOL)lockIfNeeded 222 | { 223 | if (self.currentState != BKPasscodeViewControllerStateCheckPassword) { 224 | return NO; 225 | } 226 | 227 | if (NO == [self.delegate respondsToSelector:@selector(passcodeViewControllerLockUntilDate:)]) { 228 | return NO; 229 | } 230 | 231 | NSDate *lockUntil = [self.delegate passcodeViewControllerLockUntilDate:self]; 232 | if (lockUntil == nil || [lockUntil timeIntervalSinceNow] < 0) { 233 | return NO; 234 | } 235 | 236 | [self showLockMessageWithLockUntilDate:lockUntil]; 237 | 238 | return YES; 239 | } 240 | 241 | - (void)updateLockMessageOrUnlockIfNeeded 242 | { 243 | if (self.currentState != BKPasscodeViewControllerStateCheckPassword) { 244 | return; 245 | } 246 | 247 | if (NO == [self.delegate respondsToSelector:@selector(passcodeViewControllerLockUntilDate:)]) { 248 | return; 249 | } 250 | 251 | BKPasscodeInputView *inputView = self.passcodeInputView; 252 | 253 | NSDate *lockUntil = [self.delegate passcodeViewControllerLockUntilDate:self]; 254 | 255 | if (lockUntil == nil || [lockUntil timeIntervalSinceNow] < 0) { 256 | 257 | // invalidate timer 258 | [self.lockStateUpdateTimer invalidate]; 259 | self.lockStateUpdateTimer = nil; 260 | 261 | [self updatePasscodeInputViewTitle:inputView]; 262 | 263 | inputView.enabled = YES; 264 | 265 | } else { 266 | [self showLockMessageWithLockUntilDate:lockUntil]; 267 | } 268 | } 269 | 270 | - (void)lockStateUpdateTimerFired:(NSTimer *)timer 271 | { 272 | [self updateLockMessageOrUnlockIfNeeded]; 273 | } 274 | 275 | - (void)startTouchIDAuthenticationIfPossible 276 | { 277 | [self startTouchIDAuthenticationIfPossible:nil]; 278 | } 279 | 280 | - (void)startTouchIDAuthenticationIfPossible:(void (^)(BOOL))aCompletionBlock 281 | { 282 | if (NO == [self canAuthenticateWithTouchID]) { 283 | if (aCompletionBlock) { 284 | aCompletionBlock(NO); 285 | } 286 | return; 287 | } 288 | 289 | self.promptingTouchID = YES; 290 | 291 | [self.touchIDManager loadPasscodeWithCompletionBlock:^(NSString *passcode) { 292 | 293 | self.promptingTouchID = NO; 294 | 295 | if (passcode) { 296 | 297 | self.passcodeInputView.passcode = passcode; 298 | 299 | [self passcodeInputViewDidFinish:self.passcodeInputView]; 300 | } 301 | 302 | if (aCompletionBlock) { 303 | aCompletionBlock(YES); 304 | } 305 | }]; 306 | } 307 | 308 | #pragma mark - Private methods 309 | 310 | - (void)updatePasscodeInputViewTitle:(BKPasscodeInputView *)passcodeInputView 311 | { 312 | switch (self.currentState) { 313 | case BKPasscodeViewControllerStateCheckPassword: 314 | if (self.type == BKPasscodeViewControllerChangePasscodeType) { 315 | passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your old passcode", @"BKPasscodeView", @"기존 암호 입력"); 316 | } else { 317 | passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your passcode", @"BKPasscodeView", @"암호 입력"); 318 | } 319 | break; 320 | 321 | case BKPasscodeViewControllerStateInputPassword: 322 | if (self.type == BKPasscodeViewControllerChangePasscodeType) { 323 | passcodeInputView.title = NSLocalizedStringFromTable(@"Enter your new passcode", @"BKPasscodeView", @"새로운 암호 입력"); 324 | } else { 325 | passcodeInputView.title = NSLocalizedStringFromTable(@"Enter a passcode", @"BKPasscodeView", @"암호 입력"); 326 | } 327 | break; 328 | 329 | case BKPasscodeViewControllerStateReinputPassword: 330 | passcodeInputView.title = NSLocalizedStringFromTable(@"Re-enter your passcode", @"BKPasscodeView", @"암호 재입력"); 331 | break; 332 | 333 | default: 334 | break; 335 | } 336 | } 337 | 338 | - (void)showFailedAttemptsCount:(NSUInteger)failCount inputView:(BKPasscodeInputView *)aInputView 339 | { 340 | if (failCount == 0) { 341 | aInputView.errorMessage = NSLocalizedStringFromTable(@"Invalid Passcode", @"BKPasscodeView", @"잘못된 암호"); 342 | } else if (failCount == 1) { 343 | aInputView.errorMessage = NSLocalizedStringFromTable(@"1 Failed Passcode Attempt", @"BKPasscodeView", @"1번의 암호 입력 시도 실패"); 344 | } else { 345 | aInputView.errorMessage = [NSString stringWithFormat:NSLocalizedStringFromTable(@"%d Failed Passcode Attempts", @"BKPasscodeView", @"%d번의 암호 입력 시도 실패"), failCount]; 346 | } 347 | } 348 | 349 | - (void)showTouchIDSwitchView 350 | { 351 | BKTouchIDSwitchView *view = [[BKTouchIDSwitchView alloc] init]; 352 | view.delegate = self; 353 | view.touchIDSwitch.on = self.touchIDManager.isTouchIDEnabled; 354 | 355 | [self.shiftingView showView:view withDirection:BKShiftingDirectionForward]; 356 | } 357 | 358 | - (BOOL)canAuthenticateWithTouchID 359 | { 360 | if (NO == [BKTouchIDManager canUseTouchID]) { 361 | return NO; 362 | } 363 | 364 | if (self.type != BKPasscodeViewControllerCheckPasscodeType) { 365 | return NO; 366 | } 367 | 368 | if (nil == self.touchIDManager || NO == self.touchIDManager.isTouchIDEnabled) { 369 | return NO; 370 | } 371 | 372 | if (self.promptingTouchID) { 373 | return NO; 374 | } 375 | 376 | if ([UIApplication sharedApplication].applicationState == UIApplicationStateInactive) { 377 | return NO; 378 | } 379 | 380 | return YES; 381 | } 382 | 383 | #pragma mark - BKPasscodeInputViewDelegate 384 | 385 | - (void)passcodeInputViewDidFinish:(BKPasscodeInputView *)aInputView 386 | { 387 | NSString *passcode = aInputView.passcode; 388 | 389 | switch (self.currentState) { 390 | case BKPasscodeViewControllerStateCheckPassword: 391 | { 392 | NSAssert([self.delegate respondsToSelector:@selector(passcodeViewController:authenticatePasscode:resultHandler:)], 393 | @"delegate must implement passcodeViewController:authenticatePasscode:resultHandler:"); 394 | 395 | [self.delegate passcodeViewController:self authenticatePasscode:passcode resultHandler:^(BOOL succeed) { 396 | 397 | NSAssert([NSThread isMainThread], @"you must invoke result handler in main thread."); 398 | 399 | if (succeed) { 400 | 401 | if (self.type == BKPasscodeViewControllerChangePasscodeType) { 402 | 403 | self.oldPasscode = passcode; 404 | self.currentState = BKPasscodeViewControllerStateInputPassword; 405 | 406 | BKPasscodeInputView *newPasscodeInputView = [self.passcodeInputView copy]; 407 | 408 | [self customizePasscodeInputView:newPasscodeInputView]; 409 | 410 | [self updatePasscodeInputViewTitle:newPasscodeInputView]; 411 | [self.shiftingView showView:newPasscodeInputView withDirection:BKShiftingDirectionForward]; 412 | 413 | [self.passcodeInputView becomeFirstResponder]; 414 | 415 | } else { 416 | 417 | [self.delegate passcodeViewController:self didFinishWithPasscode:passcode]; 418 | 419 | } 420 | 421 | } else { 422 | 423 | if ([self.delegate respondsToSelector:@selector(passcodeViewControllerDidFailAttempt:)]) { 424 | [self.delegate passcodeViewControllerDidFailAttempt:self]; 425 | } 426 | 427 | NSUInteger failCount = 0; 428 | 429 | if ([self.delegate respondsToSelector:@selector(passcodeViewControllerNumberOfFailedAttempts:)]) { 430 | failCount = [self.delegate passcodeViewControllerNumberOfFailedAttempts:self]; 431 | } 432 | 433 | [self showFailedAttemptsCount:failCount inputView:aInputView]; 434 | 435 | // reset entered passcode 436 | aInputView.passcode = nil; 437 | 438 | // shake 439 | self.viewShaker = [[AFViewShaker alloc] initWithView:aInputView.passcodeField]; 440 | [self.viewShaker shakeWithDuration:0.5f completion:nil]; 441 | 442 | // lock if needed 443 | if ([self.delegate respondsToSelector:@selector(passcodeViewControllerLockUntilDate:)]) { 444 | NSDate *lockUntilDate = [self.delegate passcodeViewControllerLockUntilDate:self]; 445 | if (lockUntilDate != nil) { 446 | [self showLockMessageWithLockUntilDate:lockUntilDate]; 447 | } 448 | } 449 | 450 | } 451 | }]; 452 | 453 | break; 454 | } 455 | case BKPasscodeViewControllerStateInputPassword: 456 | { 457 | if (self.type == BKPasscodeViewControllerChangePasscodeType && [self.oldPasscode isEqualToString:passcode]) { 458 | 459 | aInputView.passcode = nil; 460 | aInputView.message = NSLocalizedStringFromTable(@"Enter a different passcode. Cannot re-use the same passcode.", @"BKPasscodeView", @"다른 암호를 입력하십시오. 동일한 암호를 다시 사용할 수 없습니다."); 461 | 462 | } else { 463 | 464 | self.theNewPasscode = passcode; 465 | self.currentState = BKPasscodeViewControllerStateReinputPassword; 466 | 467 | BKPasscodeInputView *newPasscodeInputView = [self.passcodeInputView copy]; 468 | 469 | [self customizePasscodeInputView:newPasscodeInputView]; 470 | 471 | [self updatePasscodeInputViewTitle:newPasscodeInputView]; 472 | [self.shiftingView showView:newPasscodeInputView withDirection:BKShiftingDirectionForward]; 473 | 474 | [self.passcodeInputView becomeFirstResponder]; 475 | } 476 | 477 | break; 478 | } 479 | case BKPasscodeViewControllerStateReinputPassword: 480 | { 481 | if ([passcode isEqualToString:self.theNewPasscode]) { 482 | 483 | if (self.touchIDManager && [BKTouchIDManager canUseTouchID]) { 484 | [self showTouchIDSwitchView]; 485 | } else { 486 | [self.delegate passcodeViewController:self didFinishWithPasscode:passcode]; 487 | } 488 | 489 | } else { 490 | 491 | self.currentState = BKPasscodeViewControllerStateInputPassword; 492 | 493 | BKPasscodeInputView *newPasscodeInputView = [self.passcodeInputView copy]; 494 | 495 | [self customizePasscodeInputView:newPasscodeInputView]; 496 | 497 | [self updatePasscodeInputViewTitle:newPasscodeInputView]; 498 | 499 | newPasscodeInputView.message = NSLocalizedStringFromTable(@"Passcodes did not match.\nTry again.", @"BKPasscodeView", @"암호가 일치하지 않습니다.\n다시 시도하십시오."); 500 | 501 | [self.shiftingView showView:newPasscodeInputView withDirection:BKShiftingDirectionBackward]; 502 | 503 | [self.passcodeInputView becomeFirstResponder]; 504 | } 505 | break; 506 | } 507 | default: 508 | break; 509 | } 510 | } 511 | 512 | #pragma mark - BKTouchIDSwitchViewDelegate 513 | 514 | - (void)touchIDSwitchViewDidPressDoneButton:(BKTouchIDSwitchView *)view 515 | { 516 | BOOL enabled = view.touchIDSwitch.isOn; 517 | 518 | if (enabled) { 519 | 520 | [self.touchIDManager savePasscode:self.theNewPasscode completionBlock:^(BOOL success) { 521 | if (success) { 522 | [self.delegate passcodeViewController:self didFinishWithPasscode:self.theNewPasscode]; 523 | } else { 524 | if ([self.delegate respondsToSelector:@selector(passcodeViewControllerDidFailTouchIDKeychainOperation:)]) { 525 | [self.delegate passcodeViewControllerDidFailTouchIDKeychainOperation:self]; 526 | } 527 | } 528 | }]; 529 | 530 | } else { 531 | 532 | [self.touchIDManager deletePasscodeWithCompletionBlock:^(BOOL success) { 533 | if (success) { 534 | [self.delegate passcodeViewController:self didFinishWithPasscode:self.theNewPasscode]; 535 | } else { 536 | if ([self.delegate respondsToSelector:@selector(passcodeViewControllerDidFailTouchIDKeychainOperation:)]) { 537 | [self.delegate passcodeViewControllerDidFailTouchIDKeychainOperation:self]; 538 | } 539 | } 540 | }]; 541 | } 542 | } 543 | 544 | #pragma mark - Notifications 545 | 546 | - (void)didReceiveKeyboardWillShowHideNotification:(NSNotification *)notification 547 | { 548 | CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 549 | 550 | if (SYSTEM_VERSION_LESS_THAN(@"8.0")) { 551 | UIInterfaceOrientation statusBarOrientation = [[UIApplication sharedApplication] statusBarOrientation]; 552 | self.keyboardHeight = UIInterfaceOrientationIsPortrait(statusBarOrientation) ? CGRectGetHeight(keyboardRect) : CGRectGetWidth(keyboardRect); 553 | } else { 554 | self.keyboardHeight = CGRectGetHeight(keyboardRect); 555 | } 556 | 557 | [self.view setNeedsLayout]; 558 | } 559 | 560 | - (void)didReceiveApplicationWillEnterForegroundNotification:(NSNotification *)notification 561 | { 562 | [self startTouchIDAuthenticationIfPossible]; 563 | } 564 | 565 | @end 566 | -------------------------------------------------------------------------------- /BKPasscodeView/BKShiftingView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKShiftingView.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 10. 11.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSUInteger, BKShiftingDirection) { 12 | BKShiftingDirectionForward, 13 | BKShiftingDirectionBackward, 14 | }; 15 | 16 | @interface BKShiftingView : UIView 17 | 18 | @property (nonatomic, strong) UIView *currentView; 19 | 20 | - (void)showView:(UIView *)view withDirection:(BKShiftingDirection)direction; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /BKPasscodeView/BKShiftingView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKShiftingView.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 10. 11.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKShiftingView.h" 10 | 11 | @implementation BKShiftingView 12 | 13 | - (void)layoutSubviews 14 | { 15 | [super layoutSubviews]; 16 | 17 | self.currentView.frame = self.bounds; 18 | } 19 | 20 | - (void)setCurrentView:(UIView *)currentView 21 | { 22 | if (_currentView == currentView) { 23 | return; 24 | } 25 | 26 | [_currentView removeFromSuperview]; 27 | 28 | _currentView = currentView; 29 | 30 | if (currentView) { 31 | [self addSubview:currentView]; 32 | } 33 | 34 | [self setNeedsLayout]; 35 | } 36 | 37 | - (void)showView:(UIView *)view withDirection:(BKShiftingDirection)direction 38 | { 39 | UIView *oldView = self.currentView; 40 | oldView.userInteractionEnabled = NO; 41 | 42 | CGRect nextFrame = self.bounds; 43 | 44 | switch (direction) { 45 | case BKShiftingDirectionForward: 46 | nextFrame.origin.x = CGRectGetWidth(self.bounds); 47 | break; 48 | case BKShiftingDirectionBackward: 49 | nextFrame.origin.x = -CGRectGetWidth(self.bounds); 50 | break; 51 | } 52 | 53 | view.frame = nextFrame; 54 | 55 | [self addSubview:view]; 56 | 57 | // start animation 58 | [UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 59 | 60 | switch (direction) { 61 | case BKShiftingDirectionForward: 62 | oldView.frame = CGRectOffset(oldView.frame, -CGRectGetWidth(self.bounds), 0); 63 | view.frame = CGRectOffset(view.frame, -CGRectGetWidth(self.bounds), 0); 64 | break; 65 | case BKShiftingDirectionBackward: 66 | oldView.frame = CGRectOffset(oldView.frame, CGRectGetWidth(self.bounds), 0); 67 | view.frame = CGRectOffset(view.frame, CGRectGetWidth(self.bounds), 0); 68 | break; 69 | } 70 | 71 | } completion:^(BOOL finished) { 72 | 73 | [oldView removeFromSuperview]; 74 | 75 | }]; 76 | 77 | _currentView = view; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /BKPasscodeView/BKTouchIDManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKTouchIDManager.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 10. 12.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BKTouchIDManager : NSObject 12 | 13 | @property (nonatomic, strong, readonly) NSString *keychainServiceName; 14 | @property (nonatomic, strong) NSString *promptText; 15 | @property (nonatomic, readonly, getter=isTouchIDEnabled) BOOL touchIDEnabled; 16 | 17 | + (BOOL)canUseTouchID; 18 | 19 | - (instancetype)initWithKeychainServiceName:(NSString *)serviceName; 20 | 21 | - (void)savePasscode:(NSString *)passcode completionBlock:(void(^)(BOOL success))completionBlock; 22 | 23 | - (void)loadPasscodeWithCompletionBlock:(void(^)(NSString *passcode))completionBlock; 24 | 25 | - (void)deletePasscodeWithCompletionBlock:(void(^)(BOOL success))completionBlock; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /BKPasscodeView/BKTouchIDManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKTouchIDManager.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 10. 12.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKTouchIDManager.h" 10 | #import 11 | 12 | static NSString *const BKTouchIDManagerPasscodeAccountName = @"passcode"; 13 | static NSString *const BKTouchIDManagerTouchIDEnabledAccountName = @"enabled"; 14 | 15 | @interface BKTouchIDManager () { 16 | dispatch_queue_t _queue; 17 | } 18 | 19 | @property (nonatomic, strong) NSString *keychainServiceName; 20 | 21 | @end 22 | 23 | @implementation BKTouchIDManager 24 | 25 | - (instancetype)initWithKeychainServiceName:(NSString *)serviceName 26 | { 27 | self = [super init]; 28 | if (self) { 29 | 30 | _queue = dispatch_queue_create("BKTouchIDManagerQueue", DISPATCH_QUEUE_SERIAL); 31 | 32 | NSParameterAssert(serviceName); 33 | 34 | self.keychainServiceName = serviceName; 35 | } 36 | return self; 37 | } 38 | 39 | + (BOOL)canUseTouchID 40 | { 41 | if (![LAContext class]) { 42 | return NO; 43 | } 44 | 45 | LAContext *context = [[LAContext alloc] init]; 46 | 47 | NSError *error = nil; 48 | BOOL result = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]; 49 | 50 | return result; 51 | } 52 | 53 | - (void)savePasscode:(NSString *)passcode completionBlock:(void(^)(BOOL success))completionBlock 54 | { 55 | NSParameterAssert(passcode); 56 | 57 | if (NO == [[self class] canUseTouchID]) { 58 | if (completionBlock) { 59 | completionBlock(NO); 60 | } 61 | return; 62 | } 63 | 64 | NSString *serviceName = self.keychainServiceName; 65 | NSData *passcodeData = [passcode dataUsingEncoding:NSUTF8StringEncoding]; 66 | 67 | dispatch_async(_queue, ^{ 68 | 69 | BOOL success = [[self class] saveKeychainItemWithServiceName:serviceName 70 | accountName:BKTouchIDManagerPasscodeAccountName 71 | data:passcodeData 72 | sacFlags:kSecAccessControlUserPresence]; 73 | 74 | if (success) { 75 | 76 | BOOL enabled = YES; 77 | 78 | success = [[self class] saveKeychainItemWithServiceName:serviceName 79 | accountName:BKTouchIDManagerTouchIDEnabledAccountName 80 | data:[NSData dataWithBytes:&enabled length:sizeof(BOOL)] 81 | sacFlags:0]; 82 | } 83 | 84 | if (completionBlock) { 85 | dispatch_async(dispatch_get_main_queue(), ^{ 86 | completionBlock(success); 87 | }); 88 | } 89 | }); 90 | } 91 | 92 | - (void)loadPasscodeWithCompletionBlock:(void (^)(NSString *))completionBlock 93 | { 94 | if (NO == [[self class] canUseTouchID]) { 95 | if (completionBlock) { 96 | completionBlock(nil); 97 | } 98 | return; 99 | } 100 | 101 | NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, 102 | (__bridge id)kSecAttrService: self.keychainServiceName, 103 | (__bridge id)kSecAttrAccount: BKTouchIDManagerPasscodeAccountName, 104 | (__bridge id)kSecReturnData: @YES }]; 105 | 106 | if (self.promptText) { 107 | query[(__bridge id)kSecUseOperationPrompt] = self.promptText; 108 | } 109 | 110 | dispatch_async(_queue, ^{ 111 | 112 | CFTypeRef dataTypeRef = NULL; 113 | 114 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef); 115 | 116 | NSString *result = nil; 117 | 118 | if (status == errSecSuccess) { 119 | 120 | NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef; 121 | result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding]; 122 | } 123 | 124 | if (completionBlock) { 125 | dispatch_async(dispatch_get_main_queue(), ^{ 126 | completionBlock(result); 127 | }); 128 | } 129 | }); 130 | } 131 | 132 | - (void)deletePasscodeWithCompletionBlock:(void (^)(BOOL))completionBlock 133 | { 134 | dispatch_async(_queue, ^{ 135 | 136 | BOOL success = ([[self class] deleteKeychainItemWithServiceName:self.keychainServiceName accountName:BKTouchIDManagerPasscodeAccountName] && 137 | [[self class] deleteKeychainItemWithServiceName:self.keychainServiceName accountName:BKTouchIDManagerTouchIDEnabledAccountName]); 138 | 139 | if (completionBlock) { 140 | dispatch_async(dispatch_get_main_queue(), ^{ 141 | completionBlock(success); 142 | }); 143 | } 144 | }); 145 | } 146 | 147 | - (BOOL)isTouchIDEnabled 148 | { 149 | NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, 150 | (__bridge id)kSecAttrService: self.keychainServiceName, 151 | (__bridge id)kSecAttrAccount: BKTouchIDManagerTouchIDEnabledAccountName, 152 | (__bridge id)kSecReturnData: @YES }; 153 | 154 | CFTypeRef dataTypeRef = NULL; 155 | 156 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef); 157 | 158 | if (status == errSecSuccess) { 159 | 160 | NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef; 161 | BOOL result; 162 | [resultData getBytes:&result length:sizeof(BOOL)]; 163 | 164 | return result; 165 | 166 | } else { 167 | return NO; 168 | } 169 | } 170 | 171 | #pragma mark - Static Methods 172 | 173 | + (BOOL)saveKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data sacFlags:(SecAccessControlCreateFlags)sacFlags 174 | { 175 | // try to update first 176 | BOOL success = [self updateKeychainItemWithServiceName:serviceName accountName:accountName data:data]; 177 | 178 | if (success) { 179 | return YES; 180 | } 181 | 182 | // try deleting when update failed (workaround for iOS 8 bug) 183 | [self deleteKeychainItemWithServiceName:serviceName accountName:accountName]; 184 | 185 | // try add 186 | return [self addKeychainItemWithServiceName:serviceName accountName:accountName data:data sacFlags:sacFlags]; 187 | } 188 | 189 | + (BOOL)addKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data sacFlags:(SecAccessControlCreateFlags)sacFlags 190 | { 191 | CFErrorRef error = NULL; 192 | SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, 193 | kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, 194 | sacFlags, &error); 195 | 196 | if (sacObject == NULL || error != NULL) { 197 | return NO; 198 | } 199 | 200 | NSDictionary *attributes = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, 201 | (__bridge id)kSecAttrService: serviceName, 202 | (__bridge id)kSecAttrAccount: accountName, 203 | (__bridge id)kSecValueData: data, 204 | (__bridge id)kSecUseNoAuthenticationUI: @YES, 205 | (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject }; 206 | 207 | OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil); 208 | 209 | return (status == errSecSuccess); 210 | } 211 | 212 | + (BOOL)updateKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName data:(NSData *)data 213 | { 214 | NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, 215 | (__bridge id)kSecAttrService: serviceName, 216 | (__bridge id)kSecAttrAccount: accountName }; 217 | 218 | NSDictionary *changes = @{ (__bridge id)kSecValueData: data }; 219 | 220 | OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes); 221 | 222 | return (status == errSecSuccess); 223 | } 224 | 225 | + (BOOL)deleteKeychainItemWithServiceName:(NSString *)serviceName accountName:(NSString *)accountName 226 | { 227 | NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, 228 | (__bridge id)kSecAttrService: serviceName, 229 | (__bridge id)kSecAttrAccount: accountName }; 230 | 231 | OSStatus status = SecItemDelete((__bridge CFDictionaryRef)(query)); 232 | 233 | return (status == errSecSuccess || status == errSecItemNotFound); 234 | } 235 | 236 | @end 237 | -------------------------------------------------------------------------------- /BKPasscodeView/BKTouchIDSwitchView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKTouchIDSwitchView.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 10. 11.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol BKTouchIDSwitchViewDelegate; 12 | 13 | 14 | @interface BKTouchIDSwitchView : UIView 15 | 16 | @property (nonatomic, weak) id delegate; 17 | 18 | @property (nonatomic, strong) UIView *switchBackgroundView; 19 | @property (nonatomic, strong) UILabel *messageLabel; 20 | @property (nonatomic, strong) UILabel *titleLabel; 21 | @property (nonatomic, strong) UISwitch *touchIDSwitch; 22 | @property (nonatomic, strong) UIButton *doneButton; 23 | 24 | @end 25 | 26 | 27 | @protocol BKTouchIDSwitchViewDelegate 28 | 29 | - (void)touchIDSwitchViewDidPressDoneButton:(BKTouchIDSwitchView *)view; 30 | 31 | @end -------------------------------------------------------------------------------- /BKPasscodeView/BKTouchIDSwitchView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKTouchIDSwitchView.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 10. 11.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKTouchIDSwitchView.h" 10 | 11 | @implementation BKTouchIDSwitchView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | [self _initialize]; 18 | } 19 | return self; 20 | } 21 | 22 | - (id)initWithCoder:(NSCoder *)aDecoder 23 | { 24 | self = [super initWithCoder:aDecoder]; 25 | if (self) { 26 | [self _initialize]; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)_initialize 32 | { 33 | self.switchBackgroundView = [[UIView alloc] init]; 34 | self.switchBackgroundView.backgroundColor = [UIColor whiteColor]; 35 | self.switchBackgroundView.layer.borderColor = [UIColor lightGrayColor].CGColor; 36 | self.switchBackgroundView.layer.borderWidth = .5f; 37 | [self addSubview:self.switchBackgroundView]; 38 | 39 | self.messageLabel = [[UILabel alloc] init]; 40 | self.messageLabel.numberOfLines = 0; 41 | self.messageLabel.lineBreakMode = NSLineBreakByWordWrapping; 42 | self.messageLabel.textAlignment = NSTextAlignmentCenter; 43 | self.messageLabel.text = NSLocalizedStringFromTable(@"Do you want to use Touch ID for authentication?", @"BKPasscodeView", @"Touch ID를 사용하시겠습니까?"); 44 | self.messageLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; 45 | [self addSubview:self.messageLabel]; 46 | 47 | self.titleLabel = [[UILabel alloc] init]; 48 | self.titleLabel.text = NSLocalizedStringFromTable(@"Enable Touch ID", @"BKPasscodeView", @"Touch ID 사용"); 49 | self.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; 50 | [self addSubview:self.titleLabel]; 51 | 52 | self.touchIDSwitch = [[UISwitch alloc] init]; 53 | [self addSubview:self.touchIDSwitch]; 54 | 55 | self.doneButton = [UIButton buttonWithType:UIButtonTypeSystem]; 56 | [self.doneButton.titleLabel setFont:[UIFont systemFontOfSize:20.f]]; 57 | [self.doneButton setTitle:NSLocalizedStringFromTable(@"Done", @"BKPasscodeView", @"확인") forState:UIControlStateNormal]; 58 | [self.doneButton addTarget:self action:@selector(doneButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 59 | [self addSubview:self.doneButton]; 60 | } 61 | 62 | - (void)layoutSubviews 63 | { 64 | [super layoutSubviews]; 65 | 66 | UIEdgeInsets contentInset = UIEdgeInsetsMake(20, 20, 20, 20); 67 | static CGFloat verticalSpaces[] = { 40, 30 }; 68 | 69 | CGRect contentBounds = UIEdgeInsetsInsetRect(self.bounds, contentInset); 70 | 71 | self.messageLabel.frame = CGRectMake(0, 0, CGRectGetWidth(contentBounds), 0); 72 | [self.messageLabel sizeToFit]; 73 | 74 | [self.titleLabel sizeToFit]; 75 | 76 | [self.doneButton sizeToFit]; 77 | 78 | CGFloat contentHeight = (CGRectGetHeight(self.messageLabel.frame) + verticalSpaces[0] + 79 | CGRectGetHeight(self.touchIDSwitch.frame) + verticalSpaces[1] + 80 | CGRectGetHeight(self.doneButton.frame)); 81 | 82 | CGFloat offsetY = floorf((CGRectGetHeight(self.frame) - contentHeight) * 0.5f); 83 | 84 | CGRect rect; 85 | 86 | rect = self.messageLabel.frame; 87 | rect.origin = CGPointMake(contentInset.left, offsetY); 88 | rect.size.width = CGRectGetWidth(contentBounds); 89 | self.messageLabel.frame = rect; 90 | 91 | offsetY += CGRectGetHeight(rect) + verticalSpaces[0]; 92 | 93 | rect = self.touchIDSwitch.frame; 94 | rect.origin = CGPointMake(CGRectGetMaxX(contentBounds) - CGRectGetWidth(self.touchIDSwitch.frame), offsetY); 95 | self.touchIDSwitch.frame = rect; 96 | 97 | rect = self.titleLabel.frame; 98 | rect.origin = CGPointMake(contentInset.left, offsetY); 99 | rect.size.height = CGRectGetHeight(self.touchIDSwitch.frame); 100 | self.titleLabel.frame = rect; 101 | 102 | offsetY += CGRectGetHeight(rect) + verticalSpaces[1]; 103 | 104 | rect = self.doneButton.frame; 105 | rect.size.width += 10; 106 | rect.size.height += 10; 107 | rect.origin.x = floorf((CGRectGetWidth(self.frame) - CGRectGetWidth(rect)) * 0.5f); 108 | rect.origin.y = offsetY; 109 | self.doneButton.frame = rect; 110 | 111 | self.switchBackgroundView.frame = CGRectMake(-1, 112 | CGRectGetMinY(self.touchIDSwitch.frame) - 12, 113 | CGRectGetWidth(self.frame) + 2, 114 | CGRectGetHeight(self.touchIDSwitch.frame) + 24); 115 | 116 | } 117 | 118 | - (void)doneButtonPressed:(id)sender 119 | { 120 | if ([self.delegate respondsToSelector:@selector(touchIDSwitchViewDidPressDoneButton:)]) { 121 | [self.delegate touchIDSwitchViewDidPressDoneButton:self]; 122 | } 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 26FC41C1C1DFBD9A964AD247 /* libPods-BKPasscodeViewDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17007E61260EF53C48FD29F0 /* libPods-BKPasscodeViewDemo.a */; }; 11 | D51F3F3844011A1D2CE2B865 /* libPods-BKPasscodeViewDemoTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3478068038FE0219C1AC2FAF /* libPods-BKPasscodeViewDemoTests.a */; }; 12 | DF1327931927DA4900B5D8CB /* BKPasscodeField.m in Sources */ = {isa = PBXBuildFile; fileRef = DF13278C1927DA4900B5D8CB /* BKPasscodeField.m */; }; 13 | DF1327941927DA4900B5D8CB /* BKPasscodeInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = DF13278E1927DA4900B5D8CB /* BKPasscodeInputView.m */; }; 14 | DF1327951927DA4900B5D8CB /* BKPasscodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DF1327901927DA4900B5D8CB /* BKPasscodeViewController.m */; }; 15 | DF17CD8119E99C28001FA035 /* BKTouchIDManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DF17CD8019E99C28001FA035 /* BKTouchIDManager.m */; }; 16 | DF1F5D801A1B878C00B13631 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF1F5D7F1A1B878C00B13631 /* LocalAuthentication.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 17 | DF23B10319E8567C00162483 /* BKShiftingView.m in Sources */ = {isa = PBXBuildFile; fileRef = DF23B10219E8567C00162483 /* BKShiftingView.m */; }; 18 | DF23B10619E85EB400162483 /* BKTouchIDSwitchView.m in Sources */ = {isa = PBXBuildFile; fileRef = DF23B10519E85EB400162483 /* BKTouchIDSwitchView.m */; }; 19 | DF346E4F190BF66600E850F9 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DF346E4E190BF66600E850F9 /* MainViewController.m */; }; 20 | DF7AFC3A19F3CD6500FEB0F9 /* BKPasscodeView.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF7AFC3819F3CD6500FEB0F9 /* BKPasscodeView.strings */; }; 21 | DFB107B51903ECF70038FB05 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB107B41903ECF70038FB05 /* Foundation.framework */; }; 22 | DFB107B71903ECF70038FB05 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB107B61903ECF70038FB05 /* CoreGraphics.framework */; }; 23 | DFB107B91903ECF70038FB05 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB107B81903ECF70038FB05 /* UIKit.framework */; }; 24 | DFB107BF1903ECF70038FB05 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DFB107BD1903ECF70038FB05 /* InfoPlist.strings */; }; 25 | DFB107C11903ECF70038FB05 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB107C01903ECF70038FB05 /* main.m */; }; 26 | DFB107C51903ECF70038FB05 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB107C41903ECF70038FB05 /* AppDelegate.m */; }; 27 | DFB107C71903ECF70038FB05 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DFB107C61903ECF70038FB05 /* Images.xcassets */; }; 28 | DFB107CE1903ECF70038FB05 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB107CD1903ECF70038FB05 /* XCTest.framework */; }; 29 | DFB107CF1903ECF70038FB05 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB107B41903ECF70038FB05 /* Foundation.framework */; }; 30 | DFB107D01903ECF70038FB05 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB107B81903ECF70038FB05 /* UIKit.framework */; }; 31 | DFB107D81903ECF70038FB05 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DFB107D61903ECF70038FB05 /* InfoPlist.strings */; }; 32 | DFB107DA1903ECF70038FB05 /* BKPasscodeViewDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB107D91903ECF70038FB05 /* BKPasscodeViewDemoTests.m */; }; 33 | DFD1DB66198D27D0005BC7AB /* BKCustomPasscodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DFD1DB64198D27D0005BC7AB /* BKCustomPasscodeViewController.m */; }; 34 | DFD1DB69198D2809005BC7AB /* BKPasscodeLockScreenManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DFD1DB68198D2809005BC7AB /* BKPasscodeLockScreenManager.m */; }; 35 | DFD1DB6C198D4A14005BC7AB /* BKPasscodeDummyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DFD1DB6B198D4A14005BC7AB /* BKPasscodeDummyViewController.m */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXContainerItemProxy section */ 39 | DFB107D11903ECF70038FB05 /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = DFB107A91903ECF70038FB05 /* Project object */; 42 | proxyType = 1; 43 | remoteGlobalIDString = DFB107B01903ECF70038FB05; 44 | remoteInfo = BKPasscodeViewDemo; 45 | }; 46 | /* End PBXContainerItemProxy section */ 47 | 48 | /* Begin PBXFileReference section */ 49 | 17007E61260EF53C48FD29F0 /* libPods-BKPasscodeViewDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BKPasscodeViewDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 3478068038FE0219C1AC2FAF /* libPods-BKPasscodeViewDemoTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BKPasscodeViewDemoTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 3CA8D7725E96F2D794730EB4 /* Pods-BKPasscodeViewDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BKPasscodeViewDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-BKPasscodeViewDemo/Pods-BKPasscodeViewDemo.release.xcconfig"; sourceTree = ""; }; 52 | 8292B44A2B577048F6F87739 /* Pods-BKPasscodeViewDemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BKPasscodeViewDemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-BKPasscodeViewDemoTests/Pods-BKPasscodeViewDemoTests.release.xcconfig"; sourceTree = ""; }; 53 | 9778CF9C5B2DD6DB5534C67D /* Pods-BKPasscodeViewDemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BKPasscodeViewDemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BKPasscodeViewDemoTests/Pods-BKPasscodeViewDemoTests.debug.xcconfig"; sourceTree = ""; }; 54 | DF13278B1927DA4900B5D8CB /* BKPasscodeField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKPasscodeField.h; sourceTree = ""; }; 55 | DF13278C1927DA4900B5D8CB /* BKPasscodeField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKPasscodeField.m; sourceTree = ""; }; 56 | DF13278D1927DA4900B5D8CB /* BKPasscodeInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKPasscodeInputView.h; sourceTree = ""; }; 57 | DF13278E1927DA4900B5D8CB /* BKPasscodeInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKPasscodeInputView.m; sourceTree = ""; }; 58 | DF13278F1927DA4900B5D8CB /* BKPasscodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKPasscodeViewController.h; sourceTree = ""; }; 59 | DF1327901927DA4900B5D8CB /* BKPasscodeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKPasscodeViewController.m; sourceTree = ""; }; 60 | DF17CD7F19E99C28001FA035 /* BKTouchIDManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKTouchIDManager.h; sourceTree = ""; }; 61 | DF17CD8019E99C28001FA035 /* BKTouchIDManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKTouchIDManager.m; sourceTree = ""; }; 62 | DF1F5D7F1A1B878C00B13631 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; }; 63 | DF23B10119E8567C00162483 /* BKShiftingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKShiftingView.h; sourceTree = ""; }; 64 | DF23B10219E8567C00162483 /* BKShiftingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKShiftingView.m; sourceTree = ""; }; 65 | DF23B10419E85EB400162483 /* BKTouchIDSwitchView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKTouchIDSwitchView.h; sourceTree = ""; }; 66 | DF23B10519E85EB400162483 /* BKTouchIDSwitchView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKTouchIDSwitchView.m; sourceTree = ""; }; 67 | DF346E4D190BF66600E850F9 /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = ""; }; 68 | DF346E4E190BF66600E850F9 /* MainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = ""; }; 69 | DF72273F19DEF75E00994725 /* BKPasscodeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKPasscodeUtils.h; sourceTree = ""; }; 70 | DF7AFC3919F3CD6500FEB0F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/BKPasscodeView.strings; sourceTree = ""; }; 71 | DF7AFC3B19F3CD6A00FEB0F9 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/BKPasscodeView.strings; sourceTree = ""; }; 72 | DF831312190D0C7B00148DB2 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; 73 | DFB107B11903ECF70038FB05 /* BKPasscodeViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BKPasscodeViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | DFB107B41903ECF70038FB05 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 75 | DFB107B61903ECF70038FB05 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 76 | DFB107B81903ECF70038FB05 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 77 | DFB107BC1903ECF70038FB05 /* BKPasscodeViewDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BKPasscodeViewDemo-Info.plist"; sourceTree = ""; }; 78 | DFB107BE1903ECF70038FB05 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 79 | DFB107C01903ECF70038FB05 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 80 | DFB107C21903ECF70038FB05 /* BKPasscodeViewDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BKPasscodeViewDemo-Prefix.pch"; sourceTree = ""; }; 81 | DFB107C31903ECF70038FB05 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 82 | DFB107C41903ECF70038FB05 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 83 | DFB107C61903ECF70038FB05 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 84 | DFB107CC1903ECF70038FB05 /* BKPasscodeViewDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BKPasscodeViewDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 85 | DFB107CD1903ECF70038FB05 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 86 | DFB107D51903ECF70038FB05 /* BKPasscodeViewDemoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BKPasscodeViewDemoTests-Info.plist"; sourceTree = ""; }; 87 | DFB107D71903ECF70038FB05 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 88 | DFB107D91903ECF70038FB05 /* BKPasscodeViewDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BKPasscodeViewDemoTests.m; sourceTree = ""; }; 89 | DFD1DB64198D27D0005BC7AB /* BKCustomPasscodeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKCustomPasscodeViewController.m; sourceTree = ""; }; 90 | DFD1DB65198D27D0005BC7AB /* BKCustomPasscodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKCustomPasscodeViewController.h; sourceTree = ""; }; 91 | DFD1DB67198D2809005BC7AB /* BKPasscodeLockScreenManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKPasscodeLockScreenManager.h; sourceTree = ""; }; 92 | DFD1DB68198D2809005BC7AB /* BKPasscodeLockScreenManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKPasscodeLockScreenManager.m; sourceTree = ""; }; 93 | DFD1DB6A198D4A14005BC7AB /* BKPasscodeDummyViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BKPasscodeDummyViewController.h; sourceTree = ""; }; 94 | DFD1DB6B198D4A14005BC7AB /* BKPasscodeDummyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BKPasscodeDummyViewController.m; sourceTree = ""; }; 95 | F9196B564860108BF3278697 /* Pods-BKPasscodeViewDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BKPasscodeViewDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BKPasscodeViewDemo/Pods-BKPasscodeViewDemo.debug.xcconfig"; sourceTree = ""; }; 96 | /* End PBXFileReference section */ 97 | 98 | /* Begin PBXFrameworksBuildPhase section */ 99 | DFB107AE1903ECF70038FB05 /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | DF1F5D801A1B878C00B13631 /* LocalAuthentication.framework in Frameworks */, 104 | DFB107B71903ECF70038FB05 /* CoreGraphics.framework in Frameworks */, 105 | DFB107B91903ECF70038FB05 /* UIKit.framework in Frameworks */, 106 | DFB107B51903ECF70038FB05 /* Foundation.framework in Frameworks */, 107 | 26FC41C1C1DFBD9A964AD247 /* libPods-BKPasscodeViewDemo.a in Frameworks */, 108 | ); 109 | runOnlyForDeploymentPostprocessing = 0; 110 | }; 111 | DFB107C91903ECF70038FB05 /* Frameworks */ = { 112 | isa = PBXFrameworksBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | DFB107CE1903ECF70038FB05 /* XCTest.framework in Frameworks */, 116 | DFB107D01903ECF70038FB05 /* UIKit.framework in Frameworks */, 117 | DFB107CF1903ECF70038FB05 /* Foundation.framework in Frameworks */, 118 | D51F3F3844011A1D2CE2B865 /* libPods-BKPasscodeViewDemoTests.a in Frameworks */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXFrameworksBuildPhase section */ 123 | 124 | /* Begin PBXGroup section */ 125 | DF13278A1927DA4900B5D8CB /* BKPasscodeView */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | DF72273F19DEF75E00994725 /* BKPasscodeUtils.h */, 129 | DF13278B1927DA4900B5D8CB /* BKPasscodeField.h */, 130 | DF13278C1927DA4900B5D8CB /* BKPasscodeField.m */, 131 | DF13278D1927DA4900B5D8CB /* BKPasscodeInputView.h */, 132 | DF13278E1927DA4900B5D8CB /* BKPasscodeInputView.m */, 133 | DF23B10419E85EB400162483 /* BKTouchIDSwitchView.h */, 134 | DF23B10519E85EB400162483 /* BKTouchIDSwitchView.m */, 135 | DF23B10119E8567C00162483 /* BKShiftingView.h */, 136 | DF23B10219E8567C00162483 /* BKShiftingView.m */, 137 | DF13278F1927DA4900B5D8CB /* BKPasscodeViewController.h */, 138 | DF1327901927DA4900B5D8CB /* BKPasscodeViewController.m */, 139 | DFD1DB6A198D4A14005BC7AB /* BKPasscodeDummyViewController.h */, 140 | DFD1DB6B198D4A14005BC7AB /* BKPasscodeDummyViewController.m */, 141 | DFD1DB67198D2809005BC7AB /* BKPasscodeLockScreenManager.h */, 142 | DFD1DB68198D2809005BC7AB /* BKPasscodeLockScreenManager.m */, 143 | DF17CD7F19E99C28001FA035 /* BKTouchIDManager.h */, 144 | DF17CD8019E99C28001FA035 /* BKTouchIDManager.m */, 145 | ); 146 | path = BKPasscodeView; 147 | sourceTree = ""; 148 | }; 149 | DFB107A81903ECF70038FB05 = { 150 | isa = PBXGroup; 151 | children = ( 152 | DF13278A1927DA4900B5D8CB /* BKPasscodeView */, 153 | DFB107BA1903ECF70038FB05 /* BKPasscodeViewDemo */, 154 | DFB107D31903ECF70038FB05 /* BKPasscodeViewDemoTests */, 155 | DFB107B31903ECF70038FB05 /* Frameworks */, 156 | DFB107B21903ECF70038FB05 /* Products */, 157 | F34EE2212DDD0501D7449420 /* Pods */, 158 | ); 159 | sourceTree = ""; 160 | }; 161 | DFB107B21903ECF70038FB05 /* Products */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | DFB107B11903ECF70038FB05 /* BKPasscodeViewDemo.app */, 165 | DFB107CC1903ECF70038FB05 /* BKPasscodeViewDemoTests.xctest */, 166 | ); 167 | name = Products; 168 | sourceTree = ""; 169 | }; 170 | DFB107B31903ECF70038FB05 /* Frameworks */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | DF1F5D7F1A1B878C00B13631 /* LocalAuthentication.framework */, 174 | DFB107B41903ECF70038FB05 /* Foundation.framework */, 175 | DFB107B61903ECF70038FB05 /* CoreGraphics.framework */, 176 | DFB107B81903ECF70038FB05 /* UIKit.framework */, 177 | DFB107CD1903ECF70038FB05 /* XCTest.framework */, 178 | 17007E61260EF53C48FD29F0 /* libPods-BKPasscodeViewDemo.a */, 179 | 3478068038FE0219C1AC2FAF /* libPods-BKPasscodeViewDemoTests.a */, 180 | ); 181 | name = Frameworks; 182 | sourceTree = ""; 183 | }; 184 | DFB107BA1903ECF70038FB05 /* BKPasscodeViewDemo */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | DFB107C31903ECF70038FB05 /* AppDelegate.h */, 188 | DFB107C41903ECF70038FB05 /* AppDelegate.m */, 189 | DF346E4D190BF66600E850F9 /* MainViewController.h */, 190 | DF346E4E190BF66600E850F9 /* MainViewController.m */, 191 | DFD1DB65198D27D0005BC7AB /* BKCustomPasscodeViewController.h */, 192 | DFD1DB64198D27D0005BC7AB /* BKCustomPasscodeViewController.m */, 193 | DFB107C61903ECF70038FB05 /* Images.xcassets */, 194 | DFB107BB1903ECF70038FB05 /* Supporting Files */, 195 | ); 196 | path = BKPasscodeViewDemo; 197 | sourceTree = ""; 198 | }; 199 | DFB107BB1903ECF70038FB05 /* Supporting Files */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | DF7AFC3819F3CD6500FEB0F9 /* BKPasscodeView.strings */, 203 | DFB107BC1903ECF70038FB05 /* BKPasscodeViewDemo-Info.plist */, 204 | DFB107BD1903ECF70038FB05 /* InfoPlist.strings */, 205 | DFB107C01903ECF70038FB05 /* main.m */, 206 | DFB107C21903ECF70038FB05 /* BKPasscodeViewDemo-Prefix.pch */, 207 | ); 208 | name = "Supporting Files"; 209 | sourceTree = ""; 210 | }; 211 | DFB107D31903ECF70038FB05 /* BKPasscodeViewDemoTests */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | DFB107D91903ECF70038FB05 /* BKPasscodeViewDemoTests.m */, 215 | DFB107D41903ECF70038FB05 /* Supporting Files */, 216 | ); 217 | path = BKPasscodeViewDemoTests; 218 | sourceTree = ""; 219 | }; 220 | DFB107D41903ECF70038FB05 /* Supporting Files */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | DFB107D51903ECF70038FB05 /* BKPasscodeViewDemoTests-Info.plist */, 224 | DFB107D61903ECF70038FB05 /* InfoPlist.strings */, 225 | ); 226 | name = "Supporting Files"; 227 | sourceTree = ""; 228 | }; 229 | F34EE2212DDD0501D7449420 /* Pods */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | F9196B564860108BF3278697 /* Pods-BKPasscodeViewDemo.debug.xcconfig */, 233 | 3CA8D7725E96F2D794730EB4 /* Pods-BKPasscodeViewDemo.release.xcconfig */, 234 | 9778CF9C5B2DD6DB5534C67D /* Pods-BKPasscodeViewDemoTests.debug.xcconfig */, 235 | 8292B44A2B577048F6F87739 /* Pods-BKPasscodeViewDemoTests.release.xcconfig */, 236 | ); 237 | name = Pods; 238 | sourceTree = ""; 239 | }; 240 | /* End PBXGroup section */ 241 | 242 | /* Begin PBXNativeTarget section */ 243 | DFB107B01903ECF70038FB05 /* BKPasscodeViewDemo */ = { 244 | isa = PBXNativeTarget; 245 | buildConfigurationList = DFB107DD1903ECF70038FB05 /* Build configuration list for PBXNativeTarget "BKPasscodeViewDemo" */; 246 | buildPhases = ( 247 | 7BB30F5AC4C72AEE69F76EB7 /* [CP] Check Pods Manifest.lock */, 248 | DFB107AD1903ECF70038FB05 /* Sources */, 249 | DFB107AE1903ECF70038FB05 /* Frameworks */, 250 | DFB107AF1903ECF70038FB05 /* Resources */, 251 | 3FB92F7C2E575462079ACF7B /* [CP] Embed Pods Frameworks */, 252 | CFCF3D24DBD311E522870596 /* [CP] Copy Pods Resources */, 253 | ); 254 | buildRules = ( 255 | ); 256 | dependencies = ( 257 | ); 258 | name = BKPasscodeViewDemo; 259 | productName = BKPasscodeViewDemo; 260 | productReference = DFB107B11903ECF70038FB05 /* BKPasscodeViewDemo.app */; 261 | productType = "com.apple.product-type.application"; 262 | }; 263 | DFB107CB1903ECF70038FB05 /* BKPasscodeViewDemoTests */ = { 264 | isa = PBXNativeTarget; 265 | buildConfigurationList = DFB107E01903ECF70038FB05 /* Build configuration list for PBXNativeTarget "BKPasscodeViewDemoTests" */; 266 | buildPhases = ( 267 | BF9758529AAB5C37B2416A19 /* [CP] Check Pods Manifest.lock */, 268 | DFB107C81903ECF70038FB05 /* Sources */, 269 | DFB107C91903ECF70038FB05 /* Frameworks */, 270 | DFB107CA1903ECF70038FB05 /* Resources */, 271 | AC8B280F8EFC09C8C91A8ABF /* [CP] Embed Pods Frameworks */, 272 | F766989AA740427FFF5440D2 /* [CP] Copy Pods Resources */, 273 | ); 274 | buildRules = ( 275 | ); 276 | dependencies = ( 277 | DFB107D21903ECF70038FB05 /* PBXTargetDependency */, 278 | ); 279 | name = BKPasscodeViewDemoTests; 280 | productName = BKPasscodeViewDemoTests; 281 | productReference = DFB107CC1903ECF70038FB05 /* BKPasscodeViewDemoTests.xctest */; 282 | productType = "com.apple.product-type.bundle.unit-test"; 283 | }; 284 | /* End PBXNativeTarget section */ 285 | 286 | /* Begin PBXProject section */ 287 | DFB107A91903ECF70038FB05 /* Project object */ = { 288 | isa = PBXProject; 289 | attributes = { 290 | LastUpgradeCheck = 0510; 291 | ORGANIZATIONNAME = "Byungkook Jang"; 292 | TargetAttributes = { 293 | DFB107B01903ECF70038FB05 = { 294 | DevelopmentTeam = VK4AQ7RN7A; 295 | }; 296 | DFB107CB1903ECF70038FB05 = { 297 | TestTargetID = DFB107B01903ECF70038FB05; 298 | }; 299 | }; 300 | }; 301 | buildConfigurationList = DFB107AC1903ECF70038FB05 /* Build configuration list for PBXProject "BKPasscodeViewDemo" */; 302 | compatibilityVersion = "Xcode 3.2"; 303 | developmentRegion = English; 304 | hasScannedForEncodings = 0; 305 | knownRegions = ( 306 | en, 307 | ko, 308 | ja, 309 | ); 310 | mainGroup = DFB107A81903ECF70038FB05; 311 | productRefGroup = DFB107B21903ECF70038FB05 /* Products */; 312 | projectDirPath = ""; 313 | projectRoot = ""; 314 | targets = ( 315 | DFB107B01903ECF70038FB05 /* BKPasscodeViewDemo */, 316 | DFB107CB1903ECF70038FB05 /* BKPasscodeViewDemoTests */, 317 | ); 318 | }; 319 | /* End PBXProject section */ 320 | 321 | /* Begin PBXResourcesBuildPhase section */ 322 | DFB107AF1903ECF70038FB05 /* Resources */ = { 323 | isa = PBXResourcesBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | DF7AFC3A19F3CD6500FEB0F9 /* BKPasscodeView.strings in Resources */, 327 | DFB107BF1903ECF70038FB05 /* InfoPlist.strings in Resources */, 328 | DFB107C71903ECF70038FB05 /* Images.xcassets in Resources */, 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | }; 332 | DFB107CA1903ECF70038FB05 /* Resources */ = { 333 | isa = PBXResourcesBuildPhase; 334 | buildActionMask = 2147483647; 335 | files = ( 336 | DFB107D81903ECF70038FB05 /* InfoPlist.strings in Resources */, 337 | ); 338 | runOnlyForDeploymentPostprocessing = 0; 339 | }; 340 | /* End PBXResourcesBuildPhase section */ 341 | 342 | /* Begin PBXShellScriptBuildPhase section */ 343 | 3FB92F7C2E575462079ACF7B /* [CP] Embed Pods Frameworks */ = { 344 | isa = PBXShellScriptBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | ); 348 | inputPaths = ( 349 | ); 350 | name = "[CP] Embed Pods Frameworks"; 351 | outputPaths = ( 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | shellPath = /bin/sh; 355 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BKPasscodeViewDemo/Pods-BKPasscodeViewDemo-frameworks.sh\"\n"; 356 | showEnvVarsInLog = 0; 357 | }; 358 | 7BB30F5AC4C72AEE69F76EB7 /* [CP] Check Pods Manifest.lock */ = { 359 | isa = PBXShellScriptBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | ); 363 | inputPaths = ( 364 | ); 365 | name = "[CP] Check Pods Manifest.lock"; 366 | outputPaths = ( 367 | ); 368 | runOnlyForDeploymentPostprocessing = 0; 369 | shellPath = /bin/sh; 370 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 371 | showEnvVarsInLog = 0; 372 | }; 373 | AC8B280F8EFC09C8C91A8ABF /* [CP] Embed Pods Frameworks */ = { 374 | isa = PBXShellScriptBuildPhase; 375 | buildActionMask = 2147483647; 376 | files = ( 377 | ); 378 | inputPaths = ( 379 | ); 380 | name = "[CP] Embed Pods Frameworks"; 381 | outputPaths = ( 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | shellPath = /bin/sh; 385 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BKPasscodeViewDemoTests/Pods-BKPasscodeViewDemoTests-frameworks.sh\"\n"; 386 | showEnvVarsInLog = 0; 387 | }; 388 | BF9758529AAB5C37B2416A19 /* [CP] Check Pods Manifest.lock */ = { 389 | isa = PBXShellScriptBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | ); 393 | inputPaths = ( 394 | ); 395 | name = "[CP] Check Pods Manifest.lock"; 396 | outputPaths = ( 397 | ); 398 | runOnlyForDeploymentPostprocessing = 0; 399 | shellPath = /bin/sh; 400 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 401 | showEnvVarsInLog = 0; 402 | }; 403 | CFCF3D24DBD311E522870596 /* [CP] Copy Pods Resources */ = { 404 | isa = PBXShellScriptBuildPhase; 405 | buildActionMask = 2147483647; 406 | files = ( 407 | ); 408 | inputPaths = ( 409 | ); 410 | name = "[CP] Copy Pods Resources"; 411 | outputPaths = ( 412 | ); 413 | runOnlyForDeploymentPostprocessing = 0; 414 | shellPath = /bin/sh; 415 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BKPasscodeViewDemo/Pods-BKPasscodeViewDemo-resources.sh\"\n"; 416 | showEnvVarsInLog = 0; 417 | }; 418 | F766989AA740427FFF5440D2 /* [CP] Copy Pods Resources */ = { 419 | isa = PBXShellScriptBuildPhase; 420 | buildActionMask = 2147483647; 421 | files = ( 422 | ); 423 | inputPaths = ( 424 | ); 425 | name = "[CP] Copy Pods Resources"; 426 | outputPaths = ( 427 | ); 428 | runOnlyForDeploymentPostprocessing = 0; 429 | shellPath = /bin/sh; 430 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BKPasscodeViewDemoTests/Pods-BKPasscodeViewDemoTests-resources.sh\"\n"; 431 | showEnvVarsInLog = 0; 432 | }; 433 | /* End PBXShellScriptBuildPhase section */ 434 | 435 | /* Begin PBXSourcesBuildPhase section */ 436 | DFB107AD1903ECF70038FB05 /* Sources */ = { 437 | isa = PBXSourcesBuildPhase; 438 | buildActionMask = 2147483647; 439 | files = ( 440 | DFD1DB6C198D4A14005BC7AB /* BKPasscodeDummyViewController.m in Sources */, 441 | DF17CD8119E99C28001FA035 /* BKTouchIDManager.m in Sources */, 442 | DFD1DB66198D27D0005BC7AB /* BKCustomPasscodeViewController.m in Sources */, 443 | DF1327951927DA4900B5D8CB /* BKPasscodeViewController.m in Sources */, 444 | DF23B10619E85EB400162483 /* BKTouchIDSwitchView.m in Sources */, 445 | DFD1DB69198D2809005BC7AB /* BKPasscodeLockScreenManager.m in Sources */, 446 | DF1327931927DA4900B5D8CB /* BKPasscodeField.m in Sources */, 447 | DFB107C51903ECF70038FB05 /* AppDelegate.m in Sources */, 448 | DF346E4F190BF66600E850F9 /* MainViewController.m in Sources */, 449 | DF1327941927DA4900B5D8CB /* BKPasscodeInputView.m in Sources */, 450 | DF23B10319E8567C00162483 /* BKShiftingView.m in Sources */, 451 | DFB107C11903ECF70038FB05 /* main.m in Sources */, 452 | ); 453 | runOnlyForDeploymentPostprocessing = 0; 454 | }; 455 | DFB107C81903ECF70038FB05 /* Sources */ = { 456 | isa = PBXSourcesBuildPhase; 457 | buildActionMask = 2147483647; 458 | files = ( 459 | DFB107DA1903ECF70038FB05 /* BKPasscodeViewDemoTests.m in Sources */, 460 | ); 461 | runOnlyForDeploymentPostprocessing = 0; 462 | }; 463 | /* End PBXSourcesBuildPhase section */ 464 | 465 | /* Begin PBXTargetDependency section */ 466 | DFB107D21903ECF70038FB05 /* PBXTargetDependency */ = { 467 | isa = PBXTargetDependency; 468 | target = DFB107B01903ECF70038FB05 /* BKPasscodeViewDemo */; 469 | targetProxy = DFB107D11903ECF70038FB05 /* PBXContainerItemProxy */; 470 | }; 471 | /* End PBXTargetDependency section */ 472 | 473 | /* Begin PBXVariantGroup section */ 474 | DF7AFC3819F3CD6500FEB0F9 /* BKPasscodeView.strings */ = { 475 | isa = PBXVariantGroup; 476 | children = ( 477 | DF7AFC3919F3CD6500FEB0F9 /* en */, 478 | DF7AFC3B19F3CD6A00FEB0F9 /* ko */, 479 | ); 480 | name = BKPasscodeView.strings; 481 | sourceTree = ""; 482 | }; 483 | DFB107BD1903ECF70038FB05 /* InfoPlist.strings */ = { 484 | isa = PBXVariantGroup; 485 | children = ( 486 | DFB107BE1903ECF70038FB05 /* en */, 487 | ); 488 | name = InfoPlist.strings; 489 | sourceTree = ""; 490 | }; 491 | DFB107D61903ECF70038FB05 /* InfoPlist.strings */ = { 492 | isa = PBXVariantGroup; 493 | children = ( 494 | DFB107D71903ECF70038FB05 /* en */, 495 | DF831312190D0C7B00148DB2 /* ko */, 496 | ); 497 | name = InfoPlist.strings; 498 | sourceTree = ""; 499 | }; 500 | /* End PBXVariantGroup section */ 501 | 502 | /* Begin XCBuildConfiguration section */ 503 | DFB107DB1903ECF70038FB05 /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | ALWAYS_SEARCH_USER_PATHS = NO; 507 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 508 | CLANG_CXX_LIBRARY = "libc++"; 509 | CLANG_ENABLE_MODULES = YES; 510 | CLANG_ENABLE_OBJC_ARC = YES; 511 | CLANG_MODULES_AUTOLINK = NO; 512 | CLANG_WARN_BOOL_CONVERSION = YES; 513 | CLANG_WARN_CONSTANT_CONVERSION = YES; 514 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 515 | CLANG_WARN_EMPTY_BODY = YES; 516 | CLANG_WARN_ENUM_CONVERSION = YES; 517 | CLANG_WARN_INT_CONVERSION = YES; 518 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 519 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 520 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 521 | COPY_PHASE_STRIP = NO; 522 | GCC_C_LANGUAGE_STANDARD = gnu99; 523 | GCC_DYNAMIC_NO_PIC = NO; 524 | GCC_OPTIMIZATION_LEVEL = 0; 525 | GCC_PREPROCESSOR_DEFINITIONS = ( 526 | "DEBUG=1", 527 | "$(inherited)", 528 | ); 529 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 530 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 531 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 532 | GCC_WARN_UNDECLARED_SELECTOR = YES; 533 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 534 | GCC_WARN_UNUSED_FUNCTION = YES; 535 | GCC_WARN_UNUSED_VARIABLE = YES; 536 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 537 | ONLY_ACTIVE_ARCH = YES; 538 | SDKROOT = iphoneos; 539 | }; 540 | name = Debug; 541 | }; 542 | DFB107DC1903ECF70038FB05 /* Release */ = { 543 | isa = XCBuildConfiguration; 544 | buildSettings = { 545 | ALWAYS_SEARCH_USER_PATHS = NO; 546 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 547 | CLANG_CXX_LIBRARY = "libc++"; 548 | CLANG_ENABLE_MODULES = YES; 549 | CLANG_ENABLE_OBJC_ARC = YES; 550 | CLANG_MODULES_AUTOLINK = NO; 551 | CLANG_WARN_BOOL_CONVERSION = YES; 552 | CLANG_WARN_CONSTANT_CONVERSION = YES; 553 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 554 | CLANG_WARN_EMPTY_BODY = YES; 555 | CLANG_WARN_ENUM_CONVERSION = YES; 556 | CLANG_WARN_INT_CONVERSION = YES; 557 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 558 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 559 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 560 | COPY_PHASE_STRIP = YES; 561 | ENABLE_NS_ASSERTIONS = NO; 562 | GCC_C_LANGUAGE_STANDARD = gnu99; 563 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 564 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 565 | GCC_WARN_UNDECLARED_SELECTOR = YES; 566 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 567 | GCC_WARN_UNUSED_FUNCTION = YES; 568 | GCC_WARN_UNUSED_VARIABLE = YES; 569 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 570 | SDKROOT = iphoneos; 571 | VALIDATE_PRODUCT = YES; 572 | }; 573 | name = Release; 574 | }; 575 | DFB107DE1903ECF70038FB05 /* Debug */ = { 576 | isa = XCBuildConfiguration; 577 | baseConfigurationReference = F9196B564860108BF3278697 /* Pods-BKPasscodeViewDemo.debug.xcconfig */; 578 | buildSettings = { 579 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 580 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 581 | CODE_SIGN_IDENTITY = "iPhone Developer"; 582 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 583 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 584 | GCC_PREFIX_HEADER = "BKPasscodeViewDemo/BKPasscodeViewDemo-Prefix.pch"; 585 | HEADER_SEARCH_PATHS = ( 586 | "$(inherited)", 587 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 588 | "$(SRCROOT)/Externals/**", 589 | ); 590 | INFOPLIST_FILE = "BKPasscodeViewDemo/BKPasscodeViewDemo-Info.plist"; 591 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 592 | PRODUCT_NAME = "$(TARGET_NAME)"; 593 | PROVISIONING_PROFILE = ""; 594 | WRAPPER_EXTENSION = app; 595 | }; 596 | name = Debug; 597 | }; 598 | DFB107DF1903ECF70038FB05 /* Release */ = { 599 | isa = XCBuildConfiguration; 600 | baseConfigurationReference = 3CA8D7725E96F2D794730EB4 /* Pods-BKPasscodeViewDemo.release.xcconfig */; 601 | buildSettings = { 602 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 603 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 604 | CODE_SIGN_IDENTITY = "iPhone Developer"; 605 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 606 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 607 | GCC_PREFIX_HEADER = "BKPasscodeViewDemo/BKPasscodeViewDemo-Prefix.pch"; 608 | HEADER_SEARCH_PATHS = ( 609 | "$(inherited)", 610 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 611 | "$(SRCROOT)/Externals/**", 612 | ); 613 | INFOPLIST_FILE = "BKPasscodeViewDemo/BKPasscodeViewDemo-Info.plist"; 614 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 615 | PRODUCT_NAME = "$(TARGET_NAME)"; 616 | PROVISIONING_PROFILE = ""; 617 | WRAPPER_EXTENSION = app; 618 | }; 619 | name = Release; 620 | }; 621 | DFB107E11903ECF70038FB05 /* Debug */ = { 622 | isa = XCBuildConfiguration; 623 | baseConfigurationReference = 9778CF9C5B2DD6DB5534C67D /* Pods-BKPasscodeViewDemoTests.debug.xcconfig */; 624 | buildSettings = { 625 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/BKPasscodeViewDemo.app/BKPasscodeViewDemo"; 626 | FRAMEWORK_SEARCH_PATHS = ( 627 | "$(SDKROOT)/Developer/Library/Frameworks", 628 | "$(inherited)", 629 | "$(DEVELOPER_FRAMEWORKS_DIR)", 630 | ); 631 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 632 | GCC_PREFIX_HEADER = "BKPasscodeViewDemo/BKPasscodeViewDemo-Prefix.pch"; 633 | GCC_PREPROCESSOR_DEFINITIONS = ( 634 | "DEBUG=1", 635 | "$(inherited)", 636 | ); 637 | INFOPLIST_FILE = "BKPasscodeViewDemoTests/BKPasscodeViewDemoTests-Info.plist"; 638 | PRODUCT_NAME = "$(TARGET_NAME)"; 639 | TEST_HOST = "$(BUNDLE_LOADER)"; 640 | WRAPPER_EXTENSION = xctest; 641 | }; 642 | name = Debug; 643 | }; 644 | DFB107E21903ECF70038FB05 /* Release */ = { 645 | isa = XCBuildConfiguration; 646 | baseConfigurationReference = 8292B44A2B577048F6F87739 /* Pods-BKPasscodeViewDemoTests.release.xcconfig */; 647 | buildSettings = { 648 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/BKPasscodeViewDemo.app/BKPasscodeViewDemo"; 649 | FRAMEWORK_SEARCH_PATHS = ( 650 | "$(SDKROOT)/Developer/Library/Frameworks", 651 | "$(inherited)", 652 | "$(DEVELOPER_FRAMEWORKS_DIR)", 653 | ); 654 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 655 | GCC_PREFIX_HEADER = "BKPasscodeViewDemo/BKPasscodeViewDemo-Prefix.pch"; 656 | INFOPLIST_FILE = "BKPasscodeViewDemoTests/BKPasscodeViewDemoTests-Info.plist"; 657 | PRODUCT_NAME = "$(TARGET_NAME)"; 658 | TEST_HOST = "$(BUNDLE_LOADER)"; 659 | WRAPPER_EXTENSION = xctest; 660 | }; 661 | name = Release; 662 | }; 663 | /* End XCBuildConfiguration section */ 664 | 665 | /* Begin XCConfigurationList section */ 666 | DFB107AC1903ECF70038FB05 /* Build configuration list for PBXProject "BKPasscodeViewDemo" */ = { 667 | isa = XCConfigurationList; 668 | buildConfigurations = ( 669 | DFB107DB1903ECF70038FB05 /* Debug */, 670 | DFB107DC1903ECF70038FB05 /* Release */, 671 | ); 672 | defaultConfigurationIsVisible = 0; 673 | defaultConfigurationName = Release; 674 | }; 675 | DFB107DD1903ECF70038FB05 /* Build configuration list for PBXNativeTarget "BKPasscodeViewDemo" */ = { 676 | isa = XCConfigurationList; 677 | buildConfigurations = ( 678 | DFB107DE1903ECF70038FB05 /* Debug */, 679 | DFB107DF1903ECF70038FB05 /* Release */, 680 | ); 681 | defaultConfigurationIsVisible = 0; 682 | defaultConfigurationName = Release; 683 | }; 684 | DFB107E01903ECF70038FB05 /* Build configuration list for PBXNativeTarget "BKPasscodeViewDemoTests" */ = { 685 | isa = XCConfigurationList; 686 | buildConfigurations = ( 687 | DFB107E11903ECF70038FB05 /* Debug */, 688 | DFB107E21903ECF70038FB05 /* Release */, 689 | ); 690 | defaultConfigurationIsVisible = 0; 691 | defaultConfigurationName = Release; 692 | }; 693 | /* End XCConfigurationList section */ 694 | }; 695 | rootObject = DFB107A91903ECF70038FB05 /* Project object */; 696 | } 697 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo.xcodeproj/project.xcworkspace/xcshareddata/BKPasscodeViewDemo.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 5D2EFFDB-B251-4CE3-9150-4D102A0A9EE7 9 | IDESourceControlProjectName 10 | BKPasscodeViewDemo 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 79B3F192D057AD253F66F1402DED80B0CBE2E637 14 | github.com:bkook/BKPasscodeView.git 15 | 16 | IDESourceControlProjectPath 17 | BKPasscodeViewDemo.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 79B3F192D057AD253F66F1402DED80B0CBE2E637 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:bkook/BKPasscodeView.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 79B3F192D057AD253F66F1402DED80B0CBE2E637 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 79B3F192D057AD253F66F1402DED80B0CBE2E637 36 | IDESourceControlWCCName 37 | BKPasscodeViewDemo 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo.xcodeproj/xcuserdata/bkook.xcuserdatad/xcschemes/BKPasscodeViewDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo.xcodeproj/xcuserdata/bkook.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BKPasscodeViewDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | DFB107B01903ECF70038FB05 16 | 17 | primary 18 | 19 | 20 | DFB107CB1903ECF70038FB05 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo.xcworkspace/xcshareddata/BKPasscodeViewDemo.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 28861FD6-A3D5-436F-BF91-BB1BA44DE27D 9 | IDESourceControlProjectName 10 | BKPasscodeViewDemo 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 79B3F192D057AD253F66F1402DED80B0CBE2E637 14 | github.com:bkook/BKPasscodeView.git 15 | 16 | IDESourceControlProjectPath 17 | BKPasscodeViewDemo.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 79B3F192D057AD253F66F1402DED80B0CBE2E637 21 | .. 22 | 23 | IDESourceControlProjectURL 24 | github.com:bkook/BKPasscodeView.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 79B3F192D057AD253F66F1402DED80B0CBE2E637 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 79B3F192D057AD253F66F1402DED80B0CBE2E637 36 | IDESourceControlWCCName 37 | BKPasscodeViewDemo 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo.xcworkspace/xcuserdata/bkook.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BKPasscodeLockScreenManager.h" 11 | 12 | extern NSString *const BKPasscodeKeychainServiceName; 13 | 14 | @interface AppDelegate : UIResponder 15 | 16 | @property (strong, nonatomic) UIWindow *window; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "MainViewController.h" 11 | 12 | NSString *const BKPasscodeKeychainServiceName = @"BKPasscodeSampleService"; 13 | 14 | @interface AppDelegate () 15 | 16 | @property (strong, nonatomic) MainViewController *mainViewController; 17 | 18 | @end 19 | 20 | @implementation AppDelegate 21 | 22 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 23 | { 24 | [[BKPasscodeLockScreenManager sharedManager] setDelegate:self]; 25 | 26 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 27 | self.window.backgroundColor = [UIColor whiteColor]; 28 | 29 | self.mainViewController = [[MainViewController alloc] initWithStyle:UITableViewStyleGrouped]; 30 | 31 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.mainViewController]; 32 | self.window.rootViewController = navController; 33 | [self.window makeKeyAndVisible]; 34 | 35 | return YES; 36 | } 37 | 38 | - (void)applicationWillResignActive:(UIApplication *)application 39 | { 40 | // 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. 41 | // 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. 42 | } 43 | 44 | - (void)applicationDidEnterBackground:(UIApplication *)application 45 | { 46 | // 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. 47 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 48 | 49 | [[BKPasscodeLockScreenManager sharedManager] showLockScreen:NO]; 50 | } 51 | 52 | - (void)applicationWillEnterForeground:(UIApplication *)application 53 | { 54 | // 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. 55 | } 56 | 57 | - (void)applicationDidBecomeActive:(UIApplication *)application 58 | { 59 | // 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. 60 | } 61 | 62 | - (void)applicationWillTerminate:(UIApplication *)application 63 | { 64 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 65 | } 66 | 67 | - (BOOL)lockScreenManagerShouldShowLockScreen:(BKPasscodeLockScreenManager *)aManager 68 | { 69 | return self.mainViewController.lockWhenEnterBackgroundSwitch.isOn || self.mainViewController.showingLockScreenManually; 70 | } 71 | 72 | - (UIViewController *)lockScreenManagerPasscodeViewController:(BKPasscodeLockScreenManager *)aManager 73 | { 74 | BKPasscodeViewController *viewController = [[BKPasscodeViewController alloc] initWithNibName:nil bundle:nil]; 75 | viewController.type = BKPasscodeViewControllerCheckPasscodeType; 76 | viewController.delegate = self.mainViewController; 77 | 78 | viewController.touchIDManager = [[BKTouchIDManager alloc] initWithKeychainServiceName:BKPasscodeKeychainServiceName]; 79 | viewController.touchIDManager.promptText = @"Scan fingerprint to unlock"; 80 | 81 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; 82 | return navController; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/BKCustomPasscodeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKCustomPasscodeViewController.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 6. 28.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKPasscodeViewController.h" 10 | 11 | @interface BKCustomPasscodeViewController : BKPasscodeViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/BKCustomPasscodeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKCustomPasscodeViewController.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 6. 28.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "BKCustomPasscodeViewController.h" 10 | 11 | @interface BKCustomPasscodeViewController () 12 | 13 | @end 14 | 15 | @implementation BKCustomPasscodeViewController 16 | 17 | - (void)customizePasscodeInputView:(BKPasscodeInputView *)aPasscodeInputView 18 | { 19 | [super customizePasscodeInputView:aPasscodeInputView]; 20 | 21 | if ([aPasscodeInputView.passcodeField isKindOfClass:[BKPasscodeField class]]) { 22 | BKPasscodeField *passcodeField = (BKPasscodeField *)aPasscodeInputView.passcodeField; 23 | passcodeField.imageSource = self; 24 | passcodeField.dotSize = CGSizeMake(32, 32); 25 | } 26 | } 27 | 28 | #pragma mark - BKPasscodeFieldImageSource 29 | 30 | - (UIImage *)passcodeField:(BKPasscodeField *)aPasscodeField dotImageAtIndex:(NSInteger)aIndex filled:(BOOL)aFilled 31 | { 32 | if (aFilled) { 33 | return [UIImage imageNamed:@"star_full"]; 34 | } else { 35 | return [UIImage imageNamed:@"star_empty"]; 36 | } 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/BKPasscodeViewDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.bkoook.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/BKPasscodeViewDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/Images.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" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "60x60", 21 | "scale" : "3x" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /BKPasscodeViewDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /BKPasscodeViewDemo/Images.xcassets/star_empty.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "star_empty@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BKPasscodeViewDemo/Images.xcassets/star_empty.imageset/star_empty@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/BKPasscodeViewDemo/Images.xcassets/star_empty.imageset/star_empty@2x.png -------------------------------------------------------------------------------- /BKPasscodeViewDemo/Images.xcassets/star_full.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "star_full@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BKPasscodeViewDemo/Images.xcassets/star_full.imageset/star_full@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/BKPasscodeViewDemo/Images.xcassets/star_full.imageset/star_full@2x.png -------------------------------------------------------------------------------- /BKPasscodeViewDemo/MainViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.h 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 26.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "BKCustomPasscodeViewController.h" 12 | 13 | @interface MainViewController : UITableViewController 14 | 15 | @property (strong, nonatomic) UISwitch *simplePasscodeSwitch; 16 | @property (strong, nonatomic) UISwitch *customizeAppearanceSwitch; 17 | @property (strong, nonatomic) UISwitch *lockWhenEnterBackgroundSwitch; 18 | @property (strong, nonatomic) UISwitch *authWithTouchIDFirstSwitch; 19 | 20 | @property (strong, nonatomic) NSString *passcode; 21 | @property (nonatomic) NSUInteger failedAttempts; 22 | @property (strong, nonatomic) NSDate *lockUntilDate; 23 | 24 | @property (nonatomic) BOOL showingLockScreenManually; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/MainViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 26.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import "MainViewController.h" 10 | #import "AppDelegate.h" 11 | 12 | @interface MainViewController () 13 | 14 | @end 15 | 16 | @implementation MainViewController 17 | 18 | - (id)initWithStyle:(UITableViewStyle)style 19 | { 20 | self = [super initWithStyle:style]; 21 | if (self) { 22 | self.passcode = @"1234"; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | 31 | _simplePasscodeSwitch = [[UISwitch alloc] init]; 32 | [_simplePasscodeSwitch setOn:YES]; 33 | 34 | _customizeAppearanceSwitch = [[UISwitch alloc] init]; 35 | [_customizeAppearanceSwitch setOn:NO]; 36 | 37 | _lockWhenEnterBackgroundSwitch = [[UISwitch alloc] init]; 38 | [_lockWhenEnterBackgroundSwitch setOn:NO]; 39 | 40 | _authWithTouchIDFirstSwitch = [[UISwitch alloc] init]; 41 | [_authWithTouchIDFirstSwitch setOn:YES]; 42 | 43 | self.title = @"BKPasscodeViewDemo"; 44 | 45 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"reuseIdentifier"]; 46 | } 47 | 48 | #pragma mark - Table view data source 49 | 50 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 51 | { 52 | return 2; 53 | } 54 | 55 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 56 | { 57 | if (section == 0) { 58 | return 4; 59 | } else if (section == 1) { 60 | return 4; 61 | } else { 62 | return 0; 63 | } 64 | } 65 | 66 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 67 | { 68 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"reuseIdentifier" forIndexPath:indexPath]; 69 | cell.accessoryView = nil; 70 | cell.selectionStyle = UITableViewCellSelectionStyleDefault; 71 | 72 | cell.textLabel.numberOfLines = 0; 73 | 74 | switch (indexPath.section) { 75 | case 0: 76 | if (indexPath.row == 0) { 77 | cell.textLabel.text = @"Set passcode"; 78 | } else if (indexPath.row == 1) { 79 | cell.textLabel.text = @"Change passcode"; 80 | } else if (indexPath.row == 2) { 81 | cell.textLabel.text = @"Check passcode"; 82 | } else if (indexPath.row == 3) { 83 | cell.textLabel.text = @"Show lock screen"; 84 | } 85 | break; 86 | 87 | case 1: 88 | if (indexPath.row == 0) { 89 | cell.textLabel.text = @"Use simple passcode"; 90 | cell.accessoryView = self.simplePasscodeSwitch; 91 | } else if (indexPath.row == 1) { 92 | cell.textLabel.text = @"Customize appearance"; 93 | cell.accessoryView = self.customizeAppearanceSwitch; 94 | } else if (indexPath.row == 2) { 95 | cell.textLabel.text = @"Lock when enter background"; 96 | cell.accessoryView = self.lockWhenEnterBackgroundSwitch; 97 | } else { 98 | cell.textLabel.text = @"Auth with Touch ID first if available"; 99 | cell.accessoryView = self.authWithTouchIDFirstSwitch; 100 | } 101 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 102 | break; 103 | } 104 | 105 | return cell; 106 | } 107 | 108 | - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section 109 | { 110 | if (section == 3) { 111 | return @"Default password is 1234"; 112 | } 113 | return nil; 114 | } 115 | 116 | - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath 117 | { 118 | if (indexPath.section < 3) { 119 | return indexPath; 120 | } 121 | return nil; 122 | } 123 | 124 | - (BKPasscodeViewController *)createPasscodeViewController 125 | { 126 | if (self.customizeAppearanceSwitch.isOn) { 127 | return [[BKCustomPasscodeViewController alloc] initWithNibName:nil bundle:nil]; 128 | } else { 129 | return [[BKPasscodeViewController alloc] initWithNibName:nil bundle:nil]; 130 | } 131 | } 132 | 133 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 134 | { 135 | if (indexPath.section == 0) { 136 | 137 | switch (indexPath.row) { 138 | case 0: 139 | [self presentPasscodeViewControllerWithType:BKPasscodeViewControllerNewPasscodeType]; 140 | break; 141 | case 1: 142 | [self presentPasscodeViewControllerWithType:BKPasscodeViewControllerChangePasscodeType]; 143 | break; 144 | case 2: 145 | [self presentPasscodeViewControllerWithType:BKPasscodeViewControllerCheckPasscodeType]; 146 | break; 147 | case 3: 148 | { 149 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 150 | 151 | self.showingLockScreenManually = YES; 152 | 153 | [[BKPasscodeLockScreenManager sharedManager] showLockScreen:YES]; 154 | 155 | self.showingLockScreenManually = NO; 156 | 157 | break; 158 | } 159 | default: 160 | break; 161 | } 162 | } 163 | } 164 | 165 | - (void)presentPasscodeViewControllerWithType:(BKPasscodeViewControllerType)type 166 | { 167 | BKPasscodeViewController *viewController = [self createPasscodeViewController]; 168 | viewController.delegate = self; 169 | viewController.type = type; 170 | 171 | // Passcode style (numeric or ASCII) 172 | viewController.passcodeStyle = (self.simplePasscodeSwitch.isOn) ? BKPasscodeInputViewNumericPasscodeStyle : BKPasscodeInputViewNormalPasscodeStyle; 173 | 174 | // Setup Touch ID manager 175 | BKTouchIDManager *touchIDManager = [[BKTouchIDManager alloc] initWithKeychainServiceName:BKPasscodeKeychainServiceName]; 176 | touchIDManager.promptText = @"BKPasscodeView Touch ID Demo"; 177 | viewController.touchIDManager = touchIDManager; 178 | 179 | viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(passcodeViewCloseButtonPressed:)]; 180 | 181 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; 182 | 183 | if (self.authWithTouchIDFirstSwitch.isOn && viewController.type == BKPasscodeViewControllerCheckPasscodeType) { 184 | 185 | // To prevent duplicated selection before showing Touch ID user interface. 186 | self.tableView.userInteractionEnabled = NO; 187 | 188 | // Show Touch ID user interface 189 | [viewController startTouchIDAuthenticationIfPossible:^(BOOL prompted) { 190 | 191 | // Enable user interaction 192 | self.tableView.userInteractionEnabled = YES; 193 | 194 | // If Touch ID is unavailable or disabled, present passcode view controller for manual input. 195 | if (prompted) { 196 | NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; 197 | if (selectedIndexPath) { 198 | [self.tableView deselectRowAtIndexPath:selectedIndexPath animated:YES]; 199 | } 200 | } else { 201 | [self presentViewController:navController animated:YES completion:nil]; 202 | } 203 | }]; 204 | 205 | } else { 206 | 207 | [self presentViewController:navController animated:YES completion:nil]; 208 | } 209 | } 210 | 211 | - (void)passcodeViewCloseButtonPressed:(id)sender 212 | { 213 | [self dismissViewControllerAnimated:YES completion:nil]; 214 | } 215 | 216 | #pragma mark - BKPasscodeViewControllerDelegate 217 | 218 | - (void)passcodeViewController:(BKPasscodeViewController *)aViewController authenticatePasscode:(NSString *)aPasscode resultHandler:(void (^)(BOOL))aResultHandler 219 | { 220 | if ([aPasscode isEqualToString:self.passcode]) { 221 | 222 | self.lockUntilDate = nil; 223 | self.failedAttempts = 0; 224 | 225 | aResultHandler(YES); 226 | } else { 227 | aResultHandler(NO); 228 | } 229 | } 230 | 231 | - (void)passcodeViewControllerDidFailAttempt:(BKPasscodeViewController *)aViewController 232 | { 233 | self.failedAttempts++; 234 | 235 | if (self.failedAttempts > 5) { 236 | 237 | NSTimeInterval timeInterval = 60; 238 | 239 | if (self.failedAttempts > 6) { 240 | 241 | NSUInteger multiplier = self.failedAttempts - 6; 242 | 243 | timeInterval = (5 * 60) * multiplier; 244 | 245 | if (timeInterval > 3600 * 24) { 246 | timeInterval = 3600 * 24; 247 | } 248 | } 249 | 250 | self.lockUntilDate = [NSDate dateWithTimeIntervalSinceNow:timeInterval]; 251 | } 252 | } 253 | 254 | - (NSUInteger)passcodeViewControllerNumberOfFailedAttempts:(BKPasscodeViewController *)aViewController 255 | { 256 | return self.failedAttempts; 257 | } 258 | 259 | - (NSDate *)passcodeViewControllerLockUntilDate:(BKPasscodeViewController *)aViewController 260 | { 261 | return self.lockUntilDate; 262 | } 263 | 264 | - (void)passcodeViewController:(BKPasscodeViewController *)aViewController didFinishWithPasscode:(NSString *)aPasscode 265 | { 266 | switch (aViewController.type) { 267 | case BKPasscodeViewControllerNewPasscodeType: 268 | case BKPasscodeViewControllerChangePasscodeType: 269 | self.passcode = aPasscode; 270 | self.failedAttempts = 0; 271 | self.lockUntilDate = nil; 272 | break; 273 | default: 274 | break; 275 | } 276 | 277 | [aViewController dismissViewControllerAnimated:YES completion:nil]; 278 | } 279 | 280 | @end 281 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/en.lproj/BKPasscodeView.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/BKPasscodeViewDemo/en.lproj/BKPasscodeView.strings -------------------------------------------------------------------------------- /BKPasscodeViewDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/ja.lproj/BKPasscodeView.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/BKPasscodeViewDemo/ja.lproj/BKPasscodeView.strings -------------------------------------------------------------------------------- /BKPasscodeViewDemo/ja.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/ko.lproj/BKPasscodeView.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/BKPasscodeViewDemo/ko.lproj/BKPasscodeView.strings -------------------------------------------------------------------------------- /BKPasscodeViewDemo/ko.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /BKPasscodeViewDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // BKPasscodeViewDemo 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BKPasscodeViewDemoTests/BKPasscodeViewDemoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.bkoook.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /BKPasscodeViewDemoTests/BKPasscodeViewDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BKPasscodeViewDemoTests.m 3 | // BKPasscodeViewDemoTests 4 | // 5 | // Created by Byungkook Jang on 2014. 4. 20.. 6 | // Copyright (c) 2014년 Byungkook Jang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BKPasscodeViewDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation BKPasscodeViewDemoTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /BKPasscodeViewDemoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /BKPasscodeViewDemoTests/ko.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 bkook 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. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | # Uncomment this line to define a global platform for your project 4 | platform :ios 5 | 6 | target "BKPasscodeViewDemo" do 7 | 8 | pod 'AFViewShaker', '~> 0.0' 9 | 10 | end 11 | 12 | target "BKPasscodeViewDemoTests" do 13 | 14 | end 15 | 16 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFViewShaker (0.0.2) 3 | 4 | DEPENDENCIES: 5 | - AFViewShaker (~> 0.0) 6 | 7 | SPEC CHECKSUMS: 8 | AFViewShaker: 7e4bc215ca3e92347b78da436fca74e1730b52e8 9 | 10 | PODFILE CHECKSUM: c5a6ac0e95b8884205e41037da4dc4c3f39d61ee 11 | 12 | COCOAPODS: 1.0.1 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BKPasscodeView 2 | ============== 3 | - iOS7 style passcode view. Supports create, change and authenticate password. 4 | - Customizable lock policy for too many failure attempts. 5 | - Customizable passcode digit appearance. 6 | - Shows lock scrren when application entered background state. Use ```BKPasscodeLockScreenManager``` 7 | - You can authenticate passcode asynchronously. (e.g. using API to authenticate passcode) 8 | - Supports Touch ID API in iOS 8. 9 | 10 | ## Screenshots 11 | 12 | ![Screenshot](./Screenshots/passcode_01.png) 13 | ![Screenshot](./Screenshots/passcode_02.png) 14 | ![Screenshot](./Screenshots/passcode_03.png) 15 | ![Screenshot](./Screenshots/passcode_04.png) 16 | ![Screenshot](./Screenshots/passcode_05.png) 17 | ![Screenshot](./Screenshots/passcode_06.png) 18 | ![Screenshot](./Screenshots/passcode_07.png) 19 | 20 | ## Classes 21 | | Class | Description | 22 | | ----- | ----------- | 23 | | ```BKPasscodeField``` | A custom control that conforms ```UIKeyInput```. When it become first responder keyboard will be displayed to input passcode. | 24 | | ```BKPasscodeInputView``` | A view that supports numeric or normal(ASCII) passcode. This view can display title, message and error message. You can customize label appearances by overriding static methods. | 25 | | ```BKShiftingPasscodeInputView``` | A view that make a transition between two ```BKPasscodeInputView```. You can shift passcode views forward and backward. | 26 | | ```BKPasscodeViewController``` | A view controller that supports create, change and authenticate passcode. | 27 | | ```BKPasscodeLockScreenManager``` | A manager that shows lock screen when application entered background state. You can activate with ```activateWithDelegate:``` method. | 28 | | ```BKTouchIDManager``` | A manager that save, load and delete keychain item. It saves passcode to keychain and item cannot be accessed without fingerprint. | 29 | 30 | ## Podfile 31 | ```ruby 32 | platform :ios 33 | pod 'BKPasscodeView', '~> 0.1.2' 34 | ``` 35 | 36 | ## Example 37 | 38 | ### Presenting Passcode View Controller 39 | ```objc 40 | BKPasscodeViewController *viewController = [[BKPasscodeViewController alloc] initWithNibName:nil bundle:nil]; 41 | viewController.delegate = self; 42 | viewController.type = BKPasscodeViewControllerNewPasscodeType; 43 | // viewController.type = BKPasscodeViewControllerChangePasscodeType; // for change 44 | // viewController.type = BKPasscodeViewControllerCheckPasscodeType; // for authentication 45 | 46 | viewController.passcodeStyle = BKPasscodeInputViewNumericPasscodeStyle; 47 | // viewController.passcodeStyle = BKPasscodeInputViewNormalPasscodeStyle; // for ASCII style passcode. 48 | 49 | // To supports Touch ID feature, set BKTouchIDManager instance to view controller. 50 | // It only supports iOS 8 or greater. 51 | viewController.touchIDManager = [[BKTouchIDManager alloc] initWithKeychainServiceName:@"<# your keychain service name #>"]; 52 | viewController.touchIDManager.promptText = @"Scan fingerprint to authenticate"; // You can set prompt text. 53 | 54 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; 55 | [self presentViewController:navController animated:YES completion:nil]; 56 | 57 | ``` 58 | 59 | ### Authenticate with Touch ID without presenting passcode view controller 60 | ```objc 61 | BKPasscodeViewController *viewController = [[BKPasscodeViewController alloc] initWithNibName:nil bundle:nil]; 62 | viewController.delegate = self; 63 | viewController.type = BKPasscodeViewControllerCheckPasscodeType; // for authentication 64 | viewController.passcodeStyle = BKPasscodeInputViewNumericPasscodeStyle; 65 | 66 | // To supports Touch ID feature, set BKTouchIDManager instance to view controller. 67 | // It only supports iOS 8 or greater. 68 | viewController.touchIDManager = [[BKTouchIDManager alloc] initWithKeychainServiceName:@"<# your keychain service name #>"]; 69 | viewController.touchIDManager.promptText = @"Scan fingerprint to authenticate"; // You can set prompt text. 70 | 71 | // Show Touch ID user interface 72 | [viewController startTouchIDAuthenticationIfPossible:^(BOOL prompted) { 73 | 74 | // If Touch ID is unavailable or disabled, present passcode view controller for manual input. 75 | if (NO == prompted) { 76 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; 77 | [self presentViewController:navController animated:YES completion:nil]; 78 | } 79 | }]; 80 | 81 | ``` 82 | 83 | ### Showing Lock Screen 84 | ```objc 85 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 86 | { 87 | // ... 88 | 89 | [[BKPasscodeLockScreenManager sharedManager] setDelegate:self]; 90 | 91 | // ... 92 | return YES; 93 | } 94 | 95 | - (void)applicationDidEnterBackground:(UIApplication *)application 96 | { 97 | // ... 98 | // show passcode view controller when enter background. Screen will be obscured from here. 99 | [[BKPasscodeLockScreenManager sharedManager] showLockScreen:NO]; 100 | } 101 | 102 | - (BOOL)lockScreenManagerShouldShowLockScreen:(BKPasscodeLockScreenManager *)aManager 103 | { 104 | return YES; // return NO if you don't want to present lock screen. 105 | } 106 | 107 | - (UIViewController *)lockScreenManagerPasscodeViewController:(BKPasscodeLockScreenManager *)aManager 108 | { 109 | BKPasscodeViewController *viewController = [[BKPasscodeViewController alloc] initWithNibName:nil bundle:nil]; 110 | viewController.type = BKPasscodeViewControllerCheckPasscodeType; 111 | viewController.delegate = <# set delegate to authenticate passcode #>; 112 | 113 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; 114 | return navController; 115 | } 116 | ``` 117 | 118 | -------------------------------------------------------------------------------- /Screenshots/passcode_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/Screenshots/passcode_01.png -------------------------------------------------------------------------------- /Screenshots/passcode_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/Screenshots/passcode_02.png -------------------------------------------------------------------------------- /Screenshots/passcode_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/Screenshots/passcode_03.png -------------------------------------------------------------------------------- /Screenshots/passcode_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/Screenshots/passcode_04.png -------------------------------------------------------------------------------- /Screenshots/passcode_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/Screenshots/passcode_05.png -------------------------------------------------------------------------------- /Screenshots/passcode_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/Screenshots/passcode_06.png -------------------------------------------------------------------------------- /Screenshots/passcode_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bkook/BKPasscodeView/26033db7ee3422d8b8fa41622e90dfa9d1589acb/Screenshots/passcode_07.png --------------------------------------------------------------------------------