@@ -23,6 +23,8 @@ @interface TOPasscodeSettingsViewController ()
23
23
24
24
/* Layout Calculations */
25
25
@property (nonatomic , assign ) CGFloat verticalMidPoint;
26
+ @property (nonatomic , assign ) CGRect keyboardFrame;
27
+ @property (nonatomic , readonly ) CGRect contentOverlapFrame; // Either the keypad or the system keyboard
26
28
27
29
/* Views */
28
30
@property (nonatomic , strong ) UIView *containerView;
@@ -33,6 +35,10 @@ @interface TOPasscodeSettingsViewController ()
33
35
@property (nonatomic , strong ) TOPasscodeSettingsKeypadView *keypadView;
34
36
@property (nonatomic , strong ) TOPasscodeSettingsWarningLabel *warningLabel;
35
37
38
+ /* Bar Items */
39
+ @property (nonatomic , strong ) UIBarButtonItem *nextBarButtonItem;
40
+ @property (nonatomic , strong ) UIBarButtonItem *doneBarButtonItem;
41
+
36
42
@end
37
43
38
44
@implementation TOPasscodeSettingsViewController
@@ -61,6 +67,12 @@ - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibB
61
67
- (void )setUp
62
68
{
63
69
_failedPasscodeAttemptCount = 0 ;
70
+ [[NSNotificationCenter defaultCenter ] addObserver: self selector: @selector (keyboardWillChangeFrame: ) name: UIKeyboardWillChangeFrameNotification object: nil ];
71
+ }
72
+
73
+ - (void )dealloc
74
+ {
75
+ [[NSNotificationCenter defaultCenter ] removeObserver: self name: UIKeyboardWillChangeFrameNotification object: nil ];
64
76
}
65
77
66
78
#pragma mark - View Set-up -
@@ -92,7 +104,7 @@ - (void)viewDidLoad {
92
104
self.inputField = [[TOPasscodeInputField alloc ] init ];
93
105
self.inputField .tintColor = [UIColor blackColor ];
94
106
self.inputField .autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
95
- self.inputField .passcodeCompletedHandler = ^(NSString *passcode) { [weakSelf numberViewDidEnterPasscode : passcode]; };
107
+ self.inputField .passcodeCompletedHandler = ^(NSString *passcode) { [weakSelf inputViewDidCompletePasscode : passcode]; };
96
108
[self .inputField sizeToFit ];
97
109
[self .containerView addSubview: self .inputField];
98
110
@@ -146,6 +158,10 @@ - (void)viewDidLoad {
146
158
+ kTOPasscodeSettingsLabelInputSpacing ;
147
159
self.verticalMidPoint *= 0 .5f ;
148
160
161
+ // Bar button items
162
+ self.nextBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle: NSLocalizedString(@" Next" , @" " ) style: UIBarButtonItemStylePlain target: self action: @selector (nextButtonTapped: )];
163
+ self.doneBarButtonItem = [[UIBarButtonItem alloc ] initWithBarButtonSystemItem: UIBarButtonSystemItemDone target: self action: @selector (doneButtonTapped: )];
164
+
149
165
// Apply light/dark mode
150
166
[self applyThemeForStyle: self .style];
151
167
}
@@ -155,21 +171,25 @@ - (void)viewWillAppear:(BOOL)animated
155
171
[super viewWillAppear: animated];
156
172
157
173
self.state = self.requireCurrentPasscode ? TOPasscodeSettingsViewStateEnterCurrentPassword : TOPasscodeSettingsViewStateEnterNewPassword;
158
- [self updateContentForState: self .state type: self .passcodeType];
174
+ [self updateContentForState: self .state type: self .passcodeType animated: NO ];
159
175
}
160
176
161
177
#pragma mark - View Update -
162
178
163
- - (void )updateContentForState : (TOPasscodeSettingsViewState)state type : (TOPasscodeType)type
179
+ - (void )updateContentForState : (TOPasscodeSettingsViewState)state type : (TOPasscodeType)type animated : ( BOOL ) animated
164
180
{
165
181
BOOL confirmingPasscode = state == TOPasscodeSettingsViewStateEnterCurrentPassword;
182
+ BOOL variableSizePasscode = (type >= TOPasscodeTypeCustomNumeric);
166
183
167
184
// Update the visibility of the options button
168
185
self.optionsButton .hidden = !(state == TOPasscodeSettingsViewStateEnterNewPassword);
169
186
170
187
// Clear the input view
171
188
self.inputField .passcode = nil ;
172
189
190
+ // Disable the input view
191
+ self.inputField .enabled = NO ;
192
+
173
193
// Update the warning label
174
194
self.warningLabel .hidden = !(confirmingPasscode && self.failedPasscodeAttemptCount > 0 );
175
195
self.warningLabel .numberOfWarnings = self.failedPasscodeAttemptCount ;
@@ -179,7 +199,7 @@ - (void)updateContentForState:(TOPasscodeSettingsViewState)state type:(TOPasscod
179
199
self.warningLabel .frame = frame;
180
200
181
201
// Change the input view if needed
182
- if (type < TOPasscodeTypeCustomNumeric ) {
202
+ if (!variableSizePasscode ) {
183
203
self.inputField .style = TOPasscodeInputFieldStyleFixed;
184
204
self.inputField .fixedInputView .length = (self.passcodeType == TOPasscodeTypeSixDigits) ? 6 : 4 ;
185
205
}
@@ -191,12 +211,15 @@ - (void)updateContentForState:(TOPasscodeSettingsViewState)state type:(TOPasscod
191
211
switch (state) {
192
212
case TOPasscodeSettingsViewStateEnterCurrentPassword:
193
213
self.titleLabel .text = NSLocalizedString(@" Enter your passcode" , @" " );
214
+ self.navigationItem .rightBarButtonItem = nil ;
194
215
break ;
195
216
case TOPasscodeSettingsViewStateEnterNewPassword:
196
217
self.titleLabel .text = NSLocalizedString(@" Enter a new passcode" , @" " );
218
+ self.navigationItem .rightBarButtonItem = variableSizePasscode ? self.nextBarButtonItem : nil ;
197
219
break ;
198
220
case TOPasscodeSettingsViewStateConfirmNewPassword:
199
221
self.titleLabel .text = NSLocalizedString(@" Confirm new passcode" , @" " );
222
+ self.navigationItem .rightBarButtonItem = variableSizePasscode ? self.doneBarButtonItem : nil ;
200
223
break ;
201
224
}
202
225
@@ -211,6 +234,28 @@ - (void)updateContentForState:(TOPasscodeSettingsViewState)state type:(TOPasscod
211
234
frame = self.inputField .frame ;
212
235
frame.origin .x = (CGRectGetWidth (self.containerView .frame ) - CGRectGetWidth (frame)) * 0 .5f ;
213
236
self.inputField .frame = CGRectIntegral (frame);
237
+
238
+ // If we're the alphanumeric type, present the keyboard
239
+ if (type == TOPasscodeTypeCustomAlphanumeric) {
240
+ self.inputField .enabled = YES ;
241
+ [self .inputField becomeFirstResponder ];
242
+ }
243
+ else {
244
+ if (self.inputField .isFirstResponder ) {
245
+ [self .inputField resignFirstResponder ];
246
+ }
247
+ }
248
+
249
+ // If not animated, force a blanket re-layout
250
+ if (!animated) {
251
+ [self viewDidLayoutSubviews ];
252
+ return ;
253
+ }
254
+
255
+ // If animated, perform the animation
256
+ [UIView animateWithDuration: 0 .3f animations: ^{
257
+ [self viewDidLayoutSubviews ];
258
+ }];
214
259
}
215
260
216
261
- (void )transitionToState : (TOPasscodeSettingsViewState)state animated : (BOOL )animated
@@ -279,7 +324,11 @@ - (void)viewDidLayoutSubviews
279
324
frame.size .height = MAX (frame.size .height , kTOPasscodeKeypadMinHeight );
280
325
frame.size .height = MIN (frame.size .height , kTOPasscodeKeypadMaxHeight );
281
326
frame.size .width = viewSize.width ;
282
- frame.origin .y = viewSize.height - frame.size .height ;
327
+ frame.origin .y = viewSize.height ;
328
+ if (self.passcodeType != TOPasscodeTypeCustomAlphanumeric) {
329
+ frame.origin .y -= frame.size .height ;
330
+ }
331
+
283
332
self.keypadView .frame = CGRectIntegral (frame);
284
333
285
334
BOOL horizontalLayout = frame.size .height < kTOPasscodeKeypadMinHeight + FLT_EPSILON;
@@ -290,13 +339,13 @@ - (void)viewDidLayoutSubviews
290
339
291
340
// Layout the container view
292
341
frame = self.containerView .frame ;
293
- frame.origin .y = (((viewSize.height - (topContentHeight + self.keypadView . frame .size .height ))) * 0 .5f ) - self.verticalMidPoint ;
342
+ frame.origin .y = (((viewSize.height - (topContentHeight + self.contentOverlapFrame .size .height ))) * 0 .5f ) - self.verticalMidPoint ;
294
343
frame.origin .y += topContentHeight;
295
344
self.containerView .frame = CGRectIntegral (frame);
296
345
297
346
// Layout the passcode options button
298
347
frame = self.optionsButton .frame ;
299
- frame.origin .y = CGRectGetMinY (self.keypadView . frame ) - kTOPasscodeSettingsOptionsButtonOffset - CGRectGetHeight (frame);
348
+ frame.origin .y = CGRectGetMinY (self.contentOverlapFrame ) - kTOPasscodeSettingsOptionsButtonOffset - CGRectGetHeight (frame);
300
349
frame.origin .x = (CGRectGetWidth (self.view .frame ) - CGRectGetWidth (frame)) * 0 .5f ;
301
350
self.optionsButton .frame = frame;
302
351
@@ -362,7 +411,7 @@ - (void)applyThemeForStyle:(TOPasscodeSettingsViewStyle)style
362
411
}
363
412
364
413
#pragma mark - Data Management -
365
- - (void )numberViewDidEnterPasscode : (NSString *)passcode
414
+ - (void )inputViewDidCompletePasscode : (NSString *)passcode
366
415
{
367
416
switch (self.state ) {
368
417
case TOPasscodeSettingsViewStateEnterCurrentPassword:
@@ -414,6 +463,31 @@ - (void)confirmNewPasscode:(NSString *)passcode
414
463
[self .delegate passcodeSettingsViewController: self didChangeToNewPasscode: self .potentialPasscode ofType: self .passcodeType];
415
464
}
416
465
466
+ #pragma mark - System Keyboard Handling -
467
+ - (void )keyboardWillChangeFrame : (NSNotification *)notification
468
+ {
469
+ self.keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue ];
470
+ [self viewDidLayoutSubviews ];
471
+ }
472
+
473
+ - (CGRect)contentOverlapFrame
474
+ {
475
+ if (self.passcodeType < TOPasscodeTypeCustomAlphanumeric) {
476
+ return self.keypadView .frame ;
477
+ }
478
+
479
+ // Work out where our view is in relation to the screen
480
+ UIWindow *window = [UIApplication sharedApplication ].keyWindow ;
481
+ CGRect viewFrame = [self .view.superview convertRect: self .view.frame toView: window];
482
+
483
+ CGFloat overlap = CGRectGetMaxY (viewFrame) - CGRectGetMinY (self.keyboardFrame );
484
+
485
+ CGRect overlapFrame = self.keyboardFrame ;
486
+ overlapFrame.origin .y = MIN (viewFrame.size .height - overlap, viewFrame.size .height );
487
+ overlapFrame.size .height = MAX (overlap, 0 .0f );
488
+ return overlapFrame;
489
+ }
490
+
417
491
#pragma mark - Button Callbacks -
418
492
419
493
- (void )optionsCodeButtonTapped : (id )sender
@@ -456,28 +530,43 @@ - (void)optionsCodeButtonTapped:(id)sender
456
530
[self presentViewController: alertController animated: YES completion: nil ];
457
531
}
458
532
533
+ - (void )nextButtonTapped:(id )sender
534
+ {
535
+ [self transitionToState: TOPasscodeSettingsViewStateConfirmNewPassword animated: YES ];
536
+ }
537
+
538
+ - (void )doneButtonTapped:(id )sender
539
+ {
540
+ [self confirmNewPasscode: self .inputField.passcode];
541
+ }
542
+
459
543
#pragma mark - Accessors -
460
544
- (void )setPasscodeType:(TOPasscodeType)passcodeType
545
+ {
546
+ [self setPasscodeType: passcodeType animated: NO ];
547
+ }
548
+
549
+ - (void )setPasscodeType:(TOPasscodeType)passcodeType animated: (BOOL )animated
461
550
{
462
551
if (_passcodeType == passcodeType) { return ; }
463
552
_passcodeType = passcodeType;
464
553
465
- [self updateContentForState: self .state type: _passcodeType];
554
+ [self updateContentForState: self .state type: _passcodeType animated: animated ];
466
555
}
467
556
468
557
- (void )setState: (TOPasscodeSettingsViewState)state
469
558
{
470
559
if (_state == state) { return ; }
471
560
_state = state;
472
561
473
- [self updateContentForState: _state type: self .passcodeType];
562
+ [self updateContentForState: _state type: self .passcodeType animated: NO ];
474
563
}
475
564
476
565
- (void )setFailedPasscodeAttemptCount: (NSInteger )failedPasscodeAttemptCount
477
566
{
478
567
if (_failedPasscodeAttemptCount == failedPasscodeAttemptCount) { return ; }
479
568
_failedPasscodeAttemptCount = failedPasscodeAttemptCount;
480
- [self updateContentForState: self .state type: self .passcodeType];
569
+ [self updateContentForState: self .state type: self .passcodeType animated: NO ];
481
570
}
482
571
483
572
@end
0 commit comments