TOPasscodeViewController.m 24 KB

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