TOPasscodeViewController.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. //
  2. // TOPasscodeViewController.m
  3. //
  4. // Copyright 2017 Timothy Oliver. All rights reserved.
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to
  8. // deal in the Software without restriction, including without limitation the
  9. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  10. // sell copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  17. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  20. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
  21. // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. #import "TOPasscodeViewController.h"
  23. #import "TOPasscodeView.h"
  24. #import "TOPasscodeViewControllerAnimatedTransitioning.h"
  25. #import "TOPasscodeKeypadView.h"
  26. #import "TOPasscodeInputField.h"
  27. @interface TOPasscodeViewController () <UIViewControllerTransitioningDelegate>
  28. /* State */
  29. @property (nonatomic, assign, readwrite) TOPasscodeType passcodeType;
  30. @property (nonatomic, assign) CGFloat keyboardHeight;
  31. @property (nonatomic, assign) BOOL passcodeSuccess;
  32. @property (nonatomic, readonly) UIView *leftButton;
  33. @property (nonatomic, readonly) UIView *rightButton;
  34. /* Views */
  35. @property (nonatomic, strong, readwrite) UIVisualEffectView *backgroundEffectView;
  36. @property (nonatomic, strong, readwrite) UIView *backgroundView;
  37. @property (nonatomic, strong, readwrite) TOPasscodeView *passcodeView;
  38. @property (nonatomic, strong, readwrite) UIButton *biometricButton;
  39. @property (nonatomic, strong, readwrite) UIButton *cancelButton;
  40. @end
  41. @implementation TOPasscodeViewController
  42. #pragma mark - Instance Creation -
  43. - (instancetype)initWithStyle:(TOPasscodeViewStyle)style passcodeType:(TOPasscodeType)type
  44. {
  45. if (self = [super initWithNibName:nil bundle:nil]) {
  46. _style = style;
  47. _passcodeType = type;
  48. [self setUp];
  49. }
  50. return self;
  51. }
  52. - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  53. {
  54. if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
  55. [self setUp];
  56. }
  57. return self;
  58. }
  59. - (void)dealloc
  60. {
  61. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil];
  62. }
  63. #pragma mark - View Setup -
  64. - (void)setUp
  65. {
  66. self.transitioningDelegate = self;
  67. self.automaticallyPromptForBiometricValidation = NO;
  68. self.allowCancel = YES;
  69. if (TOPasscodeViewStyleIsTranslucent(self.style)) {
  70. self.modalPresentationStyle = UIModalPresentationOverFullScreen;
  71. }
  72. else {
  73. self.modalPresentationStyle = UIModalPresentationFullScreen;
  74. }
  75. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:)
  76. name:UIKeyboardWillChangeFrameNotification object:nil];
  77. }
  78. - (void)setUpBackgroundEffectViewForStyle:(TOPasscodeViewStyle)style
  79. {
  80. BOOL translucent = TOPasscodeViewStyleIsTranslucent(style);
  81. // Return if it already exists when it should
  82. if (translucent && self.backgroundEffectView) { return; }
  83. // Return if it doesn't exist when it shouldn't
  84. if (!translucent && !self.backgroundEffectView) { return; }
  85. // Remove it if we're now opaque
  86. if (!translucent) {
  87. [self.backgroundEffectView removeFromSuperview];
  88. self.backgroundEffectView = nil;
  89. return;
  90. }
  91. // Create it otherwise
  92. UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:[self blurEffectStyleForStyle:style]];
  93. self.backgroundEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
  94. self.backgroundEffectView.frame = self.view.bounds;
  95. self.backgroundEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  96. [self.view insertSubview:self.backgroundEffectView atIndex:0];
  97. }
  98. - (void)setUpBackgroundViewForStyle:(TOPasscodeViewStyle)style
  99. {
  100. BOOL translucent = TOPasscodeViewStyleIsTranslucent(style);
  101. if (!translucent && self.backgroundView) { return; }
  102. if (translucent && !self.backgroundView) { return; }
  103. if (translucent) {
  104. [self.backgroundView removeFromSuperview];
  105. self.backgroundView = nil;
  106. return;
  107. }
  108. self.backgroundView = [[UIView alloc] initWithFrame:self.view.bounds];
  109. self.backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  110. [self.view insertSubview:self.backgroundView atIndex:0];
  111. }
  112. - (UIBlurEffectStyle)blurEffectStyleForStyle:(TOPasscodeViewStyle)style
  113. {
  114. switch (self.style) {
  115. case TOPasscodeViewStyleTranslucentDark: return UIBlurEffectStyleDark;
  116. case TOPasscodeViewStyleTranslucentLight: return UIBlurEffectStyleExtraLight;
  117. default: return 0;
  118. }
  119. return 0;
  120. }
  121. - (void)setUpAccessoryButtons
  122. {
  123. UIFont *buttonFont = [UIFont systemFontOfSize:16.0f];
  124. BOOL isPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
  125. if (!self.leftAccessoryButton && self.allowBiometricValidation && !self.biometricButton) {
  126. self.biometricButton = [UIButton buttonWithType:UIButtonTypeSystem];
  127. [self.biometricButton setTitle:TOPasscodeBiometryTitleForType(self.biometryType) forState:UIControlStateNormal];
  128. [self.biometricButton addTarget:self action:@selector(accessoryButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
  129. if (isPad) {
  130. self.passcodeView.leftButton = self.biometricButton;
  131. }
  132. else {
  133. [self.view addSubview:self.biometricButton];
  134. }
  135. }
  136. else {
  137. if (self.leftAccessoryButton) {
  138. [self.biometricButton removeFromSuperview];
  139. self.biometricButton = nil;
  140. }
  141. }
  142. if (!self.rightAccessoryButton && !self.cancelButton) {
  143. self.cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
  144. [self.cancelButton setTitle:NSLocalizedString(@"Cancel", @"Cancel") forState:UIControlStateNormal];
  145. self.cancelButton.titleLabel.font = buttonFont;
  146. [self.cancelButton addTarget:self action:@selector(accessoryButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
  147. // If cancelling is disabled, we hide the cancel button but we still create it, because it can
  148. // transition to backspace after user input.
  149. self.cancelButton.hidden = !self.allowCancel;
  150. if (isPad) {
  151. self.passcodeView.rightButton = self.cancelButton;
  152. }
  153. else {
  154. [self.view addSubview:self.cancelButton];
  155. }
  156. }
  157. else {
  158. if (self.rightAccessoryButton) {
  159. [self.cancelButton removeFromSuperview];
  160. self.cancelButton = nil;
  161. }
  162. }
  163. [self updateAccessoryButtonFontsForSize:self.view.bounds.size];
  164. }
  165. #pragma mark - View Management -
  166. - (void)viewDidLoad
  167. {
  168. [super viewDidLoad];
  169. self.view.backgroundColor = [UIColor clearColor];
  170. self.view.layer.allowsGroupOpacity = NO;
  171. [self setUpBackgroundEffectViewForStyle:self.style];
  172. [self setUpBackgroundViewForStyle:self.style];
  173. [self setUpAccessoryButtons];
  174. [self applyThemeForStyle:self.style];
  175. }
  176. - (void)viewDidAppear:(BOOL)animated
  177. {
  178. [super viewDidAppear:animated];
  179. // Automatically trigger biometric validation if available
  180. if (self.allowBiometricValidation && self.automaticallyPromptForBiometricValidation) {
  181. [self accessoryButtonTapped:self.biometricButton];
  182. }
  183. }
  184. - (void)viewDidLayoutSubviews
  185. {
  186. CGSize bounds = self.view.bounds.size;
  187. CGSize maxSize = bounds;
  188. if (@available(iOS 11.0, *)) {
  189. UIEdgeInsets safeAreaInsets = self.view.safeAreaInsets;
  190. if (safeAreaInsets.bottom > 0) {
  191. maxSize.height -= safeAreaInsets.bottom;
  192. }
  193. if (safeAreaInsets.left > 0) {
  194. maxSize.width -= safeAreaInsets.left;
  195. }
  196. if (safeAreaInsets.right > 0) {
  197. maxSize.width -= safeAreaInsets.right;
  198. }
  199. }
  200. // Resize the pin view to scale to the new size
  201. [self.passcodeView sizeToFitSize:maxSize];
  202. // Re-center the pin view
  203. CGRect frame = self.passcodeView.frame;
  204. frame.origin.x = (bounds.width - frame.size.width) * 0.5f;
  205. frame.origin.y = ((bounds.height - self.keyboardHeight) - frame.size.height) * 0.5f;
  206. self.passcodeView.frame = CGRectIntegral(frame);
  207. // --------------------------------------------------
  208. // Update the accessory button sizes
  209. [self updateAccessoryButtonFontsForSize:maxSize];
  210. // Re-layout the accessory buttons
  211. [self layoutAccessoryButtonsForSize:maxSize];
  212. }
  213. - (void)viewWillAppear:(BOOL)animated
  214. {
  215. [super viewWillAppear:animated];
  216. [self setNeedsStatusBarAppearanceUpdate];
  217. // Force an initial layout if the view hasn't been presented yet
  218. [UIView performWithoutAnimation:^{
  219. [self.view setNeedsLayout];
  220. [self.view layoutIfNeeded];
  221. }];
  222. // Show the keyboard if we're entering alphanumeric characters
  223. if (self.passcodeType == TOPasscodeTypeCustomAlphanumeric) {
  224. [self.passcodeView.inputField becomeFirstResponder];
  225. }
  226. }
  227. - (void)viewWillDisappear:(BOOL)animated
  228. {
  229. [super viewWillDisappear:animated];
  230. // Dismiss the keyboard if it is visible
  231. if (self.passcodeView.inputField.isFirstResponder) {
  232. [self.passcodeView.inputField resignFirstResponder];
  233. }
  234. }
  235. - (UIStatusBarStyle)preferredStatusBarStyle
  236. {
  237. return TOPasscodeViewStyleIsDark(self.style) ? UIStatusBarStyleLightContent : UIStatusBarStyleDefault;
  238. }
  239. #pragma mark - View Rotations -
  240. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
  241. {
  242. [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  243. // We don't need to do anything special on iPad or if we're using character input
  244. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad || self.passcodeType == TOPasscodeTypeCustomAlphanumeric) { return; }
  245. // Work out if we need to transition to horizontal
  246. BOOL horizontalLayout = size.height < size.width;
  247. // Perform layout animation
  248. [self.passcodeView setHorizontalLayout:horizontalLayout animated:coordinator.animated duration:coordinator.transitionDuration];
  249. }
  250. #pragma mark - View Styling -
  251. - (void)applyThemeForStyle:(TOPasscodeViewStyle)style
  252. {
  253. BOOL isDark = TOPasscodeViewStyleIsDark(style);
  254. // Apply the tint color to the accessory buttons
  255. UIColor *accessoryTintColor = self.accessoryButtonTintColor;
  256. if (!accessoryTintColor) {
  257. accessoryTintColor = isDark ? [UIColor whiteColor] : nil;
  258. }
  259. self.biometricButton.tintColor = accessoryTintColor;
  260. self.cancelButton.tintColor = accessoryTintColor;
  261. self.leftAccessoryButton.tintColor = accessoryTintColor;
  262. self.rightAccessoryButton.tintColor = accessoryTintColor;
  263. self.backgroundView.backgroundColor = isDark ? [UIColor colorWithWhite:0.1f alpha:1.0f] : [UIColor whiteColor];
  264. }
  265. - (void)updateAccessoryButtonFontsForSize:(CGSize)size
  266. {
  267. CGFloat width = size.width;
  268. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  269. width = MIN(size.width, size.height);
  270. }
  271. CGFloat pointSize = 17.0f;
  272. if (width < TOPasscodeViewContentSizeMedium) {
  273. pointSize = 14.0f;
  274. }
  275. else if (width < TOPasscodeViewContentSizeDefault) {
  276. pointSize = 16.0f;
  277. }
  278. UIFont *accessoryFont = [UIFont systemFontOfSize:pointSize];
  279. self.biometricButton.titleLabel.font = accessoryFont;
  280. self.cancelButton.titleLabel.font = accessoryFont;
  281. self.leftAccessoryButton.titleLabel.font = accessoryFont;
  282. self.rightAccessoryButton.titleLabel.font = accessoryFont;
  283. }
  284. - (void)verticalLayoutAccessoryButtonsForSize:(CGSize)size
  285. {
  286. CGFloat width = MIN(size.width, size.height);
  287. CGFloat verticalInset = 54.0f;
  288. if (width < TOPasscodeViewContentSizeMedium) {
  289. verticalInset = 37.0f;
  290. }
  291. else if (width < TOPasscodeViewContentSizeDefault) {
  292. verticalInset = 43.0f;
  293. }
  294. CGFloat inset = self.passcodeView.keypadButtonInset;
  295. CGPoint point = (CGPoint){0.0f, (self.view.bounds.size.height - self.keyboardHeight) - verticalInset};
  296. if (@available(iOS 11.0, *)) {
  297. UIEdgeInsets safeAreaInsets = self.view.safeAreaInsets;
  298. if (safeAreaInsets.bottom > 0) {
  299. point.y -= safeAreaInsets.bottom;
  300. }
  301. }
  302. if (self.leftButton) {
  303. [self.leftButton sizeToFit];
  304. point.x = self.passcodeView.frame.origin.x + inset;
  305. self.leftButton.center = point;
  306. }
  307. if (self.rightButton) {
  308. [self.rightButton sizeToFit];
  309. point.x = CGRectGetMaxX(self.passcodeView.frame) - inset;
  310. self.rightButton.center = point;
  311. }
  312. }
  313. - (void)horizontalLayoutAccessoryButtonsForSize:(CGSize)size
  314. {
  315. CGRect passcodeViewFrame = self.passcodeView.frame;
  316. CGFloat buttonInset = self.passcodeView.keypadButtonInset;
  317. CGFloat width = MIN(size.width, size.height);
  318. CGFloat verticalInset = 35.0f;
  319. if (width < TOPasscodeViewContentSizeMedium) {
  320. verticalInset = 30.0f;
  321. }
  322. else if (width < TOPasscodeViewContentSizeDefault) {
  323. verticalInset = 35.0f;
  324. }
  325. if (self.leftButton) {
  326. [self.leftButton sizeToFit];
  327. CGRect frame = self.leftButton.frame;
  328. frame.origin.y = (self.view.bounds.size.height - verticalInset) - (frame.size.height * 0.5f);
  329. frame.origin.x = (CGRectGetMaxX(passcodeViewFrame) - buttonInset) - (frame.size.width * 0.5f);
  330. self.leftButton.frame = CGRectIntegral(frame);
  331. }
  332. if (self.rightButton) {
  333. [self.rightButton sizeToFit];
  334. CGRect frame = self.rightButton.frame;
  335. frame.origin.y = verticalInset - (frame.size.height * 0.5f);
  336. frame.origin.x = (CGRectGetMaxX(passcodeViewFrame) - buttonInset) - (frame.size.width * 0.5f);
  337. self.rightButton.frame = CGRectIntegral(frame);
  338. }
  339. [self.view bringSubviewToFront:self.rightButton];
  340. [self.view bringSubviewToFront:self.leftButton];
  341. }
  342. - (void)layoutAccessoryButtonsForSize:(CGSize)size
  343. {
  344. // The buttons are always embedded in the keypad view on iPad
  345. if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone) { return; }
  346. if (self.passcodeView.horizontalLayout && self.passcodeType != TOPasscodeTypeCustomAlphanumeric) {
  347. [self horizontalLayoutAccessoryButtonsForSize:size];
  348. }
  349. else {
  350. [self verticalLayoutAccessoryButtonsForSize:size];
  351. }
  352. }
  353. #pragma mark - Interactions -
  354. - (void)accessoryButtonTapped:(id)sender
  355. {
  356. if (sender == self.cancelButton) {
  357. // When entering keyboard input, just leave the button as 'cancel'
  358. if (self.passcodeType != TOPasscodeTypeCustomAlphanumeric && self.passcodeView.passcode.length > 0) {
  359. [self.passcodeView deleteLastPasscodeCharacterAnimated:YES];
  360. [self keypadButtonTapped];
  361. return;
  362. }
  363. if ([self.delegate respondsToSelector:@selector(didTapCancelInPasscodeViewController:)]) {
  364. [self.delegate didTapCancelInPasscodeViewController:self];
  365. }
  366. }
  367. else if (sender == self.biometricButton) {
  368. if ([self.delegate respondsToSelector:@selector(didPerformBiometricValidationRequestInPasscodeViewController:)]) {
  369. [self.delegate didPerformBiometricValidationRequestInPasscodeViewController:self];
  370. }
  371. }
  372. }
  373. - (void)keypadButtonTapped
  374. {
  375. NSString *title = nil;
  376. if (self.passcodeView.passcode.length > 0) {
  377. title = NSLocalizedString(@"Delete", @"Delete");
  378. } else if (self.allowCancel) {
  379. title = NSLocalizedString(@"Cancel", @"Cancel");
  380. }
  381. [UIView performWithoutAnimation:^{
  382. if (title != nil) {
  383. [self.cancelButton setTitle:title forState:UIControlStateNormal];
  384. [self.cancelButton layoutIfNeeded];
  385. }
  386. self.cancelButton.hidden = (title == nil);
  387. }];
  388. }
  389. - (void)didCompleteEnteringPasscode:(NSString *)passcode
  390. {
  391. if (![self.delegate respondsToSelector:@selector(passcodeViewController:isCorrectCode:)]) {
  392. return;
  393. }
  394. // Validate the code
  395. BOOL isCorrect = [self.delegate passcodeViewController:self isCorrectCode:passcode];
  396. if (!isCorrect) {
  397. [self.passcodeView resetPasscodeAnimated:YES playImpact:YES];
  398. return;
  399. }
  400. // Hang onto the fact the passcode was successful to play a nicer dismissal animation
  401. self.passcodeSuccess = YES;
  402. // Perform handler if correctly entered
  403. if ([self.delegate respondsToSelector:@selector(didInputCorrectPasscodeInPasscodeViewController:)]) {
  404. [self.delegate didInputCorrectPasscodeInPasscodeViewController:self];
  405. }
  406. else {
  407. [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
  408. }
  409. }
  410. #pragma mark - Keyboard Handling -
  411. - (void)keyboardWillChangeFrame:(NSNotification *)notification
  412. {
  413. // Extract the keyboard information we need from the notification
  414. CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
  415. CGFloat animationDuration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];
  416. UIViewAnimationOptions animationCurve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
  417. // Work out the on-screen height of the keyboard
  418. self.keyboardHeight = self.view.bounds.size.height - keyboardFrame.origin.y;
  419. self.keyboardHeight = MAX(self.keyboardHeight, 0.0f);
  420. // Set that the view needs to be laid out
  421. [self.view setNeedsLayout];
  422. if (animationDuration < FLT_EPSILON) {
  423. return;
  424. }
  425. // Animate the content sliding up and down with the keyboard
  426. [UIView animateWithDuration:animationDuration
  427. delay:0.0f
  428. options:animationCurve
  429. animations:^{ [self.view layoutIfNeeded]; }
  430. completion:nil];
  431. }
  432. #pragma mark - Transitioning Delegate -
  433. - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
  434. presentingController:(UIViewController *)presenting
  435. sourceController:(UIViewController *)source
  436. {
  437. return [[TOPasscodeViewControllerAnimatedTransitioning alloc] initWithPasscodeViewController:self dismissing:NO success:NO];
  438. }
  439. - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
  440. {
  441. return [[TOPasscodeViewControllerAnimatedTransitioning alloc] initWithPasscodeViewController:self dismissing:YES success:self.passcodeSuccess];
  442. }
  443. #pragma mark - Convenience Accessors -
  444. - (UIView *)leftButton
  445. {
  446. return self.leftAccessoryButton ? self.leftAccessoryButton : self.biometricButton;
  447. }
  448. - (UIView *)rightButton
  449. {
  450. return self.rightAccessoryButton ? self.rightAccessoryButton : self.cancelButton;
  451. }
  452. #pragma mark - Public Accessors -
  453. - (TOPasscodeView *)passcodeView
  454. {
  455. if (_passcodeView) { return _passcodeView; }
  456. _passcodeView = [[TOPasscodeView alloc] initWithStyle:self.style passcodeType:self.passcodeType];
  457. _passcodeView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin |
  458. UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
  459. [_passcodeView sizeToFit];
  460. _passcodeView.center = self.view.center;
  461. [self.view addSubview:_passcodeView];
  462. __weak typeof(self) weakSelf = self;
  463. _passcodeView.passcodeCompletedHandler = ^(NSString *passcode) {
  464. [weakSelf didCompleteEnteringPasscode:passcode];
  465. };
  466. _passcodeView.passcodeDigitEnteredHandler = ^{
  467. [weakSelf keypadButtonTapped];
  468. };
  469. // Set initial layout to horizontal if we're rotated on an iPhone
  470. if (self.passcodeType != TOPasscodeTypeCustomAlphanumeric && UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
  471. CGSize boundsSize = self.view.bounds.size;
  472. _passcodeView.horizontalLayout = boundsSize.width > boundsSize.height;
  473. }
  474. return _passcodeView;
  475. }
  476. - (void)setStyle:(TOPasscodeViewStyle)style
  477. {
  478. if (style == _style) { return; }
  479. _style = style;
  480. self.passcodeView.style = style;
  481. [self setUpBackgroundEffectViewForStyle:style];
  482. }
  483. - (void)setAllowBiometricValidation:(BOOL)allowBiometricValidation
  484. {
  485. if (_allowBiometricValidation == allowBiometricValidation) {
  486. return;
  487. }
  488. _allowBiometricValidation = allowBiometricValidation;
  489. [self setUpAccessoryButtons];
  490. [self applyThemeForStyle:self.style];
  491. }
  492. - (void)setTitleLabelColor:(UIColor *)titleLabelColor
  493. {
  494. self.passcodeView.titleLabelColor = titleLabelColor;
  495. }
  496. - (UIColor *)titleLabelColor { return self.passcodeView.titleLabelColor; }
  497. - (void)setInputProgressViewTintColor:(UIColor *)inputProgressViewTintColor
  498. {
  499. self.passcodeView.inputProgressViewTintColor = inputProgressViewTintColor;
  500. }
  501. - (UIColor *)inputProgressViewTintColor { return self.passcodeView.inputProgressViewTintColor; }
  502. - (void)setKeypadButtonBackgroundTintColor:(UIColor *)keypadButtonBackgroundTintColor
  503. {
  504. self.passcodeView.keypadButtonBackgroundColor = keypadButtonBackgroundTintColor;
  505. }
  506. - (void)setKeypadButtonShowLettering:(BOOL)keypadButtonShowLettering
  507. {
  508. self.passcodeView.keypadView.showLettering = keypadButtonShowLettering;
  509. }
  510. - (UIColor *)keypadButtonBackgroundTintColor { return self.passcodeView.keypadButtonBackgroundColor; }
  511. - (void)setKeypadButtonTextColor:(UIColor *)keypadButtonTextColor
  512. {
  513. self.passcodeView.keypadButtonTextColor = keypadButtonTextColor;
  514. }
  515. - (UIColor *)keypadButtonTextColor { return self.passcodeView.keypadButtonTextColor; }
  516. - (void)setKeypadButtonHighlightedTextColor:(UIColor *)keypadButtonHighlightedTextColor
  517. {
  518. self.passcodeView.keypadButtonHighlightedTextColor = keypadButtonHighlightedTextColor;
  519. }
  520. - (UIColor *)keypadButtonHighlightedTextColor { return self.passcodeView.keypadButtonHighlightedTextColor; }
  521. - (void)setAccessoryButtonTintColor:(UIColor *)accessoryButtonTintColor
  522. {
  523. if (accessoryButtonTintColor == _accessoryButtonTintColor) { return; }
  524. _accessoryButtonTintColor = accessoryButtonTintColor;
  525. [self applyThemeForStyle:self.style];
  526. }
  527. - (void)setBiometryType:(TOPasscodeBiometryType)biometryType
  528. {
  529. if (_biometryType == biometryType) { return; }
  530. _biometryType = biometryType;
  531. if (self.biometricButton) {
  532. [self.biometricButton setTitle:TOPasscodeBiometryTitleForType(_biometryType) forState:UIControlStateNormal];
  533. }
  534. }
  535. - (void)setContentHidden:(BOOL)contentHidden
  536. {
  537. [self setContentHidden:contentHidden animated:NO];
  538. }
  539. - (void)setContentHidden:(BOOL)hidden animated:(BOOL)animated
  540. {
  541. if (hidden == _contentHidden) { return; }
  542. _contentHidden = hidden;
  543. void (^setViewsHiddenBlock)(BOOL) = ^(BOOL hidden) {
  544. self.passcodeView.hidden = hidden;
  545. self.leftButton.hidden = hidden;
  546. self.rightButton.hidden = hidden;
  547. };
  548. void (^completionBlock)(BOOL) = ^(BOOL complete) {
  549. setViewsHiddenBlock(hidden);
  550. };
  551. if (!animated) {
  552. completionBlock(YES);
  553. return;
  554. }
  555. // Make sure the views are visible before the animation
  556. setViewsHiddenBlock(NO);
  557. void (^animationBlock)(void) = ^{
  558. CGFloat alpha = hidden ? 0.0f : 1.0f;
  559. self.passcodeView.contentAlpha = alpha;
  560. self.leftButton.alpha = alpha;
  561. self.rightButton.alpha = alpha;
  562. };
  563. // Animate
  564. [UIView animateWithDuration:0.4f animations:animationBlock completion:completionBlock];
  565. }
  566. @end