123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- //
- // TOPasscodeSettingsKeypadView.m
- //
- // Copyright 2017 Timothy Oliver. All rights reserved.
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
- // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- #import "TOPasscodeSettingsKeypadView.h"
- #import "TOPasscodeSettingsKeypadButton.h"
- #import "TOPasscodeButtonLabel.h"
- #import "TOSettingsKeypadImage.h"
- const CGFloat kTOPasscodeSettingsKeypadButtonInnerSpacing = 7.0f;
- const CGFloat kTOPasscodeSettingsKeypadButtonOuterSpacing = 7.0f;
- const CGFloat kTOPasscodeSettingsKeypadCornderRadius = 12.0f;
- @interface TOPasscodeSettingsKeypadView ()
- @property (nonatomic, strong) UIView *separatorView;
- @property (nonatomic, strong) NSArray<TOPasscodeSettingsKeypadButton *> *keypadButtons;
- @property (nonatomic, strong) UIButton *deleteButton;
- @property (nonatomic, strong) UIImage *buttonBackgroundImage;
- @property (nonatomic, strong) UIImage *buttonTappedBackgroundImage;
- @end
- @implementation TOPasscodeSettingsKeypadView
- - (instancetype)initWithFrame:(CGRect)frame
- {
- if (self = [super initWithFrame:frame]) {
- [self setUp];
- }
- return self;
- }
- - (void)setUp
- {
- /* Button label styling */
- _keypadButtonNumberFont = [UIFont systemFontOfSize:32.0f weight:UIFontWeightRegular];
- _keypadButtonLetteringFont = [UIFont systemFontOfSize:11.0f weight:UIFontWeightRegular];
- _keypadButtonVerticalSpacing = 2.0f;
- _keypadButtonHorizontalSpacing = 3.0f;
- _keypadButtonLetteringSpacing = 2.0f;
- CGSize viewSize = self.frame.size;
- CGFloat height = 1.0f / [[UIScreen mainScreen] scale];
- self.separatorView = [[UIView alloc] initWithFrame:(CGRect){CGPointZero,{viewSize.width, height}}];
- self.separatorView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- [self addSubview:self.separatorView];
- [self setUpKeypadButtons];
- [self setUpDeleteButton];
- [self setUpDefaultValuesForStye:_style];
- [self applyTheme];
- }
- - (void)setUpKeypadButtons
- {
- NSInteger numberOfButtons = 10;
- NSArray *letteredTitles = @[@"ABC", @"DEF", @"GHI", @"JKL",
- @"MNO", @"PQRS", @"TUV", @"WXYZ"];
- NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:10];
- for (NSInteger i = 0; i < numberOfButtons; i++) {
- NSInteger number = (i+1) % 10; // Wrap around 0 at the end
- TOPasscodeSettingsKeypadButton *button = [TOPasscodeSettingsKeypadButton button];
- button.buttonLabel.numberString = [NSString stringWithFormat:@"%ld", (long)number];
- button.bottomInset = 2.0f;
- button.tag = number;
- if (i > 0) {
- NSInteger j = i - 1;
- if (j < letteredTitles.count) {
- button.buttonLabel.letteringString = letteredTitles[j];
- }
- }
- [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchDown];
- [self addSubview:button];
- [buttons addObject:button];
- }
- self.keypadButtons = [NSArray arrayWithArray:buttons];
- }
- - (void)setUpDeleteButton
- {
- UIImage *deleteIcon = [TOSettingsKeypadImage deleteIcon];
- self.deleteButton = [UIButton buttonWithType:UIButtonTypeSystem];
- [self.deleteButton setImage:deleteIcon forState:UIControlStateNormal];
- self.deleteButton.contentMode = UIViewContentModeCenter;
- self.deleteButton.frame = (CGRect){CGPointZero, deleteIcon.size};
- self.deleteButton.tintColor = [UIColor blackColor];
- [self.deleteButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
- [self addSubview:self.deleteButton];
- }
- - (void)setUpDefaultValuesForStye:(TOPasscodeSettingsViewStyle)style
- {
- BOOL isDark = style == TOPasscodeSettingsViewStyleDark;
- // Keypad label
- self.keypadButtonLabelTextColor = isDark ? [UIColor whiteColor] : [UIColor blackColor];
- self.keypadButtonForegroundColor = isDark ? [UIColor colorWithWhite:0.35f alpha:1.0f] : [UIColor whiteColor];
- self.keypadButtonTappedForegroundColor = isDark ? [UIColor colorWithWhite:0.45f alpha:1.0f] : [UIColor colorWithWhite:0.85f alpha:1.0f];
- // Button border color
- UIColor *borderColor = nil;
- if (isDark) {
- borderColor = [UIColor colorWithWhite:0.15f alpha:1.0f];
- }
- else {
- borderColor = [UIColor colorWithRed:166.0f/255.0f green:174.0f/255.0f blue:186.0f/255.0f alpha:1.0f];
- }
- self.keypadButtonBorderColor = borderColor;
- // Background Color
- UIColor *backgroundColor = nil;
- if (isDark) {
- backgroundColor = [UIColor colorWithWhite:0.18f alpha:1.0f];
- }
- else {
- backgroundColor = [UIColor colorWithRed:220.0f/255.0f green:225.0f/255.0f blue:232.0f/255.0f alpha:1.0f];
- }
- self.backgroundColor = backgroundColor;
- // Separator lines
- UIColor *separatorColor = nil;
- if (isDark) {
- separatorColor = [UIColor colorWithWhite:0.25f alpha:1.0f];
- }
- else {
- separatorColor = [UIColor colorWithWhite:0.7f alpha:1.0f];
- }
- self.separatorView.backgroundColor = separatorColor;
- self.deleteButton.tintColor = isDark ? [UIColor whiteColor] : [UIColor blackColor];
- }
- - (void)setUpImagesIfNeeded
- {
- if (self.buttonBackgroundImage && self.buttonTappedBackgroundImage) {
- return;
- }
- if (self.buttonBackgroundImage == nil) {
- self.buttonBackgroundImage = [TOSettingsKeypadImage buttonImageWithCornerRadius:kTOPasscodeSettingsKeypadCornderRadius
- foregroundColor:self.keypadButtonForegroundColor
- edgeColor:self.keypadButtonBorderColor
- edgeThickness:2.0f];
- }
- if (self.buttonTappedBackgroundImage == nil) {
- self.buttonTappedBackgroundImage = [TOSettingsKeypadImage buttonImageWithCornerRadius:kTOPasscodeSettingsKeypadCornderRadius
- foregroundColor:self.keypadButtonTappedForegroundColor
- edgeColor:self.keypadButtonBorderColor
- edgeThickness:2.0f];
- }
- for (TOPasscodeSettingsKeypadButton *button in self.keypadButtons) {
- button.buttonBackgroundImage = self.buttonBackgroundImage;
- button.buttonTappedBackgroundImage = self.buttonTappedBackgroundImage;
- }
- }
- - (void)applyTheme
- {
- for (TOPasscodeSettingsKeypadButton *button in self.keypadButtons) {
- button.buttonLabel.textColor = self.keypadButtonLabelTextColor;
- button.buttonLabel.letteringCharacterSpacing = self.keypadButtonLetteringSpacing;
- button.buttonLabel.letteringVerticalSpacing = self.keypadButtonVerticalSpacing;
- button.buttonLabel.letteringHorizontalSpacing = self.keypadButtonHorizontalSpacing;
- button.buttonLabel.numberLabelFont = self.keypadButtonNumberFont;
- button.buttonLabel.letteringLabelFont = self.keypadButtonLetteringFont;
- }
- }
- - (void)layoutSubviews
- {
- [super layoutSubviews];
- [self setUpImagesIfNeeded];
- CGFloat outerSpacing = kTOPasscodeSettingsKeypadButtonOuterSpacing;
- CGFloat innerSpacing = kTOPasscodeSettingsKeypadButtonInnerSpacing;
- CGSize viewSize = self.bounds.size;
- CGSize buttonSize = CGSizeZero;
- viewSize.width -= (outerSpacing * 2.0f);
- viewSize.height -= (outerSpacing * 2.0f);
-
- // Pull the buttons up to avoid overlapping the home indicator on iPhone X
- if (@available(iOS 11.0, *)) {
- viewSize.height -= self.safeAreaInsets.bottom;
- }
- // Four rows of three buttons
- buttonSize.width = floorf((viewSize.width - (innerSpacing * 2.0f)) / 3.0f);
- buttonSize.height = floorf((viewSize.height - (innerSpacing * 3.0f)) / 4.0f);
- CGPoint point = CGPointMake(outerSpacing, outerSpacing);
- CGRect buttonFrame = (CGRect){point, buttonSize};
- NSInteger i = 0;
- for (TOPasscodeSettingsKeypadButton *button in self.keypadButtons) {
- button.frame = buttonFrame;
- buttonFrame.origin.x += buttonFrame.size.width + innerSpacing;
- if (++i % 3 == 0) {
- buttonFrame.origin.x = outerSpacing;
- buttonFrame.origin.y += buttonFrame.size.height + innerSpacing;
- }
- if (button == self.keypadButtons.lastObject) {
- button.frame = buttonFrame;
- }
- }
- //Layout delete button
- CGSize boundsSize = self.bounds.size;
-
- // Adjust for home indicator on iPhone X
- if (@available(iOS 11.0, *)) {
- boundsSize.height -= self.safeAreaInsets.bottom;
- }
-
- CGRect frame = self.deleteButton.frame;
- frame.size = buttonSize;
- frame.origin.x = boundsSize.width - (outerSpacing + buttonSize.width * 0.5f);
- frame.origin.x -= (CGRectGetWidth(frame) * 0.5f);
- frame.origin.y = boundsSize.height - (outerSpacing + buttonSize.height * 0.5f);
- frame.origin.y -= (CGRectGetHeight(frame) * 0.5f);
- self.deleteButton.frame = frame;
- }
- #pragma mark - Interaction -
- - (void)buttonTapped:(id)sender
- {
- // Handler for the delete button
- if (sender == self.deleteButton) {
- if (self.deleteButtonTappedHandler) {
- self.deleteButtonTappedHandler();
- }
- return;
- }
- // Handler for the keypad buttons
- UIButton *button = (UIButton *)sender;
- NSInteger number = button.tag;
- [[UIDevice currentDevice] playInputClick];
- if (self.numberButtonTappedHandler) {
- self.numberButtonTappedHandler(number);
- }
- }
- #pragma mark - Accessors -
- - (void)setStyle:(TOPasscodeSettingsViewStyle)style
- {
- if (style == _style) {
- return;
- }
- _style = style;
- [self setUpDefaultValuesForStye:_style];
- [self applyTheme];
- }
- #pragma mark - Label Layout -
- - (void)setButtonLabelHorizontalLayout:(BOOL)buttonLabelHorizontalLayout
- {
- [self setButtonLabelHorizontalLayout:buttonLabelHorizontalLayout animated:NO];
- }
- - (void)setButtonLabelHorizontalLayout:(BOOL)horizontal animated:(BOOL)animated
- {
- if (horizontal == _buttonLabelHorizontalLayout) { return; }
- _buttonLabelHorizontalLayout = horizontal;
- for (TOPasscodeSettingsKeypadButton *button in self.keypadButtons) {
- if (!animated) {
- button.buttonLabel.horizontalLayout = horizontal;
- continue;
- }
- UIView *snapshotView = [button.buttonLabel snapshotViewAfterScreenUpdates:NO];
- snapshotView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
- [button addSubview:snapshotView];
- button.buttonLabel.horizontalLayout = horizontal;
- [button.buttonLabel setNeedsLayout];
- [button.buttonLabel layoutIfNeeded];
- [button.buttonLabel.layer removeAllAnimations];
- for (CALayer *sublayer in button.buttonLabel.layer.sublayers) {
- [sublayer removeAllAnimations];
- }
- button.buttonLabel.alpha = 0.0f;
- [UIView animateWithDuration:0.4f animations:^{
- button.buttonLabel.alpha = 1.0f;
- snapshotView.alpha = 0.0f;
- snapshotView.center = button.buttonLabel.center;
- } completion:^(BOOL complete) {
- [snapshotView removeFromSuperview];
- }];
- }
- }
- #pragma mark - Null Resettable Accessors -
- - (void)setKeypadButtonForegroundColor:(nullable UIColor *)keypadButtonForegroundColor
- {
- if (keypadButtonForegroundColor == _keypadButtonForegroundColor) { return; }
- _keypadButtonForegroundColor = keypadButtonForegroundColor;
- if (_keypadButtonForegroundColor == nil) {
- BOOL isDark = self.style == TOPasscodeSettingsViewStyleDark;
- _keypadButtonForegroundColor = isDark ? [UIColor colorWithWhite:0.3f alpha:1.0f] : [UIColor whiteColor];
- }
- self.buttonBackgroundImage = nil;
- [self setNeedsLayout];
- }
- - (void)setKeypadButtonBorderColor:(nullable UIColor *)keypadButtonBorderColor
- {
- if (keypadButtonBorderColor == _keypadButtonBorderColor) { return; }
- _keypadButtonBorderColor = keypadButtonBorderColor;
- if (_keypadButtonBorderColor == nil) {
- BOOL isDark = self.style == TOPasscodeSettingsViewStyleDark;
- UIColor *borderColor = nil;
- if (isDark) {
- borderColor = [UIColor colorWithWhite:0.2 alpha:1.0f];
- }
- else {
- borderColor = [UIColor colorWithRed:166.0f/255.0f green:174.0f/255.0f blue:186.0f/255.0f alpha:1.0f];
- }
- _keypadButtonBorderColor = borderColor;
- }
- self.buttonBackgroundImage = nil;
- [self setNeedsLayout];
- }
- - (void)setKeypadButtonTappedForegroundColor:(nullable UIColor *)keypadButtonTappedForegroundColor
- {
- if (keypadButtonTappedForegroundColor == _keypadButtonTappedForegroundColor) { return; }
- _keypadButtonTappedForegroundColor = keypadButtonTappedForegroundColor;
- if (_keypadButtonTappedForegroundColor == nil) {
- BOOL isDark = self.style == TOPasscodeSettingsViewStyleDark;
- _keypadButtonTappedForegroundColor = isDark ? [UIColor colorWithWhite:0.4f alpha:1.0f] : [UIColor colorWithWhite:0.85f alpha:1.0f];
- }
- self.buttonTappedBackgroundImage = nil;
- [self setNeedsLayout];
- }
- - (void)setEnabled:(BOOL)enabled
- {
- _enabled = enabled;
- for (TOPasscodeSettingsKeypadButton *button in self.keypadButtons) {
- button.enabled = enabled;
- }
- self.deleteButton.enabled = enabled;
- }
- @end
|