├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Design └── SettingsButtons.pcvd ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── TOPasscodeViewController.podspec ├── TOPasscodeViewController ├── Models │ ├── TOPasscodeCircleImage.h │ ├── TOPasscodeCircleImage.m │ ├── TOPasscodeViewContentLayout.h │ ├── TOPasscodeViewContentLayout.m │ ├── TOPasscodeViewControllerAnimatedTransitioning.h │ ├── TOPasscodeViewControllerAnimatedTransitioning.m │ ├── TOSettingsKeypadImage.h │ └── TOSettingsKeypadImage.m ├── Supporting │ └── TOPasscodeViewControllerConstants.h ├── TOPasscodeSettingsViewController.h ├── TOPasscodeSettingsViewController.m ├── TOPasscodeViewController.h ├── TOPasscodeViewController.m └── Views │ ├── Main │ ├── TOPasscodeCircleButton.h │ ├── TOPasscodeCircleButton.m │ ├── TOPasscodeKeypadView.h │ ├── TOPasscodeKeypadView.m │ ├── TOPasscodeView.h │ └── TOPasscodeView.m │ ├── Settings │ ├── TOPasscodeSettingsKeypadButton.h │ ├── TOPasscodeSettingsKeypadButton.m │ ├── TOPasscodeSettingsKeypadView.h │ ├── TOPasscodeSettingsKeypadView.m │ ├── TOPasscodeSettingsWarningLabel.h │ └── TOPasscodeSettingsWarningLabel.m │ └── Shared │ ├── TOPasscodeButtonLabel.h │ ├── TOPasscodeButtonLabel.m │ ├── TOPasscodeCircleView.h │ ├── TOPasscodeCircleView.m │ ├── TOPasscodeFixedInputView.h │ ├── TOPasscodeFixedInputView.m │ ├── TOPasscodeInputField.h │ ├── TOPasscodeInputField.m │ ├── TOPasscodeVariableInputView.h │ └── TOPasscodeVariableInputView.m ├── TOPasscodeViewControllerExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── TOPasscodeViewController.xcscheme │ ├── TOPasscodeViewControllerExample.xcscheme │ └── TOPasscodeViewControllerTests.xcscheme ├── TOPasscodeViewControllerExample ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Settings.imageset │ │ ├── Contents.json │ │ ├── Settings.png │ │ ├── Settings@2x.png │ │ └── Settings@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SettingsViewController.h ├── SettingsViewController.m ├── TOBlurView.h ├── TOBlurView.m ├── ViewController.h ├── ViewController.m ├── main.m └── wallpaper.jpg ├── TOPasscodeViewControllerExampleTests ├── Info.plist └── TOPasscodeViewControllerExampleTests.m ├── TOPasscodeViewControllerFramework └── Info.plist ├── breakdown.jpg ├── buildkite └── pipeline.release.yml ├── screenshot.jpg └── video.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: timoliver 2 | custom: https://tim.dev/paypal 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: macos-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Run a one-line script 13 | run: '(curl -s -L https://tim.dev/install_ios_oss_ci | bash -s arg1 arg2) && bundle exec fastlane test' 14 | env: 15 | TEST_SCHEME: "TOPasscodeViewControllerTests" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | .DS_Store 65 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | x.y.z Release Notes (yyyy-MM-dd) 2 | ============================================================= 3 | 4 | 0.0.2 - 2017-12-16 5 | ============================================================= 6 | 7 | ### Added 8 | 9 | * Full support for iPhone X, including edge layout and Face ID. 10 | 11 | ### Fixed 12 | * Custom numeric passcode UI broken on iPhone 6 and iPhone X screen size. 13 | * Enabled view controller to work in app extensions. 14 | 15 | 0.0.1 - 2017-08-12 16 | ============================================================= 17 | 18 | * Initial release! 19 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@timoliver.com.au. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `TOPasscodeViewController` 2 | 3 | Thanks so much for your interest in `TOPasscodeViewController`! It makes me incredibly happy to hear that others have not just found it useful, but are eager to help contribute to it. 4 | 5 | ## Submitting a Pull Request 6 | At this point, this library is pretty much a delicate house of cards. When modifying any of the code involved with the UI or layout, a lot of manual testing needs to be done to ensure that no regressions were introduced. 7 | 8 | If you've added or changed a feature that directly involves any UI layout, please test the following to ensure nothing has broken. 9 | * Presenting and dismissing the view controller in both portrait and landscape modes. 10 | * Presenting the view controller, rotating the device and then dismissing from the new orientation. 11 | * Presenting the view controller, then enabling split-screen on an iPad. 12 | * Changing the split-screen window sizes on iPad. 13 | 14 | If possible, please file an issue before filing a PR to discuss the feature you'd like to add. To ensure the quality of this view controller library doesn't dip, I plan to be very strict about the level of reliability and thoroughness of any code submitted through a PR. :) 15 | 16 | ## Submitting an Issue 17 | I've included all of the essential tips for filing comprehensive issues directly in the [issues template](/TimOliver/TOPasscodeViewController/blob/master/ISSUE_TEMPLATE.md). Please read that document and follow it as closely as you can when filing new issues. 18 | 19 | --- 20 | 21 | Thanks again for your interest in `TOPasscodeViewController`! I hope you've found the library useful in your apps! 22 | -------------------------------------------------------------------------------- /Design/SettingsButtons.pcvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/190f687f58b8bbe0c3c7ad914875565511b8f4bf/Design/SettingsButtons.pcvd -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - [ ] I have read this issue template and provided all possible information. 2 | - [ ] I'm using CocoaPods and have run `pod update` before filing this issue. 3 | 4 | --- 5 | > **Please remove this section of the issue template before filing your issue.** 6 | > 7 | > Thanks for considering filing an issue! Before proceeding, please consider 8 | > the type of issue you're filing and make sure to supply the proper details 9 | > needed for it! :) 10 | > 11 | > Please note that your issue may be closed without review if you do not supply 12 | > the information that is requested here. Since this library is done as a side-project 13 | > outside of work hours, please understand that a timely response cannot be guaranteed. ;) 14 | > 15 | > ## For CocoaPods Users 16 | > 17 | > Before filing a bug report, please make sure you are using the latest version 18 | > of CocoaPods (`gem install cocoapods`), and the latest version of this 19 | > library (`pod repo update`). 20 | > 21 | > Support for CocoaPods-related issues is not provided unless you confirm both of 22 | > these points in your submission. 23 | > 24 | > ## Issue Types 25 | > 26 | > **Questions**: Please check the closed issues to see if it's already been asked 27 | > before. 28 | > 29 | > **Feature Request**: Please fill in just the first two sections. Please be as thorough 30 | > as possible and explain how you would expect this feature to work both visually, and from an 31 | > API perspective. 32 | > 33 | > **Bugs**: Please be as thorough as possible when describe the bug you've discovered. If it's 34 | > a visual bug, please add a screenshot. If it's an animation bug, please provide a recording 35 | > of the bug in action. 36 | > 37 | > 38 | --- 39 | 40 | ## Hardware / Software 41 | 42 | Which version of the library were you using? 43 | Which version of iOS are you running? 44 | What model of iOS device were you testing on? 45 | If using CocoaPods, which version of CocoaPods are you on? 46 | 47 | ## Goals 48 | 49 | What is the outcome result you want to achieve with this library? 50 | 51 | ## Expected Results 52 | 53 | What did you expect to happen? 54 | 55 | ## Actual Results 56 | 57 | What happened instead? (Please attach a screenshot/screen recording if possible) 58 | 59 | ## Steps to Reproduce 60 | 61 | What are the steps needed to reproduce this issue? 62 | If this bug was caused by a specific image, please post it here. 63 | 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tim Oliver 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TOPasscodeViewController 2 | > A modal passcode input and validation view controller for iOS. 3 | 4 |

5 | 6 |

7 | 8 | [![CI](https://github.com/TimOliver/TOPasscodeViewController/workflows/CI/badge.svg)](https://github.com/TimOliver/TOPasscodeViewController/actions?query=workflow%3ACI) 9 | [![Version](https://img.shields.io/cocoapods/v/TOPasscodeViewController.svg?style=flat)](http://cocoadocs.org/docsets/TOPasscodeViewController) 10 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/master/LICENSE) 11 | [![Platform](https://img.shields.io/cocoapods/p/TOPasscodeViewController.svg?style=flat)](http://cocoadocs.org/docsets/TOPasscodeViewController) 12 | [![Beerpay](https://beerpay.io/TimOliver/TOPasscodeViewController/badge.svg?style=flat)](https://beerpay.io/TimOliver/TOPasscodeViewController) 13 | [![PayPal](https://img.shields.io/badge/paypal-donate-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M4RKULAVKV7K8) 14 | [![Twitch](https://img.shields.io/badge/twitch-timXD-6441a5.svg)](http://twitch.tv/timXD) 15 | 16 | 17 | 18 | 19 | 20 | `TOPasscodeViewController` is an open-source `UIViewController` subclass that will overlay a full-screen passcode UI over an app's content. The user must enter the correct password into it in order to proceed, or hit 'Cancel' to exit the private part of the app. 21 | 22 | This sort of UI is useful for certain apps that contain highly sensitive information (such as banking or health) where users may indeed want an extra level of security beyond the standard iOS passcode. 23 | 24 | ## Features 25 | * Prompts users to enter a passcode in order to proceed. 26 | * May be presented as a translucent overlay, partially showing the normal app content behind it. 27 | * Supports 4 different passcode types, from 4-digit passcodes, up to full alphanumeric passcodes. 28 | * Supports 4 base themes, including translucent/opaque and light/dark. 29 | * Supports Touch ID validation. 30 | * Provides an additional settings view controller for letting users change their passcode. 31 | * Passcode validation is handled by the parent app through a variety of delegate callbacks. 32 | * A custom animation and layout when the device is rotated to landscape mode on iPhone. 33 | * Custom 'opening' and 'dismissal' animations. 34 | 35 | ## System Requirements 36 | iOS 8.3 or above 37 | 38 | ## Installation 39 | 40 | #### As a CocoaPods Dependency 41 | 42 | Add the following to your Podfile: 43 | ``` ruby 44 | pod 'TOPasscodeViewController' 45 | ``` 46 | 47 | #### As a Carthage Dependency 48 | 49 | Coming soon. :) 50 | 51 | #### Manual Installation 52 | 53 | Download this project from GitHub, move the subfolder named 'TOPasscodeViewController' over to your project folder, and drag it into your Xcode project. 54 | 55 | ## Examples 56 | `TOPasscodeViewController` operates around a very strict modal implementation. It cannot be pushed to a `UINavigationController` stack, and must be presented as a full-screen dialog on an existing view controller. 57 | 58 | ### Basic Implementation 59 | ```objc 60 | - (void)showButtonTapped:(id)sender 61 | { 62 | TOPasscodeViewController *passcodeViewController = [[TOPasscodeViewController alloc] initWithStyle:TOPasscodeViewStyleTranslucentDark passcodeType:TOPasscodeTypeFourDigits]; 63 | passcodeViewController.delegate = self; 64 | [self presentViewController:passcodeViewController animated:YES completion:nil]; 65 | } 66 | 67 | - (void)didTapCancelInPasscodeViewController:(TOPasscodeViewController *)passcodeViewController 68 | { 69 | [self dismissViewControllerAnimated:YES completion:nil]; 70 | } 71 | 72 | - (BOOL)passcodeViewController:(TOPasscodeViewController *)passcodeViewController isCorrectCode:(NSString *)code 73 | { 74 | return [code isEqualToString:@"1234"]; 75 | } 76 | ``` 77 | 78 | ## Security 79 | 80 | `TOPasscodeViewController` does **not** perform any password management on your behalf. Any passcodes the user enters are forwarded to your own code via its delegate, and it's up to you to perform the validation and return the result back to `TOPasscodeViewController`. 81 | 82 | This was an intentional decision for security reasons. Instead of every app using `TOPasscodeViewController` implementing the exact same validation and storage code path, you're free to custom tailor the way passcodes are handled in your app as you see fit. 83 | 84 | No matter which passcode type, all passcodes in `TOPasscodeViewController` are handled as strings. When storing them in your app, they should be given at least the same level of scrutiny as full passwords. As such, I would strongly recommend you generate a salted hash of any user-defined passcode, and store both the hash and the salt in a protected location, like the iOS secure keychain, or an encrypted Realm file. 85 | 86 | Because passcodes are treated as generic strings, if the user has selected a different passcode type (like an arbitrary numerical or alphanumeric one), you should also store that setting alongside the hash as well. 87 | 88 | ## How it works 89 | 90 |

91 | 92 |

93 | 94 | There's nothing too crazy about how this view controller was created. All reusable components are broken out into separate `UIView` classes, and an all-encompassing `TOPasscodeView` class is used to pull as much view logic out of the view controller (one way of solving the Massive View Controller problem.) 95 | 96 | Depending on the screen width of the device (or if an iPad is using split screen), a single class manages all of the values for laying out the content with the appropriate font sizes, margins and cIrcle sizes. This was done to ensure maximum granular control over the sizing of elements per device. When transitioning between two of these sizes, all image assets are regenerated to ensure proper pixel scaling. 97 | 98 | The view controller heavily uses `UIVisualEffectView` to produce its translucent effect. When dealing with these, I discovered a few interesting tidbits: 99 | 100 | - For effect views that blur the content behind them, you can animate setting the `effect` property from `nil` to a `UIBlurEffect` object to produce a very nice gradually blurring transition effect. 101 | - Effect views with a `UIVibrancyEffect` CANNOT EVER have an alpha value less than `1.0`. Trying to animate fading in one of these views will result in an effect that will be broken until the animation has completed. To fix this, I added a `contentAlpha` property to my subclasses that would manually target the alpha values of all subviews, but would leave the visual effect view itself at `1.0`. This created the desired effect where the translucent content would fade in properly. 102 | 103 | ## Is it App Store-safe? 104 | 105 | **UPDATE: No. In it's current form, it's getting rejected from the App Store. [See issue #31.](https://github.com/TimOliver/TOPasscodeViewController/issues/31)** 106 | 107 | This is a tricky question. App Review guideline 5.2.5 states that apps can't produce UIs that might be easily confused with system functionality, but this rule is incredibly subjective and will ultimately heavily depend on the app reviewer at the time. 108 | 109 | Since the default style and text for this view controller make it very easily confused with the iOS lock screen, I would strongly recommend making these changes before shipping: 110 | 111 | - Set your app icon as the `titleView` property of `TOPasscodeViewController` to add more specialised branding to it. 112 | - Change the default tittle text to be more specific. Instead of 'Enter Passcode', put 'Enter MyApp Passcode to continue'. 113 | - Consider using the light style instead of the dark style. 114 | 115 | All in all, this might still not be enough. If you do end up getting rejected by Apple for using this library, please file an issue here and we can look at what will need to be changed to let Apple approve it. 116 | 117 | ## Credits 118 | `TOPasscodeViewController` was originally created by [Tim Oliver](http://twitter.com/TimOliverAU) as a component for [iComics](http://icomics.co), a comic reader app for iOS. 119 | 120 | iOS Device mockups used in the screenshot created by [Pixeden](http://pixeden.com). 121 | 122 | ## License 123 | `TOPasscodeViewController` is licensed under the MIT License, please see the [LICENSE](LICENSE) file. ![analytics](https://ga-beacon.appspot.com/UA-5643664-16/TOPasscodeViewController/README.md?pixel) 124 | -------------------------------------------------------------------------------- /TOPasscodeViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'TOPasscodeViewController' 3 | s.version = '0.0.2' 4 | s.license = { :type => 'MIT', :file => 'LICENSE' } 5 | s.summary = 'A view controller that prompts users to enter a passcode.' 6 | s.homepage = 'https://github.com/TimOliver/TOPasscodeViewController' 7 | s.author = 'Tim Oliver' 8 | s.source = { :git => 'https://github.com/TimOliver/TOPasscodeViewController.git', :tag => s.version } 9 | s.platform = :ios, '8.3' 10 | 11 | s.source_files = 'TOPasscodeViewController/**/*.{h,m}' 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Models/TOPasscodeCircleImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeCircleImage.h 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 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | /** 28 | A subclass of `UIImage` that can procedurally generate both hollow and full circle graphics at any size. 29 | These are used for the 'normal' and 'tapped' states of the passcode circle buttons. 30 | */ 31 | @interface TOPasscodeCircleImage : UIImage 32 | 33 | /** 34 | Generates and returns a `UIImage` of a filled circle at the specified size. 35 | 36 | @param size The diameter of the final circle image 37 | @param inset An inset value that will shrink the size of the circle. This is so it can be overlaid on a hollow circle 38 | without interfering with the anti-aliasing on the outer border. 39 | @param padding External padding around the circle to ensure it won't be clipped by the edge of the layer. 40 | Setting this value will increase the dimensions of the final `UIImage`. 41 | @param antialias Whether the circle boundary will be antialiased (Since antialiasing is unnecessary if this circle will overlay another.) 42 | */ 43 | + (UIImage *)circleImageOfSize:(CGFloat)size inset:(CGFloat)inset padding:(CGFloat)padding antialias:(BOOL)antialias; 44 | 45 | /** 46 | Generates and returns a `UIImage` of a hollow circle at the specified size. 47 | 48 | @param size The diameter of the final circle image 49 | @param strokeWidth The thickness, in points, of the stroke making up the circle image. 50 | @param padding External padding around the circle to ensure it won't be clipped by the edge of the layer. 51 | Setting this value will increase the dimensions of the final `UIImage`. 52 | */ 53 | + (UIImage *)hollowCircleImageOfSize:(CGFloat)size strokeWidth:(CGFloat)strokeWidth padding:(CGFloat)padding; 54 | 55 | @end 56 | 57 | NS_ASSUME_NONNULL_END 58 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Models/TOPasscodeCircleImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeCircleImage.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 | 23 | #import "TOPasscodeCircleImage.h" 24 | 25 | @implementation TOPasscodeCircleImage 26 | 27 | + (UIImage *)circleImageOfSize:(CGFloat)size inset:(CGFloat)inset padding:(CGFloat)padding antialias:(BOOL)antialias 28 | { 29 | UIImage *image = nil; 30 | CGSize imageSize = (CGSize){size + (padding * 2), size + (padding * 2)}; 31 | 32 | UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0f); 33 | { 34 | CGContextRef context = UIGraphicsGetCurrentContext(); 35 | 36 | if (!antialias) { 37 | CGContextSetShouldAntialias(context, NO); 38 | } 39 | 40 | CGRect rect = (CGRect){padding + inset, padding + inset, size - (inset * 2), size - (inset * 2)}; 41 | UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect:rect]; 42 | [[UIColor blackColor] setFill]; 43 | [ovalPath fill]; 44 | 45 | image = UIGraphicsGetImageFromCurrentImageContext(); 46 | } 47 | UIGraphicsEndImageContext(); 48 | 49 | return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 50 | } 51 | 52 | + (UIImage *)hollowCircleImageOfSize:(CGFloat)size strokeWidth:(CGFloat)strokeWidth padding:(CGFloat)padding 53 | { 54 | UIImage *image = nil; 55 | CGSize canvasSize = (CGSize){size + (padding * 2), size + (padding * 2)}; 56 | CGSize circleSize = (CGSize){size, size}; 57 | 58 | UIGraphicsBeginImageContextWithOptions(canvasSize, NO, 0.0f); 59 | { 60 | CGRect circleRect = (CGRect){{padding, padding}, circleSize}; 61 | circleRect = CGRectInset(circleRect, (strokeWidth * 0.5f), (strokeWidth * 0.5f)); 62 | 63 | UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:circleRect]; 64 | [[UIColor blackColor] setStroke]; 65 | path.lineWidth = strokeWidth; 66 | [path stroke]; 67 | 68 | image = UIGraphicsGetImageFromCurrentImageContext(); 69 | } 70 | UIGraphicsEndImageContext(); 71 | 72 | return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Models/TOPasscodeViewContentLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeViewContentLayout.h 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 | 23 | #import 24 | #import 25 | 26 | /** 27 | Depending on the width of the application window, all of the content views in 28 | the passcode view need to be resized in order to fit in the available space. 29 | 30 | This means that not only does the spacing and sizing of views need to be changed, but 31 | image assets need to be regenerated and font sizes need to change as well. 32 | 33 | This class assumes there will be three major screen sizes, and provides layout 34 | sizes, spacing, and styles in order to resize the passcode view for each one. 35 | 36 | The three screen styles it supports are: 37 | 38 | * Small Screens - iPhone 5/ or iPad 9.7" in 1/4 split screen mode 39 | * Medium Screens - iPhone 6/ or iPad 12.9" in 1/4 split screen mode 40 | * Large Screens - iPhone 6 Plus and all iPads when not in split screen mode. 41 | 42 | */ 43 | @interface TOPasscodeViewContentLayout : NSObject 44 | 45 | /* The width of the PIN view in which this layout object is sizing the content to fit. */ 46 | @property (nonatomic, assign) CGFloat viewWidth; 47 | 48 | /* Extra padding at the bottom in order to shift the content slightly up */ 49 | @property (nonatomic, assign) CGFloat bottomPadding; 50 | 51 | /* The title view at the very top */ 52 | @property (nonatomic, assign) CGFloat titleViewBottomSpacing; // Space from the bottom of the title view to the title label 53 | 54 | /* The Title Label Explaining the Passcode View */ 55 | @property (nonatomic, assign) CGFloat titleLabelBottomSpacing; // Space from the title label to the input view 56 | 57 | @property (nonatomic, strong) UIFont *titleLabelFont; // The font of the title label 58 | 59 | /* Title Label properties when the view is laid out horizontally */ 60 | @property (nonatomic, assign) CGFloat titleHorizontalLayoutWidth; // When laid out horizontally, the width of the title view 61 | @property (nonatomic, assign) CGFloat titleHorizontalLayoutSpacing; // The amount of spacing between the title label and the passcode keypad 62 | @property (nonatomic, assign) CGFloat titleViewHorizontalBottomSpacing; // Space from the bottom of the title view when iPhone is horizontal 63 | @property (nonatomic, assign) CGFloat titleLabelHorizontalBottomSpacing; // Spacing from the title label to input view in horizontal mode 64 | 65 | /* Circle Row Configuration */ 66 | @property (nonatomic, assign) CGFloat circleRowDiameter; // The diameter of each circle representing a PIN number 67 | @property (nonatomic, assign) CGFloat circleRowSpacing; // The spacing between each circle 68 | @property (nonatomic, assign) CGFloat circleRowBottomSpacing; // Space between the view used to indicate input 69 | 70 | /* Text Field Configuration */ 71 | @property (nonatomic, assign) CGFloat textFieldBorderThickness; // The thickness of the border stroke 72 | @property (nonatomic, assign) CGFloat textFieldBorderRadius; // The corner radius of the border 73 | @property (nonatomic, assign) CGFloat textFieldCircleDiameter; // The size of the circles in the passcode field 74 | @property (nonatomic, assign) CGFloat textFieldCircleSpacing; // The amount of spacing between each circle 75 | @property (nonatomic, assign) CGSize textFieldBorderPadding; // The amount of padding between the circles and the border 76 | @property (nonatomic, assign) NSInteger textFieldNumericCharacterLength; // The amount of circles to have in this field when set to numeric 77 | @property (nonatomic, assign) NSInteger textFieldAlphanumericCharacterLength; // The amount of circles to have in this field when set to alphanumeric 78 | @property (nonatomic, assign) CGFloat submitButtonFontSize; // The font size of the 'OK' button 79 | @property (nonatomic, assign) CGFloat submitButtonSpacing; // The spacing of the 'OK' button from the input 80 | 81 | /* Circle Button Shape and Layout */ 82 | @property (nonatomic, assign) CGFloat circleButtonDiameter; // The size of each PIN button 83 | @property (nonatomic, assign) CGSize circleButtonSpacing; // The vertical/horizontal spacing between buttons 84 | @property (nonatomic, assign) CGFloat circleButtonStrokeWidth; // The thickness of the border line 85 | 86 | /* Circle Button Label */ 87 | @property (nonatomic, strong) UIFont *circleButtonTitleLabelFont; // The font used for the '1' number labels 88 | @property (nonatomic, strong) UIFont *circleButtonLetteringLabelFont; // The font used for the 'ABC' labels 89 | @property (nonatomic, assign) CGFloat circleButtonLabelSpacing; // The vertical spacing between the number and lettering labels 90 | @property (nonatomic, assign) CGFloat circleButtonLetteringSpacing; // The spacing between the 'ABC' characters 91 | 92 | /* Default layout configurations for the various sizes */ 93 | + (TOPasscodeViewContentLayout *)defaultScreenContentLayout; /* Default layout values. Designed for iPhone 6 Plus and above. */ 94 | + (TOPasscodeViewContentLayout *)mediumScreenContentLayout; /* For medium screen sizes, like iPhone 6, or 1/4 view on iPad Pro. */ 95 | + (TOPasscodeViewContentLayout *)smallScreenContentLayout; /* For the smallest screens, like iPhone SE, and 1/4 on standard size iPads/ */ 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Models/TOPasscodeViewContentLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeViewContentLayout.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 | 23 | #import "TOPasscodeViewContentLayout.h" 24 | 25 | @implementation TOPasscodeViewContentLayout 26 | 27 | + (TOPasscodeViewContentLayout *)defaultScreenContentLayout 28 | { 29 | TOPasscodeViewContentLayout *contentLayout = [[TOPasscodeViewContentLayout alloc] init]; 30 | 31 | /* Width of the PIN View */ 32 | contentLayout.viewWidth = 414.0f; 33 | 34 | /* Bottom Padding */ 35 | contentLayout.bottomPadding = 25.0f; 36 | 37 | /* Title View Constraints */ 38 | contentLayout.titleViewBottomSpacing = 34.0f; 39 | 40 | /* The Title Label Explaining the PIN View */ 41 | contentLayout.titleLabelBottomSpacing = 34.0f; 42 | contentLayout.titleLabelFont = [UIFont systemFontOfSize: 22.0f]; 43 | 44 | /* Horizontal title constraints */ 45 | contentLayout.titleHorizontalLayoutWidth = 250.0f; 46 | contentLayout.titleHorizontalLayoutSpacing = 35.0f; 47 | contentLayout.titleViewHorizontalBottomSpacing = 20.0f; 48 | contentLayout.titleLabelHorizontalBottomSpacing = 20.0f; 49 | 50 | /* Circle Row Configuration */ 51 | contentLayout.circleRowDiameter = 15.5f; 52 | contentLayout.circleRowSpacing = 30.0f; 53 | contentLayout.circleRowBottomSpacing = 61.0f; 54 | 55 | /* Text Field Input Configuration */ 56 | contentLayout.textFieldBorderThickness = 1.5f; 57 | contentLayout.textFieldBorderRadius = 5.0f; 58 | contentLayout.textFieldCircleDiameter = 10.0f; 59 | contentLayout.textFieldCircleSpacing = 6.0f; 60 | contentLayout.textFieldBorderPadding = (CGSize){10, 10}; 61 | contentLayout.textFieldNumericCharacterLength = 10; 62 | contentLayout.textFieldAlphanumericCharacterLength = 15; 63 | contentLayout.submitButtonFontSize = 17.0f; 64 | contentLayout.submitButtonSpacing = 4.0f; 65 | 66 | /* Circle Button Shape and Layout */ 67 | contentLayout.circleButtonDiameter = 81.0f; 68 | contentLayout.circleButtonSpacing = (CGSize){25.0f, 20.0f}; 69 | contentLayout.circleButtonStrokeWidth = 1.5f; 70 | 71 | /* Circle Button Label */ 72 | contentLayout.circleButtonTitleLabelFont = [UIFont systemFontOfSize:37.5f weight:UIFontWeightThin]; 73 | contentLayout.circleButtonLetteringLabelFont = [UIFont systemFontOfSize:9.0f weight:UIFontWeightThin]; 74 | contentLayout.circleButtonLabelSpacing = 6.0f; 75 | contentLayout.circleButtonLetteringSpacing = 3.0f; 76 | 77 | return contentLayout; 78 | } 79 | 80 | + (TOPasscodeViewContentLayout *)mediumScreenContentLayout 81 | { 82 | TOPasscodeViewContentLayout *contentLayout = [[TOPasscodeViewContentLayout alloc] init]; 83 | 84 | /* Width of the PIN View */ 85 | contentLayout.viewWidth = 375.0f; 86 | 87 | /* Bottom Padding */ 88 | contentLayout.bottomPadding = 17.0f; 89 | 90 | /* Title View Constraints */ 91 | contentLayout.titleViewBottomSpacing = 27.0f; 92 | 93 | /* The Title Label Explaining the PIN View */ 94 | contentLayout.titleLabelBottomSpacing = 24.0f; 95 | contentLayout.titleLabelFont = [UIFont systemFontOfSize: 20.0f]; 96 | 97 | /* Horizontal title constraints */ 98 | contentLayout.titleHorizontalLayoutWidth = 185.0f; 99 | contentLayout.titleHorizontalLayoutSpacing = 16.0f; 100 | contentLayout.titleViewHorizontalBottomSpacing = 18.0f; 101 | contentLayout.titleLabelHorizontalBottomSpacing = 18.0f; 102 | 103 | /* Circle Row Configuration */ 104 | contentLayout.circleRowDiameter = 13.5f; 105 | contentLayout.circleRowSpacing = 26.0f; 106 | contentLayout.circleRowBottomSpacing = 53.0f; 107 | 108 | /* Submit Button */ 109 | contentLayout.submitButtonFontSize = 16.0f; 110 | contentLayout.submitButtonSpacing = 4.0f; 111 | 112 | /* Circle Button Shape and Layout */ 113 | contentLayout.circleButtonDiameter = 75.0f; 114 | contentLayout.circleButtonSpacing = (CGSize){28.0f, 15.0f}; 115 | contentLayout.circleButtonStrokeWidth = 1.5f; 116 | 117 | /* Text Field Input Configuration */ 118 | contentLayout.textFieldBorderThickness = 1.5f; 119 | contentLayout.textFieldBorderRadius = 5.0f; 120 | contentLayout.textFieldCircleDiameter = 9.0f; 121 | contentLayout.textFieldCircleSpacing = 5.0f; 122 | contentLayout.textFieldBorderPadding = (CGSize){10, 10}; 123 | contentLayout.textFieldNumericCharacterLength = 10; 124 | contentLayout.textFieldAlphanumericCharacterLength = 15; 125 | 126 | /* Circle Button Label */ 127 | contentLayout.circleButtonTitleLabelFont = [UIFont systemFontOfSize:36.5f weight:UIFontWeightThin]; 128 | contentLayout.circleButtonLetteringLabelFont = [UIFont systemFontOfSize:8.5f weight:UIFontWeightThin]; 129 | contentLayout.circleButtonLabelSpacing = 5.0f; 130 | contentLayout.circleButtonLetteringSpacing = 2.5f; 131 | 132 | return contentLayout; 133 | } 134 | 135 | + (TOPasscodeViewContentLayout *)smallScreenContentLayout 136 | { 137 | TOPasscodeViewContentLayout *contentLayout = [[TOPasscodeViewContentLayout alloc] init]; 138 | 139 | /* Width of the PIN View */ 140 | contentLayout.viewWidth = 320.0f; 141 | 142 | /* Bottom Padding */ 143 | contentLayout.bottomPadding = 12.0f; 144 | 145 | /* Title View Constraints */ 146 | contentLayout.titleViewBottomSpacing = 23.0f; 147 | 148 | /* The Title Label Explaining the PIN View */ 149 | contentLayout.titleLabelBottomSpacing = 23.0f; 150 | contentLayout.titleLabelFont = [UIFont systemFontOfSize: 17.0f]; 151 | 152 | /* Horizontal title constraints */ 153 | contentLayout.titleHorizontalLayoutWidth = 185.0f; 154 | contentLayout.titleHorizontalLayoutSpacing = 5.0f; 155 | contentLayout.titleViewHorizontalBottomSpacing = 18.0f; 156 | contentLayout.titleLabelHorizontalBottomSpacing = 18.0f; 157 | 158 | /* Circle Row Configuration */ 159 | contentLayout.circleRowDiameter = 12.5f; 160 | contentLayout.circleRowSpacing = 22.0f; 161 | contentLayout.circleRowBottomSpacing = 44.0f; 162 | 163 | /* Text Field Input Configuration */ 164 | contentLayout.textFieldBorderThickness = 1.5f; 165 | contentLayout.textFieldBorderRadius = 5.0f; 166 | contentLayout.textFieldCircleDiameter = 8.0f; 167 | contentLayout.textFieldCircleSpacing = 4.0f; 168 | contentLayout.textFieldBorderPadding = (CGSize){8, 8}; 169 | contentLayout.textFieldNumericCharacterLength = 10; 170 | contentLayout.textFieldAlphanumericCharacterLength = 15; 171 | 172 | /* Submit Button */ 173 | contentLayout.submitButtonFontSize = 15.0f; 174 | contentLayout.submitButtonSpacing = 3.0f; 175 | 176 | /* Circle Button Shape and Layout */ 177 | contentLayout.circleButtonDiameter = 76.0f; 178 | contentLayout.circleButtonSpacing = (CGSize){20.0f, 12.5f}; 179 | contentLayout.circleButtonStrokeWidth = 1.5f; 180 | 181 | /* Circle Button Label */ 182 | contentLayout.circleButtonTitleLabelFont = [UIFont systemFontOfSize:35.0f weight:UIFontWeightThin]; 183 | contentLayout.circleButtonLetteringLabelFont = [UIFont systemFontOfSize:9.0f weight:UIFontWeightThin]; 184 | contentLayout.circleButtonLabelSpacing = 4.5f; 185 | contentLayout.circleButtonLetteringSpacing = 2.0f; 186 | 187 | return contentLayout; 188 | } 189 | 190 | @end 191 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Models/TOPasscodeViewControllerAnimatedTransitioning.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeViewControllerAnimatedTransitioning.h 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 | 23 | #import 24 | #import 25 | 26 | @class TOPasscodeViewController; 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | /** 31 | An class conforming to `UIViewControllerAnimatedTransitioning` that handles the custom animation 32 | that plays when the passcode view controller is presented on the user's screen. 33 | */ 34 | @interface TOPasscodeViewControllerAnimatedTransitioning : NSObject 35 | 36 | /** The parent passcode view controller that this object will be controlling */ 37 | @property (nonatomic, weak, readonly) TOPasscodeViewController *passcodeViewController; 38 | 39 | /** Whether the controller is being presented or dismissed. The animation is played in reverse when dismissing. */ 40 | @property (nonatomic, assign) BOOL dismissing; 41 | 42 | /** If the correct passcode was successfully entered, this property can be set to YES. When the view controller 43 | is dismissing, the keypad view will also play a zooming out animation to give added context to the dismissal. */ 44 | @property (nonatomic, assign) BOOL success; 45 | 46 | /** 47 | Creates a new instanc of `TOPasscodeViewControllerAnimatedTransitioning` that will control the provided passcode 48 | view controller. 49 | 50 | @param passcodeViewController The passcode view controller in which this object will coordinate the animation upon. 51 | @param dismissing Whether the animation is played to present the view controller, or dismiss it. 52 | @param success Whether the object needs to play an additional zooming animation denoting the passcode was successfully entered. 53 | */ 54 | - (instancetype)initWithPasscodeViewController:(TOPasscodeViewController *)passcodeViewController 55 | dismissing:(BOOL)dismissing 56 | success:(BOOL)success; 57 | 58 | @end 59 | 60 | NS_ASSUME_NONNULL_END 61 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Models/TOPasscodeViewControllerAnimatedTransitioning.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeViewControllerAnimatedTransitioning.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 | 23 | #import "TOPasscodeViewControllerAnimatedTransitioning.h" 24 | #import "TOPasscodeViewController.h" 25 | #import "TOPasscodeView.h" 26 | 27 | @interface TOPasscodeViewControllerAnimatedTransitioning () 28 | @property (nonatomic, weak) TOPasscodeViewController *passcodeViewController; 29 | @end 30 | 31 | @implementation TOPasscodeViewControllerAnimatedTransitioning 32 | 33 | - (instancetype)initWithPasscodeViewController:(TOPasscodeViewController *)passcodeViewController dismissing:(BOOL)dismissing success:(BOOL)success 34 | { 35 | if (self = [super init]) { 36 | _passcodeViewController = passcodeViewController; 37 | _dismissing = dismissing; 38 | _success = success; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (NSTimeInterval)transitionDuration:(nullable id )transitionContext 45 | { 46 | return 0.35f; 47 | } 48 | 49 | - (void)animateTransition:(id )transitionContext 50 | { 51 | BOOL isPhone = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone; 52 | UIView *containerView = transitionContext.containerView; 53 | UIVisualEffectView *backgroundEffectView = self.passcodeViewController.backgroundEffectView; 54 | UIView *backgroundView = self.passcodeViewController.backgroundView; 55 | UIVisualEffect *backgroundEffect = backgroundEffectView.effect; 56 | TOPasscodeView *passcodeView = self.passcodeViewController.passcodeView; 57 | 58 | // Set the initial properties when presenting 59 | if (!self.dismissing) { 60 | backgroundEffectView.effect = nil; 61 | backgroundView.alpha = 0.0f; 62 | 63 | self.passcodeViewController.view.frame = containerView.bounds; 64 | [containerView addSubview:self.passcodeViewController.view]; 65 | } 66 | else { 67 | UIViewController *baseController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; 68 | if (baseController.view.superview == nil) { 69 | [containerView insertSubview:baseController.view atIndex:0]; 70 | } 71 | } 72 | 73 | CGFloat alpha = self.dismissing ? 1.0f : 0.0f; 74 | passcodeView.contentAlpha = alpha; 75 | 76 | // Animate the accessory views 77 | if (isPhone) { 78 | self.passcodeViewController.leftAccessoryButton.alpha = alpha; 79 | self.passcodeViewController.rightAccessoryButton.alpha = alpha; 80 | self.passcodeViewController.cancelButton.alpha = alpha; 81 | self.passcodeViewController.biometricButton.alpha = alpha; 82 | } 83 | 84 | id animationBlock = ^{ 85 | backgroundEffectView.effect = self.dismissing ? nil : backgroundEffect; 86 | backgroundView.alpha = self.dismissing ? 0.0f : 1.0f; 87 | 88 | CGFloat toAlpha = self.dismissing ? 0.0f : 1.0f; 89 | passcodeView.contentAlpha = toAlpha; 90 | if (isPhone) { 91 | self.passcodeViewController.leftAccessoryButton.alpha = toAlpha; 92 | self.passcodeViewController.rightAccessoryButton.alpha = toAlpha; 93 | self.passcodeViewController.cancelButton.alpha = toAlpha; 94 | self.passcodeViewController.biometricButton.alpha = toAlpha; 95 | } 96 | }; 97 | 98 | id completedBlock = ^(BOOL completed) { 99 | backgroundEffectView.effect = backgroundEffect; 100 | [transitionContext completeTransition:completed]; 101 | }; 102 | 103 | // If we're animating out from a successful passcode, play a zooming out animation 104 | // to give some more context 105 | if (self.success && self.dismissing) { 106 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; 107 | animation.duration = [self transitionDuration:transitionContext]; 108 | animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.9f, 0.9f, 1)]; 109 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 110 | [passcodeView.layer addAnimation:animation forKey:@"transform"]; 111 | } 112 | 113 | [UIView animateWithDuration:[self transitionDuration:transitionContext] 114 | delay:0.0f 115 | options:UIViewAnimationOptionAllowUserInteraction 116 | animations:animationBlock 117 | completion:completedBlock]; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Models/TOSettingsKeypadImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOSettingsKeypadImage.h 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 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | /** 28 | A subclass of `UIImage` that procedurally generates images for `TOPasscodeSettingsKeypadView`. 29 | This includes background images for each keypad button, and a delete icon for the bottom right corner 30 | of the keypad. 31 | */ 32 | @interface TOSettingsKeypadImage : UIImage 33 | 34 | /** 35 | Generates and returns an image of button background with a raised border in a pseudo-skeuomorphic style. 36 | 37 | @param radius The rounded radius of the button image's corners 38 | @param foregroundColor The fill color of the primary section of the button 39 | @param edgeColor The color of the raised border edge along the bottom. 40 | @param thickness The size of the border running along the bottom 41 | */ 42 | + (UIImage *)buttonImageWithCornerRadius:(CGFloat)radius 43 | foregroundColor:(UIColor *)foregroundColor 44 | edgeColor:(UIColor *)edgeColor 45 | edgeThickness:(CGFloat)thickness; 46 | 47 | /** 48 | Generates and returns a tintable delete icon. 49 | */ 50 | + (UIImage *)deleteIcon; 51 | 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Models/TOSettingsKeypadImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOSettingsKeypadImage.m 3 | // TOPasscodeViewControllerExample 4 | // 5 | // Created by Tim Oliver on 6/20/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import "TOSettingsKeypadImage.h" 10 | 11 | #define TOP_LEFT(X, Y) CGPointMake(rect.origin.x + X * limitedRadius, rect.origin.y + Y * limitedRadius) 12 | #define TOP_RIGHT(X, Y) CGPointMake(rect.origin.x + rect.size.width - X * limitedRadius, rect.origin.y + Y * limitedRadius) 13 | #define BOTTOM_RIGHT(X, Y) CGPointMake(rect.origin.x + rect.size.width - X * limitedRadius, rect.origin.y + rect.size.height - Y * limitedRadius) 14 | #define BOTTOM_LEFT(X, Y) CGPointMake(rect.origin.x + X * limitedRadius, rect.origin.y + rect.size.height - Y * limitedRadius) 15 | 16 | @implementation TOSettingsKeypadImage 17 | 18 | + (UIImage *)buttonImageWithCornerRadius:(CGFloat)radius 19 | foregroundColor:(UIColor *)foregroundColor 20 | edgeColor:(UIColor *)edgeColor 21 | edgeThickness:(CGFloat)thickness 22 | { 23 | CGFloat width = (radius * 2.0f) + 1.0f; 24 | CGFloat height = width + thickness; 25 | 26 | CGRect frame = (CGRect){CGPointZero, {width, height}}; 27 | 28 | UIImage *image = nil; 29 | UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0f); 30 | { 31 | CGContextRef context = UIGraphicsGetCurrentContext(); 32 | 33 | NSShadow* shadow = [[NSShadow alloc] init]; 34 | shadow.shadowColor = edgeColor; 35 | shadow.shadowOffset = CGSizeMake(0, thickness); 36 | shadow.shadowBlurRadius = 0; 37 | 38 | CGRect buttonFrame = frame; 39 | buttonFrame.size.height -= thickness; 40 | 41 | CGContextSaveGState(context); 42 | { 43 | CGContextSetShadowWithColor(context, shadow.shadowOffset, shadow.shadowBlurRadius, [shadow.shadowColor CGColor]); 44 | UIBezierPath *buttonPath = [[self class] bezierPathWithContinuousRoundedRect:buttonFrame cornerRadius:radius];//bezierPathWithRoundedRect:buttonFrame cornerRadius:radius]; 45 | [foregroundColor setFill]; 46 | [buttonPath fill]; 47 | } 48 | CGContextRestoreGState(context); 49 | 50 | image = UIGraphicsGetImageFromCurrentImageContext(); 51 | } 52 | UIGraphicsEndImageContext(); 53 | 54 | UIEdgeInsets insets = UIEdgeInsetsMake(radius, radius, radius + thickness, radius); 55 | image = [image resizableImageWithCapInsets:insets]; 56 | 57 | return image; 58 | } 59 | 60 | + (UIImage *)deleteIcon 61 | { 62 | UIImage *image = nil; 63 | 64 | CGRect frame = CGRectMake(0, 0, 40.0f, 21.0f); 65 | UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0f); 66 | { 67 | //// DeleteIcon 68 | { 69 | //// Border Drawing 70 | UIBezierPath* borderPath = [UIBezierPath bezierPath]; 71 | [borderPath moveToPoint: CGPointMake(25.73, 1.5)]; 72 | [borderPath addLineToPoint: CGPointMake(25.9, 1.53)]; 73 | [borderPath addCurveToPoint: CGPointMake(28.34, 3.46) controlPoint1: CGPointMake(27.03, 1.86) controlPoint2: CGPointMake(27.93, 2.56)]; 74 | [borderPath addCurveToPoint: CGPointMake(28.67, 6.56) controlPoint1: CGPointMake(28.67, 4.28) controlPoint2: CGPointMake(28.67, 5.04)]; 75 | [borderPath addLineToPoint: CGPointMake(28.64, 14.23)]; 76 | [borderPath addCurveToPoint: CGPointMake(28.35, 17.05) controlPoint1: CGPointMake(28.64, 15.76) controlPoint2: CGPointMake(28.64, 16.37)]; 77 | [borderPath addLineToPoint: CGPointMake(28.31, 17.19)]; 78 | [borderPath addCurveToPoint: CGPointMake(25.86, 19.11) controlPoint1: CGPointMake(27.89, 18.08) controlPoint2: CGPointMake(27, 18.79)]; 79 | [borderPath addCurveToPoint: CGPointMake(21.4, 19.37) controlPoint1: CGPointMake(24.82, 19.37) controlPoint2: CGPointMake(23.34, 19.37)]; 80 | [borderPath addLineToPoint: CGPointMake(11.51, 19.37)]; 81 | [borderPath addCurveToPoint: CGPointMake(9.9, 19.07) controlPoint1: CGPointMake(11.51, 19.37) controlPoint2: CGPointMake(10.41, 19.3)]; 82 | [borderPath addCurveToPoint: CGPointMake(7.38, 17.06) controlPoint1: CGPointMake(9.09, 18.68) controlPoint2: CGPointMake(8.52, 18.14)]; 83 | [borderPath addLineToPoint: CGPointMake(3.92, 13.81)]; 84 | [borderPath addCurveToPoint: CGPointMake(1.87, 11.55) controlPoint1: CGPointMake(2.78, 12.73) controlPoint2: CGPointMake(2.21, 12.19)]; 85 | [borderPath addLineToPoint: CGPointMake(1.79, 11.43)]; 86 | [borderPath addCurveToPoint: CGPointMake(1.82, 9.06) controlPoint1: CGPointMake(1.36, 10.57) controlPoint2: CGPointMake(1.4, 9.92)]; 87 | [borderPath addCurveToPoint: CGPointMake(3.96, 6.68) controlPoint1: CGPointMake(2.25, 8.29) controlPoint2: CGPointMake(2.82, 7.76)]; 88 | [borderPath addLineToPoint: CGPointMake(7.21, 3.61)]; 89 | [borderPath addCurveToPoint: CGPointMake(9.61, 1.67) controlPoint1: CGPointMake(8.35, 2.54) controlPoint2: CGPointMake(8.92, 2)]; 90 | [borderPath addLineToPoint: CGPointMake(9.73, 1.6)]; 91 | [borderPath addCurveToPoint: CGPointMake(11.41, 1.31) controlPoint1: CGPointMake(10.26, 1.37) controlPoint2: CGPointMake(10.84, 1.27)]; 92 | [borderPath addLineToPoint: CGPointMake(21.44, 1.27)]; 93 | [borderPath addCurveToPoint: CGPointMake(25.73, 1.5) controlPoint1: CGPointMake(23.38, 1.27) controlPoint2: CGPointMake(24.85, 1.27)]; 94 | [borderPath closePath]; 95 | [UIColor.blackColor setStroke]; 96 | borderPath.lineWidth = 2.5; 97 | [borderPath stroke]; 98 | 99 | 100 | //// Cross Drawing 101 | UIBezierPath* crossPath = [UIBezierPath bezierPath]; 102 | [crossPath moveToPoint: CGPointMake(15.22, 5.9)]; 103 | [crossPath addCurveToPoint: CGPointMake(15.21, 5.88) controlPoint1: CGPointMake(15.27, 5.95) controlPoint2: CGPointMake(15.21, 5.88)]; 104 | [crossPath addLineToPoint: CGPointMake(15.22, 5.9)]; 105 | [crossPath closePath]; 106 | [crossPath moveToPoint: CGPointMake(16.18, 10.28)]; 107 | [crossPath addCurveToPoint: CGPointMake(16.19, 10.26) controlPoint1: CGPointMake(16.22, 10.29) controlPoint2: CGPointMake(16.2, 10.28)]; 108 | [crossPath addLineToPoint: CGPointMake(16.18, 10.28)]; 109 | [crossPath closePath]; 110 | [crossPath moveToPoint: CGPointMake(14.52, 5.35)]; 111 | [crossPath addCurveToPoint: CGPointMake(15.21, 5.88) controlPoint1: CGPointMake(14.75, 5.46) controlPoint2: CGPointMake(14.93, 5.62)]; 112 | [crossPath addCurveToPoint: CGPointMake(15.38, 6.05) controlPoint1: CGPointMake(15.26, 5.94) controlPoint2: CGPointMake(15.32, 5.99)]; 113 | [crossPath addCurveToPoint: CGPointMake(15.43, 6.09) controlPoint1: CGPointMake(15.42, 6.09) controlPoint2: CGPointMake(15.43, 6.09)]; 114 | [crossPath addCurveToPoint: CGPointMake(15.38, 6.05) controlPoint1: CGPointMake(15.21, 5.88) controlPoint2: CGPointMake(15.27, 5.95)]; 115 | [crossPath addCurveToPoint: CGPointMake(17.97, 8.55) controlPoint1: CGPointMake(15.94, 6.59) controlPoint2: CGPointMake(17.66, 8.25)]; 116 | [crossPath addCurveToPoint: CGPointMake(17.97, 8.55) controlPoint1: CGPointMake(17.91, 8.61) controlPoint2: CGPointMake(17.94, 8.58)]; 117 | [crossPath addCurveToPoint: CGPointMake(21.36, 5.39) controlPoint1: CGPointMake(20.95, 5.68) controlPoint2: CGPointMake(21.14, 5.5)]; 118 | [crossPath addCurveToPoint: CGPointMake(22.67, 5.58) controlPoint1: CGPointMake(21.83, 5.17) controlPoint2: CGPointMake(22.34, 5.26)]; 119 | [crossPath addCurveToPoint: CGPointMake(22.98, 6.89) controlPoint1: CGPointMake(23.09, 5.99) controlPoint2: CGPointMake(23.18, 6.47)]; 120 | [crossPath addCurveToPoint: CGPointMake(22.28, 7.68) controlPoint1: CGPointMake(22.84, 7.14) controlPoint2: CGPointMake(22.65, 7.32)]; 121 | [crossPath addCurveToPoint: CGPointMake(19.68, 10.19) controlPoint1: CGPointMake(22.28, 7.68) controlPoint2: CGPointMake(20.88, 9.03)]; 122 | [crossPath addCurveToPoint: CGPointMake(22.97, 13.47) controlPoint1: CGPointMake(22.66, 13.06) controlPoint2: CGPointMake(22.85, 13.25)]; 123 | [crossPath addCurveToPoint: CGPointMake(22.76, 14.79) controlPoint1: CGPointMake(23.21, 13.95) controlPoint2: CGPointMake(23.11, 14.46)]; 124 | [crossPath addCurveToPoint: CGPointMake(21.35, 15.1) controlPoint1: CGPointMake(22.33, 15.22) controlPoint2: CGPointMake(21.8, 15.31)]; 125 | [crossPath addCurveToPoint: CGPointMake(20.48, 14.4) controlPoint1: CGPointMake(21.07, 14.97) controlPoint2: CGPointMake(20.87, 14.78)]; 126 | [crossPath addCurveToPoint: CGPointMake(17.89, 11.91) controlPoint1: CGPointMake(20.48, 14.4) controlPoint2: CGPointMake(19.08, 13.05)]; 127 | [crossPath addCurveToPoint: CGPointMake(14.5, 15.06) controlPoint1: CGPointMake(14.91, 14.78) controlPoint2: CGPointMake(14.73, 14.95)]; 128 | [crossPath addCurveToPoint: CGPointMake(13.2, 14.87) controlPoint1: CGPointMake(14.04, 15.28) controlPoint2: CGPointMake(13.53, 15.19)]; 129 | [crossPath addCurveToPoint: CGPointMake(12.89, 13.57) controlPoint1: CGPointMake(12.78, 14.47) controlPoint2: CGPointMake(12.69, 13.98)]; 130 | [crossPath addCurveToPoint: CGPointMake(13.42, 12.93) controlPoint1: CGPointMake(13, 13.35) controlPoint2: CGPointMake(13.15, 13.19)]; 131 | [crossPath addCurveToPoint: CGPointMake(13.59, 12.77) controlPoint1: CGPointMake(13.47, 12.88) controlPoint2: CGPointMake(13.53, 12.83)]; 132 | [crossPath addCurveToPoint: CGPointMake(16.19, 10.26) controlPoint1: CGPointMake(14.12, 12.25) controlPoint2: CGPointMake(15.78, 10.66)]; 133 | [crossPath addCurveToPoint: CGPointMake(12.89, 6.98) controlPoint1: CGPointMake(13.21, 7.39) controlPoint2: CGPointMake(13.01, 7.2)]; 134 | [crossPath addCurveToPoint: CGPointMake(12.77, 6.63) controlPoint1: CGPointMake(12.82, 6.84) controlPoint2: CGPointMake(12.79, 6.73)]; 135 | [crossPath addCurveToPoint: CGPointMake(13.1, 5.66) controlPoint1: CGPointMake(12.72, 6.28) controlPoint2: CGPointMake(12.83, 5.92)]; 136 | [crossPath addCurveToPoint: CGPointMake(14.52, 5.35) controlPoint1: CGPointMake(13.54, 5.24) controlPoint2: CGPointMake(14.07, 5.15)]; 137 | [crossPath closePath]; 138 | [UIColor.blackColor setFill]; 139 | [crossPath fill]; 140 | } 141 | 142 | image = UIGraphicsGetImageFromCurrentImageContext(); 143 | } 144 | UIGraphicsEndImageContext(); 145 | 146 | return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 147 | } 148 | 149 | /** 150 | Creates a bezier path with the iOS 7 squircle shape. 151 | 152 | A HUGE thanks to the folks at PaintCode for open-sourcing this 153 | https://www.paintcodeapp.com/news/code-for-ios-7-rounded-rectangles 154 | */ 155 | + (UIBezierPath *)bezierPathWithContinuousRoundedRect:(CGRect)rect cornerRadius:(CGFloat)radius 156 | { 157 | UIBezierPath* path = UIBezierPath.bezierPath; 158 | CGFloat limit = MIN(rect.size.width, rect.size.height) / 2 / 1.52866483; 159 | CGFloat limitedRadius = MIN(radius, limit); 160 | 161 | [path moveToPoint: TOP_LEFT(1.52866483, 0.00000000)]; 162 | [path addLineToPoint: TOP_RIGHT(1.52866471, 0.00000000)]; 163 | [path addCurveToPoint: TOP_RIGHT(0.66993427, 0.06549600) controlPoint1: TOP_RIGHT(1.08849323, 0.00000000) controlPoint2: TOP_RIGHT(0.86840689, 0.00000000)]; 164 | [path addLineToPoint: TOP_RIGHT(0.63149399, 0.07491100)]; 165 | [path addCurveToPoint: TOP_RIGHT(0.07491176, 0.63149399) controlPoint1: TOP_RIGHT(0.37282392, 0.16905899) controlPoint2: TOP_RIGHT(0.16906013, 0.37282401)]; 166 | [path addCurveToPoint: TOP_RIGHT(0.00000000, 1.52866483) controlPoint1: TOP_RIGHT(0.00000000, 0.86840701) controlPoint2: TOP_RIGHT(0.00000000, 1.08849299)]; 167 | [path addLineToPoint: BOTTOM_RIGHT(0.00000000, 1.52866471)]; 168 | [path addCurveToPoint: BOTTOM_RIGHT(0.06549569, 0.66993493) controlPoint1: BOTTOM_RIGHT(0.00000000, 1.08849323) controlPoint2: BOTTOM_RIGHT(0.00000000, 0.86840689)]; 169 | [path addLineToPoint: BOTTOM_RIGHT(0.07491111, 0.63149399)]; 170 | [path addCurveToPoint: BOTTOM_RIGHT(0.63149399, 0.07491111) controlPoint1: BOTTOM_RIGHT(0.16905883, 0.37282392) controlPoint2: BOTTOM_RIGHT(0.37282392, 0.16905883)]; 171 | [path addCurveToPoint: BOTTOM_RIGHT(1.52866471, 0.00000000) controlPoint1: BOTTOM_RIGHT(0.86840689, 0.00000000) controlPoint2: BOTTOM_RIGHT(1.08849323, 0.00000000)]; 172 | [path addLineToPoint: BOTTOM_LEFT(1.52866483, 0.00000000)]; 173 | [path addCurveToPoint: BOTTOM_LEFT(0.66993397, 0.06549569) controlPoint1: BOTTOM_LEFT(1.08849299, 0.00000000) controlPoint2: BOTTOM_LEFT(0.86840701, 0.00000000)]; 174 | [path addLineToPoint: BOTTOM_LEFT(0.63149399, 0.07491111)]; 175 | [path addCurveToPoint: BOTTOM_LEFT(0.07491100, 0.63149399) controlPoint1: BOTTOM_LEFT(0.37282401, 0.16905883) controlPoint2: BOTTOM_LEFT(0.16906001, 0.37282392)]; 176 | [path addCurveToPoint: BOTTOM_LEFT(0.00000000, 1.52866471) controlPoint1: BOTTOM_LEFT(0.00000000, 0.86840689) controlPoint2: BOTTOM_LEFT(0.00000000, 1.08849323)]; 177 | [path addLineToPoint: TOP_LEFT(0.00000000, 1.52866483)]; 178 | [path addCurveToPoint: TOP_LEFT(0.06549600, 0.66993397) controlPoint1: TOP_LEFT(0.00000000, 1.08849299) controlPoint2: TOP_LEFT(0.00000000, 0.86840701)]; 179 | [path addLineToPoint: TOP_LEFT(0.07491100, 0.63149399)]; 180 | [path addCurveToPoint: TOP_LEFT(0.63149399, 0.07491100) controlPoint1: TOP_LEFT(0.16906001, 0.37282401) controlPoint2: TOP_LEFT(0.37282401, 0.16906001)]; 181 | [path addCurveToPoint: TOP_LEFT(1.52866483, 0.00000000) controlPoint1: TOP_LEFT(0.86840701, 0.00000000) controlPoint2: TOP_LEFT(1.08849299, 0.00000000)]; 182 | [path closePath]; 183 | return path; 184 | } 185 | 186 | @end 187 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Supporting/TOPasscodeViewControllerConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeViewControllerConstants.h 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 | 23 | /* The visual style of the asscode view controller */ 24 | typedef NS_ENUM(NSInteger, TOPasscodeViewStyle) { 25 | TOPasscodeViewStyleTranslucentDark, 26 | TOPasscodeViewStyleTranslucentLight, 27 | TOPasscodeViewStyleOpaqueDark, 28 | TOPasscodeViewStyleOpaqueLight 29 | }; 30 | 31 | /* The visual style of the passcode settings view controller. */ 32 | typedef NS_ENUM(NSInteger, TOPasscodeSettingsViewStyle) { 33 | TOPasscodeSettingsViewStyleLight, 34 | TOPasscodeSettingsViewStyleDark 35 | }; 36 | 37 | /* Depending on the amount of horizontal space, the sizing of the elements */ 38 | typedef NS_ENUM(NSInteger, TOPasscodeViewContentSize) { 39 | TOPasscodeViewContentSizeDefault = 414, // Default, 414 points and above (6 Plus, all remaining iPad sizes) 40 | TOPasscodeViewContentSizeMedium = 375, // Greater or equal to 375 points: iPhone 6 / iPad Pro 1/4 split mode 41 | TOPasscodeViewContentSizeSmall = 320 // Greater or equal to 320 points: iPhone SE / iPad 1/4 split mode 42 | }; 43 | 44 | /* The types of passcodes that may be used. */ 45 | typedef NS_ENUM(NSInteger, TOPasscodeType) { 46 | TOPasscodeTypeFourDigits, // 4 Numbers 47 | TOPasscodeTypeSixDigits, // 6 Numbers 48 | TOPasscodeTypeCustomNumeric, // Any length of numbers 49 | TOPasscodeTypeCustomAlphanumeric // Any length of characters 50 | }; 51 | 52 | /* The type of biometrics this controller can handle */ 53 | typedef NS_ENUM(NSInteger, TOPasscodeBiometryType) { 54 | TOPasscodeBiometryTypeTouchID, 55 | TOPasscodeBiometryTypeFaceID 56 | }; 57 | 58 | static inline BOOL TOPasscodeViewStyleIsTranslucent(TOPasscodeViewStyle style) { 59 | return style <= TOPasscodeViewStyleTranslucentLight; 60 | } 61 | 62 | static inline BOOL TOPasscodeViewStyleIsDark(TOPasscodeViewStyle style) { 63 | return style < TOPasscodeViewStyleTranslucentLight || style == TOPasscodeViewStyleOpaqueDark; 64 | } 65 | 66 | static inline NSString *TOPasscodeBiometryTitleForType(TOPasscodeBiometryType type) { 67 | switch (type) { 68 | case TOPasscodeBiometryTypeFaceID: return NSLocalizedString(@"Face ID", @""); 69 | default: return NSLocalizedString(@"Touch ID", @""); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TOPasscodeViewController/TOPasscodeSettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeSettingsViewController.h 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 | 23 | #import 24 | #import "TOPasscodeViewControllerConstants.h" 25 | 26 | @class TOPasscodeSettingsViewController; 27 | 28 | typedef NS_ENUM(NSInteger, TOPasscodeSettingsViewState) { 29 | TOPasscodeSettingsViewStateEnterCurrentPasscode, 30 | TOPasscodeSettingsViewStateEnterNewPasscode, 31 | TOPasscodeSettingsViewStateConfirmNewPasscode 32 | }; 33 | 34 | NS_ASSUME_NONNULL_BEGIN 35 | 36 | /** 37 | A delegate object in charge of validating and recording the passcodes entered by the user. 38 | */ 39 | @protocol TOPasscodeSettingsViewControllerDelegate 40 | 41 | @optional 42 | 43 | /** Called when the user was prompted to input their current passcode. 44 | Return YES if passcode was right and NO otherwise. 45 | 46 | Returning NO will cause a warning label to appear 47 | */ 48 | - (BOOL)passcodeSettingsViewController:(TOPasscodeSettingsViewController *)passcodeSettingsViewController 49 | didAttemptCurrentPasscode:(NSString *)passcode; 50 | 51 | /** Called when the user has successfully set a new passcode. At this point, you should save over 52 | the old passcode with the new one. */ 53 | - (void)passcodeSettingsViewController:(TOPasscodeSettingsViewController *)passcodeSettingsViewController 54 | didChangeToNewPasscode:(NSString *)passcode ofType:(TOPasscodeType)type; 55 | 56 | @end 57 | 58 | // ---------------------------------------------------------------------- 59 | 60 | /** 61 | A standard system-styled view controller that users can use to change the passcode 62 | that they will need to enter for the main passcode view controller. 63 | 64 | This controller allows requiring the user to enter their previous passcode in first, 65 | and has passcode validation by requiring them to enter the new passcode twice. 66 | */ 67 | 68 | @interface TOPasscodeSettingsViewController : UIViewController 69 | 70 | /** Delegate event for controlling and responding to the behavior of this controller */ 71 | @property (nonatomic, weak, nullable) id delegate; 72 | 73 | /** The current state of the controller (confirming old passcode or creating a new one) */ 74 | @property (nonatomic, assign) TOPasscodeSettingsViewState state; 75 | 76 | /** Set the visual style of the view controller (light or dark) */ 77 | @property (nonatomic, assign) TOPasscodeSettingsViewStyle style; 78 | 79 | /** The input type of the passcode */ 80 | @property (nonatomic, assign) TOPasscodeType passcodeType; 81 | 82 | /** The number of incorrect passcode attempts the user has made. Use this property to decide when to disable input. */ 83 | @property (nonatomic, assign) NSInteger failedPasscodeAttemptCount; 84 | 85 | /** Before setting a new passcode, show a UI to validate the existing passcode. (Default is NO) */ 86 | @property (nonatomic, assign) BOOL requireCurrentPasscode; 87 | 88 | /** If set, the view controller will disable input until this date time has been reached */ 89 | @property (nonatomic, strong, nullable) NSDate *disabledInputDate; 90 | 91 | /* 92 | Create a new instance with the desird light or dark style 93 | 94 | @param style The visual style of the view controller 95 | */ 96 | - (instancetype)initWithStyle:(TOPasscodeSettingsViewStyle)style; 97 | 98 | /* 99 | Changes the passcode type and animates if required 100 | 101 | @param passcodeType Change the type of passcode to enter. 102 | @param animated Play a crossfade animation. 103 | */ 104 | - (void)setPasscodeType:(TOPasscodeType)passcodeType animated:(BOOL)animated; 105 | 106 | @end 107 | 108 | NS_ASSUME_NONNULL_END 109 | -------------------------------------------------------------------------------- /TOPasscodeViewController/TOPasscodeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeViewController.h 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 | 23 | #import 24 | #import "TOPasscodeViewControllerConstants.h" 25 | #import "TOPasscodeView.h" 26 | 27 | NS_ASSUME_NONNULL_BEGIN 28 | 29 | @class TOPasscodeViewController; 30 | 31 | /** 32 | A delegate object in charge of validating the passcodes that the user has entered into the passcode 33 | view controller. 34 | */ 35 | @protocol TOPasscodeViewControllerDelegate 36 | 37 | @optional 38 | 39 | /** 40 | Return YES if the user entered the expected PIN code. Return NO if it was incorrect. 41 | (For security reasons, it is safer to fetch the saved PIN code only when this method is called, and 42 | then discard it immediately. This is why the view controller does not directly store it.) 43 | */ 44 | - (BOOL)passcodeViewController:(TOPasscodeViewController *)passcodeViewController isCorrectCode:(NSString *)code; 45 | 46 | /** The user tapped the 'Cancel' button. Any dismissing of confidential content should be done in here. */ 47 | - (void)didTapCancelInPasscodeViewController:(TOPasscodeViewController *)passcodeViewController; 48 | 49 | /** The user successfully entered the correct code, as validated by `isCorrectCode:` */ 50 | - (void)didInputCorrectPasscodeInPasscodeViewController:(TOPasscodeViewController *)passcodeViewController; 51 | 52 | /** When available, the user tapped the 'Touch ID' button, or the view controller itself automatically initiated 53 | the Touch ID request on display. This method is where you should implement your 54 | own Touch ID validation logic. For security reasons, this controller does not implement the Touch ID logic itself. */ 55 | 56 | - (void)didPerformBiometricValidationRequestInPasscodeViewController:(TOPasscodeViewController *)passcodeViewController; 57 | 58 | /** Called when the pin view was resized as a result of the view controller being resized. 59 | You can use this to resize your custom header view if necessary. 60 | */ 61 | - (void)passcodeViewController:(TOPasscodeViewController *)passcodeViewController didResizePasscodeViewToWidth:(CGFloat)width; 62 | 63 | @end 64 | 65 | 66 | /** 67 | A view controller that displays an interface for entering a user passcode. 68 | It may be presented modally over another view controller, requiring the user to enter 69 | the passcode correctly before they are able to proceed inside the application. 70 | */ 71 | @interface TOPasscodeViewController : UIViewController 72 | 73 | /** A delegate object, in charge of verifying the PIN code entered by the user */ 74 | @property (nonatomic, weak, nullable) id delegate; 75 | 76 | /** The base style of the PIN view controller. Can be configured further. */ 77 | @property (nonatomic, assign) TOPasscodeViewStyle style; 78 | 79 | /** The type of passcode that is expected to be entered. */ 80 | @property (nonatomic, readonly) TOPasscodeType passcodeType; 81 | 82 | /** Will show a 'Touch ID' or 'Face ID' (depending on `biometricType`) button if the user is allowed to log in that way. (Default is NO) */ 83 | @property (nonatomic, assign) BOOL allowBiometricValidation; 84 | 85 | /** Will show a default 'Cancel' button if rightAccessoryButton is not set. (Default is YES) */ 86 | @property (nonatomic, assign) BOOL allowCancel; 87 | 88 | /** Set the type of biometrics for this device to update the title of the biometrics button properly. */ 89 | @property (nonatomic, assign) TOPasscodeBiometryType biometryType; 90 | 91 | /** If biometrics are available, automatically ask for it upon presentation (Default is NO) */ 92 | @property (nonatomic, assign) BOOL automaticallyPromptForBiometricValidation; 93 | 94 | /** Optionally change the color of the title text label. */ 95 | @property (nonatomic, strong, nullable) UIColor *titleLabelColor; 96 | 97 | /** Optionally change the tint color of the UI element that indicates input progress (eg the row of circles) */ 98 | @property (nonatomic, strong, nullable) UIColor *inputProgressViewTintColor; 99 | 100 | /** Optionally enable or disable showing the lettering label of all keypad circle buttons. **/ 101 | @property (nonatomic, assign) BOOL keypadButtonShowLettering; 102 | 103 | /** If the style isn't translucent, changes the tint color of the keypad circle button outlines. */ 104 | @property (nonatomic, strong, nullable) UIColor *keypadButtonBackgroundTintColor; 105 | 106 | /** The color of the text elements in each keypad button */ 107 | @property (nonatomic, strong, nullable) UIColor *keypadButtonTextColor; 108 | 109 | /** Optionally, the text color of the keypad button text when tapped. Animates back to the base color. */ 110 | @property (nonatomic, strong, nullable) UIColor *keypadButtonHighlightedTextColor; 111 | 112 | /** The tint button of the accessory button views at the bottom of the keypad (ie 'Cance' etc) */ 113 | @property (nonatomic, strong, nullable) UIColor *accessoryButtonTintColor; 114 | 115 | /** Controls the transluceny of the PIN background when the style has been set to translucent. */ 116 | @property (nonatomic, readonly) UIVisualEffectView *backgroundEffectView; 117 | 118 | /** Opaque, background view when the style is opaque */ 119 | @property (nonatomic, readonly) UIView *backgroundView; 120 | 121 | /** The keypad and accessory views that are displayed in the center of this view */ 122 | @property (nonatomic, readonly) TOPasscodeView *passcodeView; 123 | 124 | /** The Touch ID button, visible if biometrics is enabled and `leftAccessoryButton` is nil. */ 125 | @property (nonatomic, readonly) UIButton *biometricButton; 126 | 127 | /** The Cancel, visible if `rightAccessoryButton` is nil. */ 128 | @property (nonatomic, readonly) UIButton *cancelButton; 129 | 130 | /** The left accessory button. Setting this will override the 'Touch ID' button. */ 131 | @property (nonatomic, strong, nullable) UIButton *leftAccessoryButton; 132 | 133 | /** The right accessory button. Setting this will override the 'Cancel' button. */ 134 | @property (nonatomic, strong, nullable) UIButton *rightAccessoryButton; 135 | 136 | /** Whether all of the content views are hidden or not, but the background translucent view remains. 137 | Useful for obscuring the content while the app is suspended. */ 138 | @property (nonatomic, assign) BOOL contentHidden; 139 | 140 | /** 141 | Create a new instance of this view controller with the preset style and passcode type. 142 | 143 | @param style The visual style of the view controller (light/translucent) 144 | @param type The type of passcode to enter (6-digit/numeric) 145 | */ 146 | - (instancetype)initWithStyle:(TOPasscodeViewStyle)style passcodeType:(TOPasscodeType)type; 147 | 148 | /** 149 | Hide everything except the background translucency view. 150 | 151 | @param hidden Whether the content is hidden or not. 152 | @param animated The content will play a crossfade animation. 153 | */ 154 | - (void)setContentHidden:(BOOL)hidden animated:(BOOL)animated; 155 | 156 | @end 157 | 158 | NS_ASSUME_NONNULL_END 159 | 160 | //! Project version number for TOPasscodeViewController. 161 | FOUNDATION_EXPORT double TOPasscodeViewControllerVersionNumber; 162 | 163 | //! Project version string for TOPasscodeViewController. 164 | FOUNDATION_EXPORT const unsigned char TOPasscodeViewControllerVersionString[]; 165 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Main/TOPasscodeCircleButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeCircleButton.h 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 | 23 | #import 24 | 25 | @class TOPasscodeCircleView; 26 | @class TOPasscodeButtonLabel; 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | /** 31 | A UI control representing a single PIN code button for the keypad, 32 | including the number, lettering (eg 'ABC'), and circle border. 33 | */ 34 | @interface TOPasscodeCircleButton : UIControl 35 | 36 | // Alpha value that properly controls the necessary subviews 37 | @property (nonatomic, assign) CGFloat contentAlpha; 38 | 39 | // Required to be set before this view can be properly rendered 40 | @property (nonatomic, strong) UIImage *backgroundImage; 41 | @property (nonatomic, strong) UIImage *hightlightedBackgroundImage; 42 | @property (nonatomic, strong) UIVibrancyEffect *vibrancyEffect; 43 | 44 | // Properties with default values 45 | @property (nonatomic, strong) UIColor *textColor; 46 | @property (nonatomic, strong, nullable) UIColor *highlightedTextColor; 47 | @property (nonatomic, strong) UIFont *numberFont; 48 | @property (nonatomic, strong) UIFont *letteringFont; 49 | @property (nonatomic, assign) CGFloat letteringCharacterSpacing; 50 | @property (nonatomic, assign) CGFloat letteringVerticalSpacing; 51 | 52 | @property (nonatomic, readonly) NSString *numberString; 53 | @property (nonatomic, readonly) NSString *letteringString; 54 | 55 | // The internal views 56 | @property (nonatomic, readonly) TOPasscodeButtonLabel *buttonLabel; 57 | @property (nonatomic, readonly) TOPasscodeCircleView *circleView; 58 | @property (nonatomic, readonly) UIVisualEffectView *vibrancyView; 59 | 60 | // Callback handler 61 | @property (nonatomic, copy) void (^buttonTappedHandler)(void); 62 | 63 | /** 64 | Create a new instance of the class with the supplied number and lettering string 65 | 66 | @param numberString The string of the number to display in this button (eg '1'). 67 | @param letteringString The string of the lettering to display underneath. 68 | */ 69 | - (instancetype)initWithNumberString:(NSString *)numberString letteringString:(NSString *)letteringString; 70 | 71 | /** 72 | Set the background of the button to be the filled circle instead of hollow. 73 | 74 | @param highlighted When YES, the circle is full, when NO, it is hollow. 75 | @param animated When animated, the transition is a crossfade. 76 | */ 77 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated; 78 | 79 | @end 80 | 81 | NS_ASSUME_NONNULL_END 82 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Main/TOPasscodeCircleButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeCircleButton.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 | 23 | #import "TOPasscodeCircleButton.h" 24 | #import "TOPasscodeCircleView.h" 25 | #import "TOPasscodeButtonLabel.h" 26 | 27 | @interface TOPasscodeCircleButton () 28 | 29 | @property (nonatomic, strong, readwrite) TOPasscodeButtonLabel *buttonLabel; 30 | @property (nonatomic, strong, readwrite) TOPasscodeCircleView *circleView; 31 | @property (nonatomic, strong, readwrite) UIVisualEffectView *vibrancyView; 32 | 33 | @property (nonatomic, readwrite, copy) NSString *numberString; 34 | @property (nonatomic, readwrite, copy) NSString *letteringString; 35 | 36 | @end 37 | 38 | @implementation TOPasscodeCircleButton 39 | 40 | - (instancetype)initWithNumberString:(NSString *)numberString letteringString:(NSString *)letteringString 41 | { 42 | if (self = [super init]) { 43 | _numberString = numberString; 44 | _letteringString = letteringString; 45 | _contentAlpha = 1.0f; 46 | [self setUp]; 47 | } 48 | 49 | return self; 50 | } 51 | 52 | - (void)setUp 53 | { 54 | self.userInteractionEnabled = YES; 55 | 56 | _textColor = [UIColor whiteColor]; 57 | 58 | [self setUpSubviews]; 59 | [self setUpViewInteraction]; 60 | } 61 | 62 | - (void)setUpSubviews 63 | { 64 | if (!self.circleView) { 65 | self.circleView = [[TOPasscodeCircleView alloc] initWithFrame:self.bounds]; 66 | [self addSubview:self.circleView]; 67 | } 68 | 69 | if (!self.buttonLabel) { 70 | self.buttonLabel = [[TOPasscodeButtonLabel alloc] initWithFrame:self.bounds]; 71 | self.buttonLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 72 | self.buttonLabel.userInteractionEnabled = NO; 73 | self.buttonLabel.textColor = self.textColor; 74 | self.buttonLabel.numberString = self.numberString; 75 | self.buttonLabel.letteringString = self.letteringString; 76 | [self addSubview:self.buttonLabel]; 77 | } 78 | 79 | if (!self.vibrancyView) { 80 | self.vibrancyView = [[UIVisualEffectView alloc] initWithEffect:nil]; 81 | self.vibrancyView.userInteractionEnabled = NO; 82 | [self.vibrancyView.contentView addSubview:self.circleView]; 83 | [self addSubview:self.vibrancyView]; 84 | } 85 | } 86 | 87 | - (void)setUpViewInteraction 88 | { 89 | if (self.allTargets.count) { return; } 90 | 91 | [self addTarget:self action:@selector(buttonDidTouchDown:) forControlEvents:UIControlEventTouchDown]; 92 | [self addTarget:self action:@selector(buttonDidTouchUpInside:) forControlEvents:UIControlEventTouchUpInside]; 93 | [self addTarget:self action:@selector(buttonDidDragInside:) forControlEvents:UIControlEventTouchDragEnter]; 94 | [self addTarget:self action:@selector(buttonDidDragOutside:) forControlEvents:UIControlEventTouchDragExit]; 95 | } 96 | 97 | - (void)layoutSubviews 98 | { 99 | [super layoutSubviews]; 100 | 101 | self.vibrancyView.frame = self.bounds; 102 | self.circleView.frame = self.vibrancyView ? self.vibrancyView.bounds : self.bounds; 103 | self.buttonLabel.frame = self.bounds; 104 | [self bringSubviewToFront:self.buttonLabel]; 105 | } 106 | 107 | #pragma mark - User Interaction - 108 | 109 | - (void)buttonDidTouchDown:(id)sender 110 | { 111 | if (self.buttonTappedHandler) { self.buttonTappedHandler(); } 112 | [self setHighlighted:YES animated:NO]; 113 | } 114 | 115 | - (void)buttonDidTouchUpInside:(id)sender { [self setHighlighted:NO animated:YES]; } 116 | - (void)buttonDidDragInside:(id)sender { [self setHighlighted:YES animated:NO]; } 117 | - (void)buttonDidDragOutside:(id)sender { [self setHighlighted:NO animated:YES]; } 118 | 119 | #pragma mark - Animated Accessors - 120 | 121 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated 122 | { 123 | [self.circleView setHighlighted:highlighted animated:animated]; 124 | 125 | if (!self.highlightedTextColor) { return; } 126 | 127 | void (^textFadeBlock)(void) = ^{ 128 | self.buttonLabel.textColor = highlighted ? self.highlightedTextColor : self.textColor; 129 | }; 130 | 131 | if (!animated) { 132 | textFadeBlock(); 133 | return; 134 | } 135 | 136 | [UIView transitionWithView:self.buttonLabel 137 | duration:0.6f 138 | options:UIViewAnimationOptionTransitionCrossDissolve 139 | animations:textFadeBlock 140 | completion:nil]; 141 | } 142 | 143 | #pragma mark - Accessors - 144 | 145 | - (void)setBackgroundImage:(UIImage *)backgroundImage 146 | { 147 | self.circleView.circleImage = backgroundImage; 148 | CGRect frame = self.frame; 149 | frame.size = backgroundImage.size; 150 | self.frame = CGRectIntegral(frame); 151 | } 152 | 153 | - (UIImage *)backgroundImage { return self.circleView.circleImage; } 154 | 155 | /***********************************************************/ 156 | 157 | - (void)setVibrancyEffect:(UIVibrancyEffect *)vibrancyEffect 158 | { 159 | if (_vibrancyEffect == vibrancyEffect) { return; } 160 | _vibrancyEffect = vibrancyEffect; 161 | self.vibrancyView.effect = _vibrancyEffect; 162 | } 163 | 164 | /***********************************************************/ 165 | 166 | - (void)setHightlightedBackgroundImage:(UIImage *)hightlightedBackgroundImage 167 | { 168 | self.circleView.highlightedCircleImage = hightlightedBackgroundImage; 169 | } 170 | 171 | - (UIImage *)hightlightedBackgroundImage { return self.circleView.highlightedCircleImage; } 172 | 173 | /***********************************************************/ 174 | 175 | - (void)setNumberFont:(UIFont *)numberFont 176 | { 177 | self.buttonLabel.numberLabelFont = numberFont; 178 | [self setNeedsLayout]; 179 | } 180 | 181 | - (UIFont *)numberFont { return self.buttonLabel.numberLabelFont; } 182 | 183 | /***********************************************************/ 184 | 185 | - (void)setLetteringFont:(UIFont *)letteringFont 186 | { 187 | self.buttonLabel.letteringLabelFont = letteringFont; 188 | [self setNeedsLayout]; 189 | } 190 | 191 | - (UIFont *)letteringFont { return self.buttonLabel.letteringLabelFont; } 192 | 193 | /***********************************************************/ 194 | 195 | - (void)setLetteringVerticalSpacing:(CGFloat)letteringVerticalSpacing 196 | { 197 | self.buttonLabel.letteringVerticalSpacing = letteringVerticalSpacing; 198 | [self.buttonLabel setNeedsLayout]; 199 | } 200 | 201 | - (CGFloat)letteringVerticalSpacing { return self.buttonLabel.letteringVerticalSpacing; } 202 | 203 | /***********************************************************/ 204 | 205 | - (void)setLetteringCharacterSpacing:(CGFloat)letteringCharacterSpacing 206 | { 207 | self.buttonLabel.letteringCharacterSpacing = letteringCharacterSpacing; 208 | } 209 | 210 | - (CGFloat)letteringCharacterSpacing { return self.buttonLabel.letteringCharacterSpacing; } 211 | 212 | /***********************************************************/ 213 | 214 | - (void)setTextColor:(UIColor *)textColor 215 | { 216 | if (textColor == _textColor) { return; } 217 | _textColor = textColor; 218 | 219 | self.buttonLabel.textColor = _textColor; 220 | } 221 | 222 | /***********************************************************/ 223 | 224 | - (void)setContentAlpha:(CGFloat)contentAlpha 225 | { 226 | if (_contentAlpha == contentAlpha) { 227 | return; 228 | } 229 | 230 | _contentAlpha = contentAlpha; 231 | 232 | self.buttonLabel.alpha = contentAlpha; 233 | self.circleView.alpha = contentAlpha; 234 | } 235 | 236 | @end 237 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Main/TOPasscodeKeypadView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeKeypadView.h 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 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | @class TOPasscodeCircleButton; 28 | 29 | /** 30 | A view encompassing 9 circle buttons, making up a keypad view for entering PIN numbers. 31 | Can be laid out vertically or horizontally. 32 | */ 33 | @interface TOPasscodeKeypadView : UIView 34 | 35 | /** The type of layout for the buttons (Default is vertical) */ 36 | @property (nonatomic, assign) BOOL horizontalLayout; 37 | 38 | /** The vibrancy effect to be applied to each button background */ 39 | @property (nonatomic, strong, nullable) UIVibrancyEffect *vibrancyEffect; 40 | 41 | /** The size of each input button */ 42 | @property (nonatomic, assign) CGFloat buttonDiameter; 43 | 44 | /** The stroke width of the buttons */ 45 | @property (nonatomic, assign) CGFloat buttonStrokeWidth; 46 | 47 | /** The spacing between the buttons. Default is (CGSize){25,15} */ 48 | @property (nonatomic, assign) CGSize buttonSpacing; 49 | 50 | /** The font of the number in each button */ 51 | @property (nonatomic, strong) UIFont *buttonNumberFont; 52 | 53 | /** The font of the lettering label */ 54 | @property (nonatomic, strong) UIFont *buttonLetteringFont; 55 | 56 | /** The spacing between the lettering and the number label */ 57 | @property (nonatomic, assign) CGFloat buttonLabelSpacing; 58 | 59 | /** The spacing between the letters in the lettering label */ 60 | @property (nonatomic, assign) CGFloat buttonLetteringSpacing; 61 | 62 | /** Show the 'ABC' lettering under the numbers */ 63 | @property (nonatomic, assign) BOOL showLettering; 64 | 65 | /** The spacing in points between the letters */ 66 | @property (nonatomic, assign) CGFloat letteringSpacing; 67 | 68 | /** The tint color of the button backgrounds */ 69 | @property (nonatomic, strong) UIColor *buttonBackgroundColor; 70 | 71 | /** The color of the text elements in each button */ 72 | @property (nonatomic, strong) UIColor *buttonTextColor; 73 | 74 | /** Optionally the color of text when it's tapped. */ 75 | @property (nonatomic, strong, nullable) UIColor *buttonHighlightedTextColor; 76 | 77 | /** The alpha value of all non-translucent views */ 78 | @property (nonatomic, assign) CGFloat contentAlpha; 79 | 80 | /** Accessory views placed on either side of the '0' button */ 81 | @property (nonatomic, strong, nullable) UIView *leftAccessoryView; 82 | @property (nonatomic, strong, nullable) UIView *rightAccessoryView; 83 | 84 | /** The controls making up each of the button views */ 85 | @property (nonatomic, readonly) NSArray *keypadButtons; 86 | 87 | /** The block that is triggered whenever a user taps one of the buttons */ 88 | @property (nonatomic, copy) void (^buttonTappedHandler)(NSInteger buttonNumber); 89 | 90 | /* 91 | Perform an animation to transition to a new layout. 92 | 93 | @param horizontalLayout The content is laid out horizontally. 94 | @param animated Whether the transition is animated 95 | @param duration The animation length of the transition. 96 | */ 97 | - (void)setHorizontalLayout:(BOOL)horizontalLayout animated:(BOOL)animated duration:(CGFloat)duration; 98 | 99 | @end 100 | 101 | NS_ASSUME_NONNULL_END 102 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Main/TOPasscodeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeView.h 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 | 23 | #import 24 | #import "TOPasscodeViewControllerConstants.h" 25 | #import "TOPasscodeKeypadView.h" 26 | 27 | NS_ASSUME_NONNULL_BEGIN 28 | 29 | @class TOPasscodeCircleButton; 30 | @class TOPasscodeInputField; 31 | @class TOPasscodeKeypadView; 32 | @class TOPasscodeViewContentLayout; 33 | 34 | /** 35 | The passcode view is the primary content view for the passcode view controller. 36 | On iPad, every view except the background view is a subview of this view. 37 | On iPhone, the auxiliary buttons ('Touch ID', 'Cancel') are managed by the view controller. 38 | */ 39 | @interface TOPasscodeView : UIView 40 | 41 | /* The visual style of the view */ 42 | @property (nonatomic, assign) TOPasscodeViewStyle style; 43 | 44 | /* The type of passcode being managed by it */ 45 | @property (nonatomic, readonly) TOPasscodeType passcodeType; 46 | 47 | /* Whether the content is laid out vertically or horizontally (iPhone only) */ 48 | @property (nonatomic, assign) BOOL horizontalLayout; 49 | 50 | /* The text in the title view (Default is 'Enter Passcode') */ 51 | @property (nonatomic, copy) NSString *titleText; 52 | 53 | /* Customizable Accessory Views */ 54 | @property (nonatomic, strong, nullable) UIView *titleView; 55 | @property (nonatomic, strong, nullable) UIButton *leftButton; 56 | @property (nonatomic, strong, nullable) UIButton *rightButton; 57 | 58 | /* The default views always shown in this view */ 59 | @property (nonatomic, readonly) UILabel *titleLabel; 60 | @property (nonatomic, readonly) TOPasscodeInputField *inputField; 61 | @property (nonatomic, readonly) TOPasscodeKeypadView *keypadView; 62 | 63 | /* Overrides for theming the various elements. */ 64 | @property (nonatomic, strong, nullable) UIColor *titleLabelColor; 65 | @property (nonatomic, strong, nullable) UIColor *inputProgressViewTintColor; 66 | @property (nonatomic, strong, nullable) UIColor *keypadButtonBackgroundColor; 67 | @property (nonatomic, strong, nullable) UIColor *keypadButtonTextColor; 68 | @property (nonatomic, strong, nullable) UIColor *keypadButtonHighlightedTextColor; 69 | 70 | /* Horizontal inset from edge of keypad view to button center */ 71 | @property (nonatomic, readonly) CGFloat keypadButtonInset; 72 | 73 | /* An animatable property for animating the non-translucent subviews */ 74 | @property (nonatomic, assign) CGFloat contentAlpha; 75 | 76 | /* The passcode currently entered into this view */ 77 | @property (nonatomic, copy, nullable) NSString *passcode; 78 | 79 | /* The default layout object controlling the 80 | sizing and placement of all this view's child elements. */ 81 | @property (nonatomic, strong, null_resettable) TOPasscodeViewContentLayout *defaultContentLayout; 82 | 83 | /* As needed, additional layout objects that will be checked and used in priority over the default content layout. */ 84 | @property (nonatomic, strong, nullable) NSArray *contentLayouts; 85 | 86 | /* Callback triggered each time the user taps a key */ 87 | @property (nonatomic, copy, nullable) void (^passcodeDigitEnteredHandler)(void); 88 | 89 | /* Callback triggered when the user has finished entering the passcode */ 90 | @property (nonatomic, copy, nullable) void (^passcodeCompletedHandler)(NSString *passcode); 91 | 92 | /* 93 | Create a new instance with one of the style types 94 | 95 | @param style The visual style of the passcode view. 96 | @param type The type of passcode to accept. 97 | */ 98 | - (instancetype)initWithStyle:(TOPasscodeViewStyle)style passcodeType:(TOPasscodeType)type; 99 | 100 | /* 101 | Resize the view and all subviews for the optimum size to fit a super view of the suplied width. 102 | 103 | @param size The size of the view to which this view. 104 | */ 105 | - (void)sizeToFitSize:(CGSize)size; 106 | 107 | /* 108 | Reset the passcode to nil and optionally play animation / vibration to match 109 | 110 | @param animated Play a shaking animation to reset the passcode. 111 | @param impact On supported devices, play a small reset vibration as well. 112 | */ 113 | - (void)resetPasscodeAnimated:(BOOL)animated playImpact:(BOOL)impact; 114 | 115 | /* 116 | Delete the last character from the passcode 117 | 118 | @param animated Whether the delete operation is animated or not. 119 | */ 120 | - (void)deleteLastPasscodeCharacterAnimated:(BOOL)animated; 121 | 122 | /* 123 | Animate the transition between horizontal and vertical layouts 124 | 125 | @param horizontalLayout Whether to lay out the content vertically or horizontally. 126 | @param animated Whether the transition is animated or not. 127 | @param duration The duration of the animation 128 | */ 129 | - (void)setHorizontalLayout:(BOOL)horizontalLayout animated:(BOOL)animated duration:(CGFloat)duration; 130 | 131 | @end 132 | 133 | NS_ASSUME_NONNULL_END 134 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Settings/TOPasscodeSettingsKeypadButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeSettingsKeypadButton.h 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 | 23 | #import 24 | 25 | @class TOPasscodeButtonLabel; 26 | 27 | /** 28 | A single button view that is used in a number keypad views styled in 29 | a pseudo-skeuomorphic style. 30 | */ 31 | @interface TOPasscodeSettingsKeypadButton : UIButton 32 | 33 | /** Background Images */ 34 | @property (nonatomic, strong) UIImage *buttonBackgroundImage; 35 | @property (nonatomic, strong) UIImage *buttonTappedBackgroundImage; 36 | 37 | /* Inset of the label view from the bottom to account for the bevel */ 38 | @property (nonatomic, assign) CGFloat bottomInset; 39 | 40 | /* The button label containing the number and lettering */ 41 | @property (nonatomic, readonly) TOPasscodeButtonLabel *buttonLabel; 42 | 43 | + (TOPasscodeSettingsKeypadButton *)button; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Settings/TOPasscodeSettingsKeypadButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeSettingsKeypadButton.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 | 23 | #import "TOPasscodeSettingsKeypadButton.h" 24 | #import "TOPasscodeButtonLabel.h" 25 | 26 | @interface TOPasscodeSettingsKeypadButton () 27 | 28 | @property (nonatomic, strong, readwrite) TOPasscodeButtonLabel *buttonLabel; 29 | 30 | @end 31 | 32 | @implementation TOPasscodeSettingsKeypadButton 33 | 34 | + (TOPasscodeSettingsKeypadButton *)button 35 | { 36 | TOPasscodeSettingsKeypadButton *button = [TOPasscodeSettingsKeypadButton buttonWithType:UIButtonTypeCustom]; 37 | button.frame = CGRectMake(0,0,100,60); 38 | return button; 39 | } 40 | 41 | #pragma mark - Lazy Accessor - 42 | - (TOPasscodeButtonLabel *)buttonLabel 43 | { 44 | if (_buttonLabel) { return _buttonLabel; } 45 | 46 | CGRect frame = self.bounds; 47 | frame.size.height -= self.bottomInset; 48 | 49 | _buttonLabel = [[TOPasscodeButtonLabel alloc] initWithFrame:frame]; 50 | _buttonLabel.userInteractionEnabled = NO; 51 | _buttonLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 52 | [self addSubview:_buttonLabel]; 53 | 54 | return _buttonLabel; 55 | } 56 | 57 | #pragma mark - Layout Accessor - 58 | - (void)setBottomInset:(CGFloat)bottomInset 59 | { 60 | _bottomInset = bottomInset; 61 | 62 | CGRect frame = self.bounds; 63 | frame.size.height -= _bottomInset; 64 | self.buttonLabel.frame = frame; 65 | [self setNeedsLayout]; 66 | } 67 | 68 | #pragma mark - Control Accessor - 69 | - (void)setEnabled:(BOOL)enabled 70 | { 71 | [super setEnabled:enabled]; 72 | self.buttonLabel.alpha = enabled ? 1.0f : 0.5f; 73 | } 74 | 75 | #pragma mark - Background Image Accessor - 76 | 77 | - (void)setHighlighted:(BOOL)highlighted { 78 | [self.layer removeAllAnimations]; 79 | [UIView transitionWithView:self 80 | duration:0.25 81 | options:UIViewAnimationOptionTransitionCrossDissolve | 82 | UIViewAnimationOptionAllowAnimatedContent | 83 | UIViewAnimationOptionAllowUserInteraction 84 | animations:^{ 85 | [super setHighlighted:highlighted]; 86 | } completion:nil]; 87 | } 88 | 89 | - (void)setButtonBackgroundImage:(UIImage *)buttonBackgroundImage 90 | { 91 | [self setBackgroundImage:buttonBackgroundImage forState:UIControlStateNormal]; 92 | } 93 | 94 | - (UIImage *)buttonBackgroundImage { return [self backgroundImageForState:UIControlStateNormal]; } 95 | 96 | - (void)setButtonTappedBackgroundImage:(UIImage *)buttonTappedBackgroundImage 97 | { 98 | [self setBackgroundImage:buttonTappedBackgroundImage forState:UIControlStateHighlighted]; 99 | } 100 | 101 | - (UIImage *)buttonTappedBackgroundImage { return [self backgroundImageForState:UIControlStateHighlighted]; } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Settings/TOPasscodeSettingsKeypadView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeSettingsKeypadView.h 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 | 23 | #import 24 | #import "TOPasscodeViewControllerConstants.h" 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | /** 29 | A keypad view of 9 buttons that allow numerical input on both iPad and iPhone. 30 | Designed to match the base system, with a pseudo-skeuomorphical styling. 31 | */ 32 | @interface TOPasscodeSettingsKeypadView : UIView 33 | 34 | /* Whether the control is allowing input */ 35 | @property (nonatomic, assign) BOOL enabled; 36 | 37 | /* Whether the view is currently light mode or dark. */ 38 | @property (nonatomic, assign) TOPasscodeSettingsViewStyle style; 39 | 40 | /* The color of the separator line */ 41 | @property (nonatomic, strong) UIColor *separatorLineColor; 42 | 43 | /* Labels in the buttons are laid out horizontally */ 44 | @property (nonatomic, assign) BOOL buttonLabelHorizontalLayout; 45 | 46 | /* If overridden, the foreground color of the buttons */ 47 | @property (nonatomic, assign) CGFloat keypadButtonBorderThickness; 48 | 49 | /* Untapped background images */ 50 | @property (nonatomic, strong, null_resettable) UIColor *keypadButtonForegroundColor; 51 | @property (nonatomic, strong, null_resettable) UIColor *keypadButtonBorderColor; 52 | 53 | /* Tapped background images */ 54 | @property (nonatomic, strong, null_resettable) UIColor *keypadButtonTappedForegroundColor; 55 | @property (nonatomic, strong, nullable) UIColor *keypadButtonTappedBorderColor; 56 | 57 | /* Button label styling */ 58 | @property (nonatomic, strong) UIFont *keypadButtonNumberFont; 59 | @property (nonatomic, strong) UIFont *keypadButtonLetteringFont; 60 | @property (nonatomic, strong) UIColor *keypadButtonLabelTextColor; 61 | @property (nonatomic, assign) CGFloat keypadButtonVerticalSpacing; 62 | @property (nonatomic, assign) CGFloat keypadButtonHorizontalSpacing; 63 | @property (nonatomic, assign) CGFloat keypadButtonLetteringSpacing; 64 | 65 | /* Callback handlers */ 66 | @property (nonatomic, copy) void (^numberButtonTappedHandler)(NSInteger number); 67 | @property (nonatomic, copy) void (^deleteButtonTappedHandler)(void); 68 | 69 | /* In really small sizes, set the keypad labels to horizontal */ 70 | - (void)setButtonLabelHorizontalLayout:(BOOL)horizontal animated:(BOOL)animated; 71 | 72 | @end 73 | 74 | NS_ASSUME_NONNULL_END 75 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Settings/TOPasscodeSettingsWarningLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeSettingsWarningLabel.h 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 | 23 | #import 24 | 25 | /** 26 | When a user enters an incorrect passcode in the settings interface, 27 | this view is displayed to show the number of failed attempts. 28 | */ 29 | @interface TOPasscodeSettingsWarningLabel : UIImageView 30 | 31 | /** The number of incorrect passcode attempts to display */ 32 | @property (nonatomic, assign) NSInteger numberOfWarnings; 33 | 34 | /** The font of the text */ 35 | @property (nonatomic, strong) UIFont *textFont; 36 | 37 | /** The background color of the view */ 38 | @property (nonatomic, strong) UIColor *backgroundColor; 39 | 40 | /** Set the padding around the label */ 41 | @property (nonatomic, assign) CGSize textPadding; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Settings/TOPasscodeSettingsWarningLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeSettingsWarningLabel.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 | 23 | #import "TOPasscodeSettingsWarningLabel.h" 24 | 25 | @interface TOPasscodeSettingsWarningLabel () 26 | @property (nonatomic, strong) UILabel *label; 27 | @end 28 | 29 | @implementation TOPasscodeSettingsWarningLabel 30 | 31 | @synthesize backgroundColor = __backgroundColor; 32 | 33 | #pragma mark - View Setup - 34 | 35 | - (instancetype)initWithFrame:(CGRect)frame 36 | { 37 | if (self = [super initWithFrame:frame]) { 38 | [self setUp]; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (void)setUp 45 | { 46 | _numberOfWarnings = 0; 47 | _textPadding = CGSizeMake(14.0f, 6.0f); 48 | 49 | self.tintColor = [UIColor colorWithRed:214.0f/255.0f green:63.0f/255.0f blue:63.0f/255.0f alpha:1.0f]; 50 | 51 | self.label = [[UILabel alloc] initWithFrame:CGRectZero]; 52 | self.label.backgroundColor = [UIColor clearColor]; 53 | self.label.textAlignment = NSTextAlignmentCenter; 54 | self.label.textColor = [UIColor whiteColor]; 55 | self.label.font = [UIFont systemFontOfSize:15.0f]; 56 | [self setTextForCount:0]; 57 | [self.label sizeToFit]; 58 | [self addSubview:self.label]; 59 | } 60 | 61 | - (void)didMoveToSuperview 62 | { 63 | [super didMoveToSuperview]; 64 | [self setBackgroundImageIfNeeded]; 65 | } 66 | 67 | #pragma mark - View Layout - 68 | 69 | - (void)sizeToFit 70 | { 71 | [super sizeToFit]; 72 | [self.label sizeToFit]; 73 | 74 | CGRect labelFrame = self.label.frame; 75 | CGRect frame = self.frame; 76 | 77 | labelFrame = CGRectInset(labelFrame, -self.textPadding.width, -self.textPadding.height); 78 | frame.size = labelFrame.size; 79 | self.frame = CGRectIntegral(frame); 80 | } 81 | 82 | - (void)layoutSubviews 83 | { 84 | CGRect frame = self.frame; 85 | CGRect labelFrame = self.label.frame; 86 | 87 | labelFrame.origin.x = (CGRectGetWidth(frame) - CGRectGetWidth(labelFrame)) * 0.5f; 88 | labelFrame.origin.y = (CGRectGetHeight(frame) - CGRectGetHeight(labelFrame)) * 0.5f; 89 | self.label.frame = labelFrame; 90 | } 91 | 92 | #pragma mark - View State Handling - 93 | 94 | - (void)setTextForCount:(NSInteger)count 95 | { 96 | NSString *text = nil; 97 | if (count == 1) { 98 | text = NSLocalizedString(@"1 Failed Passcode Attempt", @""); 99 | } 100 | else { 101 | text = [NSString stringWithFormat:NSLocalizedString(@"%d Failed Passcode Attempts", @""), count]; 102 | } 103 | self.label.text = text; 104 | 105 | [self sizeToFit]; 106 | } 107 | 108 | #pragma mark - Background Image Managements - 109 | 110 | - (void)setBackgroundImageIfNeeded 111 | { 112 | // Don't bother if we're not in a view 113 | if (self.superview == nil) { return; } 114 | 115 | // Compare the view height and don't proceed if 116 | if (lround(self.image.size.height) == lround(self.frame.size.height)) { return; } 117 | 118 | // Create the image 119 | self.image = [[self class] roundedBackgroundImageWithHeight:self.frame.size.height]; 120 | } 121 | 122 | + (UIImage *)roundedBackgroundImageWithHeight:(CGFloat)height 123 | { 124 | UIImage *image = nil; 125 | CGRect frame = CGRectZero; 126 | frame.size.width = height + 1.0; 127 | frame.size.height = height; 128 | 129 | UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0f); 130 | { 131 | UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:height * 0.5f]; 132 | [[UIColor blackColor] setFill]; 133 | [path fill]; 134 | 135 | image = UIGraphicsGetImageFromCurrentImageContext(); 136 | } 137 | UIGraphicsEndImageContext(); 138 | 139 | CGFloat halfHeight = height * 0.5f; 140 | UIEdgeInsets insets = UIEdgeInsetsMake(halfHeight, halfHeight, halfHeight, halfHeight); 141 | image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 142 | image = [image resizableImageWithCapInsets:insets]; 143 | return image; 144 | } 145 | 146 | #pragma mark - Accessors - 147 | 148 | - (void)setNumberOfWarnings:(NSInteger)numberOfWarnings 149 | { 150 | _numberOfWarnings = numberOfWarnings; 151 | [self setTextForCount:_numberOfWarnings]; 152 | } 153 | 154 | @end 155 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeButtonLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeButtonLabel.h 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 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | /** 28 | A view that manages two label subviews: a larger label showing a single number 29 | and a smaller label showing lettering as well. 30 | */ 31 | @interface TOPasscodeButtonLabel : UIView 32 | 33 | // Draws the lettering label to the side 34 | @property (nonatomic, assign) BOOL horizontalLayout; 35 | 36 | // The strings of both labels 37 | @property (nonatomic, copy) NSString *numberString; 38 | @property (nonatomic, copy, nullable) NSString *letteringString; 39 | 40 | // The color of both labels 41 | @property (nonatomic, strong) UIColor *textColor; 42 | 43 | // The label views 44 | @property (nonatomic, readonly) UILabel *numberLabel; 45 | @property (nonatomic, readonly) UILabel *letteringLabel; 46 | 47 | // The fonts for each label (In case they are nil) 48 | @property (nonatomic, strong) UIFont *numberLabelFont; 49 | @property (nonatomic, strong) UIFont *letteringLabelFont; 50 | 51 | // Has initial default values 52 | @property (nonatomic, assign) CGFloat letteringCharacterSpacing; 53 | @property (nonatomic, assign) CGFloat letteringVerticalSpacing; 54 | @property (nonatomic, assign) CGFloat letteringHorizontalSpacing; 55 | 56 | // Whether the number label is centered vertically or not (NO by default) 57 | @property (nonatomic, assign) BOOL verticallyCenterNumberLabel; 58 | 59 | @end 60 | 61 | NS_ASSUME_NONNULL_END 62 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeButtonLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeButtonLabel.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 | 23 | #import "TOPasscodeButtonLabel.h" 24 | 25 | @interface TOPasscodeButtonLabel () 26 | 27 | @property (nonatomic, strong, readwrite) UILabel *numberLabel; 28 | @property (nonatomic, strong, readwrite) UILabel *letteringLabel; 29 | 30 | @end 31 | 32 | @implementation TOPasscodeButtonLabel 33 | 34 | #pragma mark - View Setup - 35 | 36 | - (instancetype)initWithFrame:(CGRect)frame 37 | { 38 | if (self = [super initWithFrame:frame]) { 39 | _letteringVerticalSpacing = 6.0f; 40 | _letteringCharacterSpacing = 3.0f; 41 | _letteringHorizontalSpacing = 5.0f; 42 | _numberLabelFont = [UIFont systemFontOfSize:37.5f weight:UIFontWeightThin]; 43 | _letteringLabelFont = [UIFont systemFontOfSize:9.0f weight:UIFontWeightThin]; 44 | [self setUpViews]; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (void)setUpViews 51 | { 52 | if (!self.numberLabel) { 53 | self.numberLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 54 | self.numberLabel.text = self.numberString; 55 | self.numberLabel.textColor = self.textColor; 56 | self.numberLabel.font = self.numberLabelFont; 57 | [self.numberLabel sizeToFit]; 58 | [self addSubview:self.numberLabel]; 59 | } 60 | 61 | // Create the lettering string only if we have a lettering value for it 62 | if (!self.letteringLabel && self.letteringString.length > 0) { 63 | self.letteringLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 64 | self.letteringLabel.textColor = self.textColor; 65 | self.letteringLabel.font = self.letteringLabelFont; 66 | [self.letteringLabel sizeToFit]; 67 | [self addSubview:self.letteringLabel]; 68 | [self updateLetteringLabelText]; 69 | } 70 | } 71 | 72 | #pragma mark - View Layout - 73 | 74 | - (void)updateLetteringLabelText 75 | { 76 | if (self.letteringString.length == 0) { 77 | return; 78 | } 79 | 80 | NSMutableAttributedString* attrStr = [[NSMutableAttributedString alloc] initWithString:self.letteringString]; 81 | [attrStr addAttribute:NSKernAttributeName value:@(_letteringCharacterSpacing) range:NSMakeRange(0, attrStr.length-1)]; 82 | self.letteringLabel.attributedText = attrStr; 83 | } 84 | 85 | - (void)layoutSubviews 86 | { 87 | [super layoutSubviews]; 88 | 89 | CGSize viewSize = self.bounds.size; 90 | 91 | [self.numberLabel sizeToFit]; 92 | [self.letteringLabel sizeToFit]; 93 | 94 | CGFloat numberVerticalHeight = self.numberLabelFont.capHeight; 95 | CGFloat letteringVerticalHeight = self.letteringLabelFont.capHeight; 96 | CGFloat textTotalHeight = (numberVerticalHeight+2.0f) + self.letteringVerticalSpacing + (letteringVerticalHeight+2.0f); 97 | 98 | CGRect frame = self.numberLabel.frame; 99 | frame.size.height = ceil(numberVerticalHeight) + 2.0f; 100 | frame.origin.x = ceilf((viewSize.width - frame.size.width) * 0.5f); 101 | 102 | if (!self.horizontalLayout && !self.verticallyCenterNumberLabel) { 103 | frame.origin.y = floorf((viewSize.height - textTotalHeight) * 0.5f); 104 | } 105 | else { 106 | frame.origin.y = floorf((viewSize.height - frame.size.height) * 0.5f); 107 | } 108 | self.numberLabel.frame = CGRectIntegral(frame); 109 | 110 | if (self.letteringLabel) { 111 | CGFloat y = CGRectGetMaxY(frame); 112 | y += self.letteringVerticalSpacing; 113 | 114 | frame = self.letteringLabel.frame; 115 | frame.size.height = ceil(letteringVerticalHeight) + 2.0f; 116 | 117 | if (!self.horizontalLayout) { 118 | frame.origin.y = floorf(y); 119 | frame.origin.x = (viewSize.width - frame.size.width) * 0.5f; 120 | } 121 | else { 122 | frame.origin.y = floorf((viewSize.height - frame.size.height) * 0.5f); 123 | frame.origin.x = CGRectGetMaxX(self.numberLabel.frame) + self.letteringHorizontalSpacing; 124 | } 125 | 126 | self.letteringLabel.frame = CGRectIntegral(frame); 127 | } 128 | } 129 | 130 | #pragma mark - Accessors - 131 | 132 | - (void)setTextColor:(UIColor *)textColor 133 | { 134 | if (textColor == _textColor) { return; } 135 | _textColor = textColor; 136 | 137 | self.numberLabel.textColor = _textColor; 138 | self.letteringLabel.textColor = _textColor; 139 | } 140 | /***********************************************************/ 141 | 142 | - (void)setNumberString:(NSString *)numberString 143 | { 144 | self.numberLabel.text = numberString; 145 | [self setNeedsLayout]; 146 | } 147 | 148 | - (NSString *)numberString { return self.numberLabel.text; } 149 | 150 | /***********************************************************/ 151 | 152 | - (void)setLetteringString:(NSString *)letteringString 153 | { 154 | _letteringString = [letteringString copy]; 155 | [self setUpViews]; 156 | [self updateLetteringLabelText]; 157 | [self setNeedsLayout]; 158 | } 159 | 160 | /***********************************************************/ 161 | 162 | - (void)setLetteringCharacterSpacing:(CGFloat)letteringCharacterSpacing 163 | { 164 | _letteringCharacterSpacing = letteringCharacterSpacing; 165 | [self updateLetteringLabelText]; 166 | } 167 | 168 | /***********************************************************/ 169 | 170 | - (void)setNumberLabelFont:(UIFont *)numberLabelFont 171 | { 172 | if (_numberLabelFont == numberLabelFont) { return; } 173 | _numberLabelFont = numberLabelFont; 174 | self.numberLabel.font = _numberLabelFont; 175 | } 176 | 177 | /***********************************************************/ 178 | 179 | - (void)setLetteringLabelFont:(UIFont *)letteringLabelFont 180 | { 181 | if (_letteringLabelFont == letteringLabelFont) { return; } 182 | _letteringLabelFont = letteringLabelFont; 183 | self.letteringLabel.font = letteringLabelFont; 184 | } 185 | 186 | @end 187 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeCircleView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeCircleView.h 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 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | /** 28 | A view containing two circle image views that can animate 29 | between filled and hollow, whilst maintaining compatibility 30 | with translucency views. 31 | */ 32 | @interface TOPasscodeCircleView : UIView 33 | 34 | /* The circle patterns used for neutral and highlighted states. */ 35 | @property (nonatomic, strong) UIImage *circleImage; 36 | @property (nonatomic, strong) UIImage *highlightedCircleImage; 37 | 38 | /* Whether the highlighted view is visible. */ 39 | @property (nonatomic, assign) BOOL isHighlighted; 40 | 41 | /* Animate the circle to be highlighted */ 42 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated; 43 | 44 | @end 45 | 46 | NS_ASSUME_NONNULL_END 47 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeCircleView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeCircleView.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 | 23 | #import "TOPasscodeCircleView.h" 24 | 25 | @interface TOPasscodeCircleView () 26 | @property (nonatomic, strong) UIImageView *bottomView; 27 | @property (nonatomic, strong) UIImageView *topView; 28 | @end 29 | 30 | @implementation TOPasscodeCircleView 31 | 32 | - (instancetype)initWithFrame:(CGRect)frame 33 | { 34 | if (self = [super initWithFrame:frame]) { 35 | self.userInteractionEnabled = NO; 36 | 37 | self.bottomView = [[UIImageView alloc] initWithFrame:self.bounds]; 38 | self.bottomView.userInteractionEnabled = NO; 39 | self.bottomView.contentMode = UIViewContentModeCenter; 40 | self.bottomView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 41 | [self addSubview:self.bottomView]; 42 | 43 | self.topView = [[UIImageView alloc] initWithFrame:self.bounds]; 44 | self.topView.userInteractionEnabled = NO; 45 | self.topView.contentMode = UIViewContentModeCenter; 46 | self.topView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 47 | self.topView.alpha = 0.0f; 48 | [self addSubview:self.topView]; 49 | } 50 | 51 | return self; 52 | } 53 | 54 | - (void)setIsHighlighted:(BOOL)isHighlighted 55 | { 56 | [self setHighlighted:isHighlighted animated:NO]; 57 | } 58 | 59 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated 60 | { 61 | if (highlighted == self.isHighlighted) { return; } 62 | 63 | _isHighlighted = highlighted; 64 | 65 | void (^animationBlock)(void) = ^{ 66 | self.topView.alpha = highlighted ? 1.0f : 0.0f; 67 | }; 68 | 69 | if (!animated) { 70 | animationBlock(); 71 | return; 72 | } 73 | 74 | [UIView animateWithDuration:0.45f animations:animationBlock]; 75 | } 76 | 77 | - (void)setCircleImage:(UIImage *)circleImage 78 | { 79 | _circleImage = circleImage; 80 | self.bottomView.image = circleImage; 81 | } 82 | 83 | - (void)setHighlightedCircleImage:(UIImage *)highlightedCircleImage 84 | { 85 | _highlightedCircleImage = highlightedCircleImage; 86 | self.topView.image = highlightedCircleImage; 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeFixedInputView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeFixedInputView.h 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 | 23 | #import 24 | 25 | @class TOPasscodeCircleView; 26 | 27 | /** 28 | A basic content view showing a row of circles that can be used to represent 29 | a fixed size passcode. 30 | */ 31 | @interface TOPasscodeFixedInputView : UIView 32 | 33 | /* The size of each circle in this view (Default is 16) */ 34 | @property (nonatomic, assign) CGFloat circleDiameter; 35 | 36 | /* The spacing between each circle (Default is 25.0f) */ 37 | @property (nonatomic, assign) CGFloat circleSpacing; 38 | 39 | /* The number of circles in this view (Default is 4) */ 40 | @property (nonatomic, assign) NSInteger length; 41 | 42 | /* The number of highlighted circles */ 43 | @property (nonatomic, assign) NSInteger highlightedLength; 44 | 45 | /* The circle views managed by this view */ 46 | @property (nonatomic, strong, readonly) NSArray *circleViews; 47 | 48 | /* Init with a set number of circles */ 49 | - (instancetype)initWithLength:(NSInteger)length; 50 | 51 | /* Set the number of highlighted circles */ 52 | - (void)setHighlightedLength:(NSInteger)highlightedLength animated:(BOOL)animated; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeFixedInputView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeFixedInputView.h 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 | 23 | #import "TOPasscodeFixedInputView.h" 24 | #import "TOPasscodeCircleView.h" 25 | #import "TOPasscodeCircleImage.h" 26 | 27 | @interface TOPasscodeFixedInputView () 28 | 29 | @property (nonatomic, strong, readwrite) NSArray *circleViews; 30 | @property (nonatomic, strong) UIImage *circleImage; 31 | @property (nonatomic, strong) UIImage *highlightedCircleImage; 32 | 33 | @end 34 | 35 | @implementation TOPasscodeFixedInputView 36 | 37 | #pragma mark - Object Creation - 38 | 39 | - (instancetype)initWithLength:(NSInteger)length 40 | { 41 | if (self = [self initWithFrame:CGRectZero]) { 42 | _length = length; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (instancetype)initWithFrame:(CGRect)frame 49 | { 50 | if (self = [super initWithFrame:frame]) { 51 | _circleSpacing = 25.0f; 52 | _circleDiameter = 16.0f; 53 | _length = 4; 54 | } 55 | 56 | return self; 57 | } 58 | 59 | #pragma mark - View Configuration - 60 | 61 | - (void)sizeToFit 62 | { 63 | // Resize the view to encompass the circles 64 | CGRect frame = self.frame; 65 | frame.size.width = (_circleDiameter * _length) + (_circleSpacing * (_length - 1)) + 2.0f; 66 | frame.size.height = _circleDiameter + 2.0f; 67 | self.frame = CGRectIntegral(frame); 68 | } 69 | 70 | - (void)layoutSubviews 71 | { 72 | CGRect frame = CGRectZero; 73 | frame.size = (CGSize){self.circleDiameter + 2.0f, self.circleDiameter + 2.0f}; 74 | 75 | for (TOPasscodeCircleView *circleView in self.circleViews) { 76 | circleView.frame = frame; 77 | frame.origin.x += self.circleDiameter + self.circleSpacing; 78 | } 79 | } 80 | 81 | #pragma mark - State Configuration - 82 | 83 | - (void)setHighlightedLength:(NSInteger)highlightedLength animated:(BOOL)animated 84 | { 85 | NSInteger i = 0; 86 | for (TOPasscodeCircleView *circleView in self.circleViews) { 87 | [circleView setHighlighted:(i < highlightedLength) animated:animated]; 88 | i++; 89 | } 90 | } 91 | 92 | #pragma mark - Circle View Configuration - 93 | 94 | - (void)setCircleViewsForLength:(NSInteger)length 95 | { 96 | NSMutableArray *circleViews = [NSMutableArray array]; 97 | if (self.circleViews) { 98 | [circleViews addObjectsFromArray:self.circleViews]; 99 | } 100 | 101 | [UIView performWithoutAnimation:^{ 102 | while (circleViews.count != length) { 103 | // Remove any extra circle views 104 | if (circleViews.count > length) { 105 | TOPasscodeCircleView *lastCircle = circleViews.lastObject; 106 | [lastCircle removeFromSuperview]; 107 | [circleViews removeLastObject]; 108 | continue; 109 | } 110 | 111 | // Add any new circle views 112 | TOPasscodeCircleView *newCircleView = [[TOPasscodeCircleView alloc] init]; 113 | [self setImagesOfCircleView:newCircleView]; 114 | [self addSubview:newCircleView]; 115 | [circleViews addObject:newCircleView]; 116 | } 117 | 118 | self.circleViews = [NSArray arrayWithArray:circleViews]; 119 | [self setNeedsLayout]; 120 | [self layoutIfNeeded]; 121 | }]; 122 | } 123 | 124 | - (void)setCircleImagesForDiameter:(CGFloat)diameter 125 | { 126 | self.circleImage = [TOPasscodeCircleImage hollowCircleImageOfSize:diameter strokeWidth:1.2f padding:1.0f]; 127 | self.highlightedCircleImage = [TOPasscodeCircleImage circleImageOfSize:diameter inset:0.5f padding:1.0f antialias:YES]; 128 | 129 | for (TOPasscodeCircleView *circleView in self.circleViews) { 130 | [self setImagesOfCircleView:circleView]; 131 | } 132 | } 133 | 134 | - (void)setImagesOfCircleView:(TOPasscodeCircleView *)circleView 135 | { 136 | circleView.circleImage = self.circleImage; 137 | circleView.highlightedCircleImage = self.highlightedCircleImage; 138 | } 139 | 140 | #pragma mark - Accessors - 141 | 142 | - (NSArray *)circleViews 143 | { 144 | if (_circleViews) { return _circleViews; } 145 | _circleViews = [NSArray array]; 146 | [self setCircleViewsForLength:self.length]; 147 | [self setCircleImagesForDiameter:self.circleDiameter]; 148 | return _circleViews; 149 | } 150 | 151 | - (void)setCircleDiameter:(CGFloat)circleDiameter 152 | { 153 | if (circleDiameter == _circleDiameter) { return; } 154 | _circleDiameter = circleDiameter; 155 | [self setCircleImagesForDiameter:_circleDiameter]; 156 | [self sizeToFit]; 157 | } 158 | 159 | - (void)setLength:(NSInteger)length 160 | { 161 | if (_length == length) { return; } 162 | _length = length; 163 | [self setCircleViewsForLength:length]; 164 | } 165 | 166 | - (void)setHighlightedLength:(NSInteger)highlightedLength 167 | { 168 | [self setHighlightedLength:highlightedLength animated:NO]; 169 | } 170 | 171 | @end 172 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeInputField.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeInputField.h 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 | 23 | #import 24 | 25 | #import "TOPasscodeFixedInputView.h" 26 | #import "TOPasscodeVariableInputView.h" 27 | 28 | 29 | NS_ASSUME_NONNULL_BEGIN 30 | 31 | typedef NS_ENUM(NSInteger, TOPasscodeInputFieldStyle) { 32 | TOPasscodeInputFieldStyleFixed, // The passcode explicitly requires a specific number of characters (Shows hollow circles) 33 | TOPasscodeInputFieldStyleVariable // The passcode can be any arbitrary number of characters (Shows an empty rectangle) 34 | }; 35 | 36 | /** 37 | An abstract input view capable of receiving different types of passcodes. 38 | When a fixed character passcode is specified, the view shows a row of circles. 39 | When a variable passcode is specified, a rounded rectangle is shown. 40 | */ 41 | @interface TOPasscodeInputField : UIView 42 | 43 | /* The visual effects view used to control the vibrancy of the input field */ 44 | @property (nonatomic, strong, readonly) UIVisualEffectView *visualEffectView; 45 | 46 | /* The input style of this control */ 47 | @property (nonatomic, assign) TOPasscodeInputFieldStyle style; 48 | 49 | /* A row of hollow circles at a preset length. Valid only when `style` is set to `fixed` */ 50 | @property (nonatomic, readonly, nullable) TOPasscodeFixedInputView *fixedInputView; 51 | 52 | /* A rounded rectangle representing a passcode of arbitrary length. Valid only when `style` is set to `variable`. */ 53 | @property (nonatomic, readonly, nullable) TOPasscodeVariableInputView *variableInputView; 54 | 55 | /* The 'submit' button shown when `showSubmitButton` is true. */ 56 | @property (nonatomic, readonly, nullable) UIButton *submitButton; 57 | 58 | /* Shows an 'OK' button next to the view when characters have been added. */ 59 | @property (nonatomic, assign) BOOL showSubmitButton; 60 | 61 | /* The amount of spacing between the 'OK' button and the passcode field */ 62 | @property (nonatomic, assign) CGFloat submitButtonSpacing; 63 | 64 | /* The amount of spacing between the 'OK' button and the passcode field */ 65 | @property (nonatomic, assign) CGFloat submitButtonVerticalSpacing; 66 | 67 | /* The font size of the submit button */ 68 | @property (nonatomic, assign) CGFloat submitButtonFontSize; 69 | 70 | /* The current passcode entered into this view */ 71 | @property (nonatomic, copy, nullable) NSString *passcode; 72 | 73 | /* If this view is directly receiving input, this can change the `UIKeyboard` appearance. */ 74 | @property (nonatomic, assign) UIKeyboardAppearance keyboardAppearance; 75 | 76 | /* The type of button used for the 'Done' button in the keyboard */ 77 | @property(nonatomic, assign) UIReturnKeyType returnKeyType; 78 | 79 | /* The alpha value of the views in this view (For tranclucent styling) */ 80 | @property (nonatomic, assign) CGFloat contentAlpha; 81 | 82 | /* Whether the view may be tapped to enable character input (Default is NO) */ 83 | @property (nonatomic, assign) BOOL enabled; 84 | 85 | /** Called when the number of digits has been entered, or the user tapped 'Done' on the keyboard */ 86 | @property (nonatomic, copy) void (^passcodeCompletedHandler)(NSString *code); 87 | 88 | /** Horizontal layout. The 'OK' button will be placed under the text field */ 89 | @property (nonatomic, assign) BOOL horizontalLayout; 90 | 91 | /* Init with the target length needed for this passcode */ 92 | - (instancetype)initWithStyle:(TOPasscodeInputFieldStyle)style; 93 | 94 | /* Replace the passcode with this one, and animate the transition. */ 95 | - (void)setPasscode:(nullable NSString *)passcode animated:(BOOL)animated; 96 | 97 | /* Add additional characters to the end of the passcode, and animate if desired. */ 98 | - (void)appendPasscodeCharacters:(NSString *)characters animated:(BOOL)animated; 99 | 100 | /* Delete a number of characters from the end, animated if desired. */ 101 | - (void)deletePasscodeCharactersOfCount:(NSInteger)deleteCount animated:(BOOL)animated; 102 | 103 | /* Plays a shaking animation and resets the passcode back to empty */ 104 | - (void)resetPasscodeAnimated:(BOOL)animated playImpact:(BOOL)impact; 105 | 106 | /* Animates the OK button changing location. */ 107 | - (void)setHorizontalLayout:(BOOL)horizontalLayout animated:(BOOL)animated duration:(CGFloat)duration; 108 | 109 | @end 110 | 111 | NS_ASSUME_NONNULL_END 112 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeVariableInputView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeVariableInputView.h 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 | 23 | #import 24 | 25 | /** 26 | A basic content view showing a rounded rectangle containing circles 27 | that can be used to represent a variable size passcode. 28 | */ 29 | @interface TOPasscodeVariableInputView : UIImageView 30 | 31 | /* The thickness of the stroke around the view (Default is 1.5) */ 32 | @property (nonatomic, assign) CGFloat outlineThickness; 33 | 34 | /* The corner radius of the stroke (Default is 5) */ 35 | @property (nonatomic, assign) CGFloat outlineCornerRadius; 36 | 37 | /* The size of each circle bullet point representing a passcoded character (Default is 10) */ 38 | @property (nonatomic, assign) CGFloat circleDiameter; 39 | 40 | /* The spacing between each circle (Default is 15) */ 41 | @property (nonatomic, assign) CGFloat circleSpacing; 42 | 43 | /* The padding between the circles and the outer outline (Default is {10,10}) */ 44 | @property (nonatomic, assign) CGSize outlinePadding; 45 | 46 | /* The maximum number of circles to show (This will indicate the view's width) (Default is 12) */ 47 | @property (nonatomic, assign) NSInteger maximumVisibleLength; 48 | 49 | /* Set the number of characters entered into this view (May be larger than `maximumVisibleLength`) */ 50 | @property (nonatomic, assign) NSInteger length; 51 | 52 | /* Set the number of characters represented by this field, animated if desired */ 53 | - (void)setLength:(NSInteger)length animated:(BOOL)animated; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /TOPasscodeViewController/Views/Shared/TOPasscodeVariableInputView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPasscodeVariableInputView.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 | 23 | #import "TOPasscodeVariableInputView.h" 24 | #import "TOPasscodeCircleImage.h" 25 | 26 | @interface TOPasscodeVariableInputView () 27 | 28 | @property (nonatomic, strong) UIImage *backgroundImage; // The outline image for this view 29 | @property (nonatomic, strong) UIImage *circleImage; // The circle image representing a single character 30 | 31 | @property (nonatomic, strong) NSMutableArray *circleViews; 32 | 33 | @end 34 | 35 | @implementation TOPasscodeVariableInputView 36 | 37 | #pragma mark - Class Creation - 38 | 39 | - (instancetype)initWithFrame:(CGRect)frame 40 | { 41 | if (self = [super initWithFrame:frame]) { 42 | _outlineThickness = 1.0f; 43 | _outlineCornerRadius = 5.0f; 44 | _circleDiameter = 11.0f; 45 | _circleSpacing = 7.0f; 46 | _outlinePadding = (CGSize){10,10}; 47 | _maximumVisibleLength = 12; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | #pragma mark - View Setup - 54 | - (void)setUpImageForCircleViews 55 | { 56 | if (self.circleImage != nil) { return; } 57 | 58 | self.circleImage = [TOPasscodeCircleImage circleImageOfSize:_circleDiameter inset:0.0f padding:1.0f antialias:YES]; 59 | for (UIImageView *circleView in self.circleViews) { 60 | circleView.image = self.circleImage; 61 | [circleView sizeToFit]; 62 | } 63 | } 64 | 65 | - (void)setUpCircleViewsForLength:(NSInteger)length 66 | { 67 | // Set up the number of circle views if needed 68 | if (self.circleViews.count == length) { return; } 69 | 70 | if (self.circleViews == nil) { 71 | self.circleViews = [NSMutableArray arrayWithCapacity:_maximumVisibleLength]; 72 | } 73 | 74 | // Reduce the number of views 75 | while (self.circleViews.count > length) { 76 | UIImageView *circleView = self.circleViews.lastObject; 77 | [circleView removeFromSuperview]; 78 | [self.circleViews removeLastObject]; 79 | } 80 | 81 | // Increase the number of views 82 | [UIView performWithoutAnimation:^{ 83 | while (self.circleViews.count < length) { 84 | UIImageView *circleView = [[UIImageView alloc] initWithImage:self.circleImage]; 85 | circleView.alpha = 0.0f; 86 | [self addSubview:circleView]; 87 | [self.circleViews addObject:circleView]; 88 | } 89 | }]; 90 | } 91 | 92 | - (void)setUpBackgroundImage 93 | { 94 | if (self.backgroundImage != nil) { return; } 95 | 96 | self.backgroundImage = [[self class] backgroundImageWithThickness:_outlineThickness cornerRadius:_outlineCornerRadius]; 97 | self.image = self.backgroundImage; 98 | } 99 | 100 | #pragma mark - View Layout - 101 | 102 | - (void)sizeToFit 103 | { 104 | CGRect frame = self.frame; 105 | 106 | // Calculate the width 107 | frame.size.width = self.outlineThickness * 2.0f; 108 | frame.size.width += (self.outlinePadding.width * 2.0f); 109 | frame.size.width += (self.maximumVisibleLength * (self.circleDiameter+2.0f)); // +2 for padding 110 | frame.size.width += ((self.maximumVisibleLength - 1) * self.circleSpacing); 111 | 112 | // Height 113 | frame.size.height = self.outlineThickness * 2.0f; 114 | frame.size.height += self.outlinePadding.height * 2.0f; 115 | frame.size.height += self.circleDiameter; 116 | 117 | self.frame = CGRectIntegral(frame); 118 | } 119 | 120 | - (void)layoutSubviews 121 | { 122 | [super layoutSubviews]; 123 | 124 | // Genearate the background image if we don't have one yet 125 | [self setUpBackgroundImage]; 126 | 127 | // Set up the circle view image 128 | [self setUpImageForCircleViews]; 129 | 130 | // Set up the circle views 131 | [self setUpCircleViewsForLength:self.maximumVisibleLength]; 132 | 133 | // Layout the circle views for the current length 134 | CGRect frame = CGRectZero; 135 | frame.size = self.circleImage.size; 136 | frame.origin.y = CGRectGetMidY(self.bounds) - (frame.size.height * 0.5f); 137 | frame.origin.x = self.outlinePadding.width + self.outlineThickness; 138 | 139 | for (UIImageView *circleView in self.circleViews) { 140 | circleView.frame = frame; 141 | frame.origin.x += frame.size.width + self.circleSpacing; 142 | } 143 | } 144 | 145 | #pragma mark - Accessors - 146 | 147 | - (void)setOutlineThickness:(CGFloat)outlineThickness 148 | { 149 | if (_outlineThickness == outlineThickness) { return; } 150 | _outlineThickness = outlineThickness; 151 | self.backgroundImage = nil; 152 | [self setNeedsLayout]; 153 | } 154 | 155 | - (void)setOutlineCornerRadius:(CGFloat)outlineCornerRadius 156 | { 157 | if (_outlineCornerRadius == outlineCornerRadius) { return; } 158 | _outlineCornerRadius = outlineCornerRadius; 159 | self.backgroundImage = nil; 160 | [self setNeedsLayout]; 161 | } 162 | 163 | - (void)setCircleDiameter:(CGFloat)circleDiameter 164 | { 165 | if (_circleDiameter == circleDiameter) { return; } 166 | _circleDiameter = circleDiameter; 167 | self.circleImage = nil; 168 | [self setUpImageForCircleViews]; 169 | } 170 | 171 | - (void)setCircleSpacing:(CGFloat)circleSpacing 172 | { 173 | if (_circleSpacing == circleSpacing) { return; } 174 | _circleSpacing = circleSpacing; 175 | [self sizeToFit]; 176 | [self setNeedsLayout]; 177 | } 178 | 179 | - (void)setOutlinePadding:(CGSize)outlinePadding 180 | { 181 | if (CGSizeEqualToSize(outlinePadding, _outlinePadding)) { return; } 182 | _outlinePadding = outlinePadding; 183 | [self sizeToFit]; 184 | [self setNeedsLayout]; 185 | } 186 | 187 | - (void)setMaximumVisibleLength:(NSInteger)maximumVisibleLength 188 | { 189 | if (_maximumVisibleLength == maximumVisibleLength) { return; } 190 | _maximumVisibleLength = maximumVisibleLength; 191 | [self setUpCircleViewsForLength:maximumVisibleLength]; 192 | [self sizeToFit]; 193 | [self setNeedsLayout]; 194 | } 195 | 196 | - (void)setLength:(NSInteger)length 197 | { 198 | [self setLength:length animated:NO]; 199 | } 200 | 201 | - (void)setLength:(NSInteger)length animated:(BOOL)animated 202 | { 203 | if (length == _length) { return; } 204 | 205 | _length = length; 206 | 207 | void (^animationBlock)(void) = ^{ 208 | NSInteger i = 0; 209 | for (UIImageView *circleView in self.circleViews) { 210 | circleView.alpha = i < length ? 1.0f : 0.0f; 211 | i++; 212 | } 213 | }; 214 | 215 | if (!animated) { 216 | animationBlock(); 217 | return; 218 | } 219 | 220 | [UIView animateWithDuration:0.4f animations:animationBlock]; 221 | } 222 | 223 | #pragma mark - Image Creation - 224 | 225 | + (UIImage *)backgroundImageWithThickness:(CGFloat)thickness cornerRadius:(CGFloat)radius 226 | { 227 | CGFloat inset = thickness / 2.0f; 228 | CGFloat dimension = (radius * 2.0f) + 2.0f; 229 | 230 | CGRect frame = CGRectZero; 231 | frame.origin = CGPointMake(inset, inset); 232 | frame.size = CGSizeMake(dimension, dimension); 233 | 234 | CGSize canvasSize = frame.size; 235 | canvasSize.width += thickness; 236 | canvasSize.height += thickness; 237 | 238 | UIGraphicsBeginImageContextWithOptions(canvasSize, NO, 0.0f); 239 | { 240 | UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:radius]; 241 | path.lineWidth = thickness; 242 | [[UIColor blackColor] setStroke]; 243 | [path stroke]; 244 | } 245 | 246 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 247 | UIGraphicsEndImageContext(); 248 | 249 | UIEdgeInsets insets = UIEdgeInsetsMake(radius+1, radius+1, radius+1, radius+1); 250 | image = [image resizableImageWithCapInsets:insets]; 251 | return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 252 | } 253 | 254 | @end 255 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOPasscodeViewController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOPasscodeViewControllerExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOPasscodeViewControllerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TOPINViewControllerExample 4 | // 5 | // Created by Tim Oliver on 5/15/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | @property (strong, nonatomic) UIWindow *window; 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TOPINViewControllerExample 4 | // 5 | // Created by Tim Oliver on 5/15/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 14 | return YES; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Assets.xcassets/Settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Settings.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Settings@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Settings@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Assets.xcassets/Settings.imageset/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/190f687f58b8bbe0c3c7ad914875565511b8f4bf/TOPasscodeViewControllerExample/Assets.xcassets/Settings.imageset/Settings.png -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Assets.xcassets/Settings.imageset/Settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/190f687f58b8bbe0c3c7ad914875565511b8f4bf/TOPasscodeViewControllerExample/Assets.xcassets/Settings.imageset/Settings@2x.png -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Assets.xcassets/Settings.imageset/Settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/190f687f58b8bbe0c3c7ad914875565511b8f4bf/TOPasscodeViewControllerExample/Assets.xcassets/Settings.imageset/Settings@3x.png -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 45 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSPhotoLibraryUsageDescription 24 | This app lets you choose images as test wallpapers against the passcode interface. 25 | NSSupportsSuddenTermination 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | NSFaceIDUsageDescription 49 | Use Face ID to unlock this demo app of TOPasscodeViewController 50 | 51 | 52 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/SettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.h 3 | // TOPasscodeViewControllerExample 4 | // 5 | // Created by Tim Oliver on 6/4/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TOPasscodeViewControllerConstants.h" 11 | 12 | @interface SettingsViewController : UITableViewController 13 | 14 | @property (nonatomic, copy) NSString *passcode; 15 | @property (nonatomic, assign) TOPasscodeType passcodeType; 16 | @property (nonatomic, assign) TOPasscodeViewStyle style; 17 | @property (nonatomic, assign) BOOL showButtonLettering; 18 | @property (nonatomic, strong) UIImage *wallpaperImage; 19 | 20 | @property (nonatomic, copy) void (^doneButtonTappedHandler)(void); 21 | @property (nonatomic, copy) void (^wallpaperChangedHandler)(UIImage *wallpaper); 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/SettingsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.m 3 | // TOPasscodeViewControllerExample 4 | // 5 | // Created by Tim Oliver on 6/4/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import "SettingsViewController.h" 10 | #import "TOPasscodeSettingsViewController.h" 11 | 12 | @interface SettingsViewController () 15 | 16 | @property (nonatomic, strong) UIImageView *imageView; 17 | 18 | @end 19 | 20 | @implementation SettingsViewController 21 | 22 | - (instancetype)init 23 | { 24 | if (self = [super initWithStyle:UITableViewStyleGrouped]) { 25 | 26 | } 27 | 28 | return self; 29 | } 30 | 31 | - (void)viewDidLoad { 32 | [super viewDidLoad]; 33 | self.title = @"Settings"; 34 | 35 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneButtonTapped)]; 36 | 37 | self.imageView = [[UIImageView alloc] initWithImage:self.wallpaperImage]; 38 | self.imageView.contentMode = UIViewContentModeScaleAspectFit; 39 | self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 40 | } 41 | 42 | - (void)didReceiveMemoryWarning { 43 | [super didReceiveMemoryWarning]; 44 | // Dispose of any resources that can be recreated. 45 | } 46 | 47 | - (void)doneButtonTapped 48 | { 49 | if (self.doneButtonTappedHandler) { 50 | self.doneButtonTappedHandler(); 51 | } 52 | } 53 | 54 | #pragma mark - Settings Controller Delegate - 55 | 56 | - (BOOL)passcodeSettingsViewController:(TOPasscodeSettingsViewController *)passcodeSettingsViewController didAttemptCurrentPasscode:(NSString *)passcode 57 | { 58 | return [passcode isEqualToString:self.passcode]; 59 | } 60 | 61 | - (void)passcodeSettingsViewController:(TOPasscodeSettingsViewController *)passcodeSettingsViewController didChangeToNewPasscode:(NSString *)passcode ofType:(TOPasscodeType)type 62 | { 63 | self.passcode = passcode; 64 | self.passcodeType = type; 65 | [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone]; 66 | [self.navigationController popViewControllerAnimated:YES]; 67 | } 68 | 69 | #pragma mark - Table view data source 70 | 71 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 72 | { 73 | switch (section) { 74 | case 0: return nil; 75 | case 1: return @"Passcode Display Style"; 76 | case 2: return @"Choose Wallpaper"; 77 | case 3: return @"Passcode Keypad Button Style"; 78 | default: break; 79 | } 80 | 81 | return nil; 82 | } 83 | 84 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 85 | return 4; 86 | } 87 | 88 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 89 | if (section == 1) { 90 | return 4; 91 | } else if (section == 3) { 92 | return 1; 93 | } else { 94 | return 1; 95 | } 96 | } 97 | 98 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 99 | { 100 | if (indexPath.section != 2) { return 44.0f; } 101 | 102 | return 280.0f; 103 | } 104 | 105 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 106 | static NSString *ident = @"Cell"; 107 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ident]; 108 | if (cell == nil) { 109 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:ident]; 110 | } 111 | 112 | if (indexPath.section == 0) { 113 | cell.textLabel.text = @"Passcode"; 114 | cell.detailTextLabel.text = self.passcode; 115 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 116 | } 117 | else if (indexPath.section == 1) { 118 | NSString *cellText = nil; 119 | 120 | switch (indexPath.row) { 121 | case 0: cellText = @"Dark Translucent"; break; 122 | case 1: cellText = @"Light Translucent"; break; 123 | case 2: cellText = @"Dark Opaque"; break; 124 | case 3: cellText = @"Light Opaque"; break; 125 | default: 126 | break; 127 | } 128 | 129 | if (indexPath.row == self.style) { 130 | cell.accessoryType = UITableViewCellAccessoryCheckmark; 131 | } 132 | else { 133 | cell.accessoryType = UITableViewCellAccessoryNone; 134 | } 135 | 136 | cell.textLabel.text = cellText; 137 | } 138 | else if (indexPath.section == 3) { 139 | NSString *cellText = nil; 140 | 141 | switch (indexPath.row) { 142 | case 0: 143 | cellText = @"Show Lettering"; 144 | cell.accessoryType = self.showButtonLettering ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; 145 | break; 146 | default: 147 | break; 148 | } 149 | 150 | cell.detailTextLabel.text = nil; 151 | cell.textLabel.text = cellText; 152 | } 153 | else { 154 | cell.textLabel.text = nil; 155 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 156 | self.imageView.frame = CGRectInset(cell.bounds, 20, 20); 157 | [cell addSubview:self.imageView]; 158 | } 159 | 160 | return cell; 161 | } 162 | 163 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 164 | { 165 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 166 | 167 | if (indexPath.section == 1) { 168 | NSIndexPath *lastIndex = [NSIndexPath indexPathForRow:self.style inSection:1]; 169 | self.style = indexPath.row; 170 | [tableView reloadRowsAtIndexPaths:@[lastIndex, indexPath] withRowAnimation:UITableViewRowAnimationNone]; 171 | } 172 | else if (indexPath.section == 0) { 173 | TOPasscodeSettingsViewController *settingsController = [[TOPasscodeSettingsViewController alloc] init]; 174 | settingsController.passcodeType = self.passcodeType; 175 | settingsController.delegate = self; 176 | settingsController.requireCurrentPasscode = YES; 177 | [self.navigationController pushViewController:settingsController animated:YES]; 178 | } 179 | else if (indexPath.section == 2) { 180 | __weak typeof(self) weakSelf = self; 181 | UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; 182 | UIImage *pasteboardImage = pasteboard.image; 183 | 184 | void (^photoAction)(UIAlertAction *) = ^(UIAlertAction *action) { 185 | UIImagePickerController *pickerController = [[UIImagePickerController alloc] init]; 186 | pickerController.delegate = self; 187 | pickerController.modalPresentationStyle = UIModalPresentationFormSheet; 188 | [weakSelf presentViewController:pickerController animated:YES completion:nil]; 189 | }; 190 | 191 | if (!pasteboardImage) { 192 | photoAction(nil); 193 | return; 194 | } 195 | 196 | void (^clipboardAction)(UIAlertAction *) = ^(UIAlertAction *action) { 197 | [weakSelf setNewWallpaper:pasteboardImage]; 198 | }; 199 | 200 | UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Choose Image Source" 201 | message:nil 202 | preferredStyle:UIAlertControllerStyleActionSheet]; 203 | [controller addAction:[UIAlertAction actionWithTitle:@"Paste Image" style:UIAlertActionStyleDefault handler:clipboardAction]]; 204 | [controller addAction:[UIAlertAction actionWithTitle:@"Choose from Library" style:UIAlertActionStyleDefault handler:photoAction]]; 205 | [self presentViewController:controller animated:YES completion:nil]; 206 | } 207 | else if (indexPath.section == 3) { 208 | NSIndexPath *lastIndex = [NSIndexPath indexPathForRow:self.style inSection:1]; 209 | UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; 210 | 211 | switch (indexPath.row) { 212 | case 0: 213 | self.showButtonLettering = !self.showButtonLettering; 214 | cell.accessoryType = self.showButtonLettering ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; 215 | break; 216 | default: 217 | break; 218 | } 219 | 220 | [tableView reloadRowsAtIndexPaths:@[lastIndex, indexPath] withRowAnimation:UITableViewRowAnimationNone]; 221 | } 222 | } 223 | 224 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 225 | { 226 | [self setNewWallpaper:info[UIImagePickerControllerOriginalImage]]; 227 | [self dismissViewControllerAnimated:YES completion:nil]; 228 | } 229 | 230 | - (void)setNewWallpaper:(UIImage *)wallpaper 231 | { 232 | if (wallpaper == nil) { return; } 233 | 234 | self.wallpaperImage = wallpaper; 235 | self.imageView.image = wallpaper; 236 | 237 | if (self.wallpaperChangedHandler) { 238 | self.wallpaperChangedHandler(wallpaper); 239 | } 240 | } 241 | 242 | @end 243 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/TOBlurView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOBlurView.h 3 | // TOPasscodeViewControllerExample 4 | // 5 | // Created by Tim Oliver on 6/3/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TOBlurView : UIVisualEffectView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/TOBlurView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOBlurView.m 3 | // TOPasscodeViewControllerExample 4 | // 5 | // Created by Tim Oliver on 6/3/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import "TOBlurView.h" 10 | 11 | @implementation TOBlurView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame 14 | { 15 | if (self = [super initWithFrame:frame]) { 16 | self.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 17 | } 18 | 19 | return self; 20 | } 21 | 22 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 23 | { 24 | if (self = [super initWithCoder:aDecoder]) { 25 | self.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 26 | } 27 | 28 | return self; 29 | } 30 | 31 | - (instancetype)initWithEffect:(UIVisualEffect *)effect 32 | { 33 | if (self = [super initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]) { 34 | 35 | } 36 | 37 | return self; 38 | } 39 | 40 | - (void)addSubview:(UIView *)view 41 | { 42 | if ([NSStringFromClass([view class]) rangeOfString:@"Backdrop"].location == NSNotFound) { 43 | return; 44 | } 45 | 46 | [super addSubview:view]; 47 | } 48 | 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TOPINViewControllerExample 4 | // 5 | // Created by Tim Oliver on 5/15/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TOPINViewControllerExample 4 | // 5 | // Created by Tim Oliver on 5/15/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "TOPasscodeViewController.h" 11 | #import "SettingsViewController.h" 12 | #import 13 | 14 | @interface ViewController () 15 | 16 | @property (nonatomic, copy) NSString *passcode; 17 | @property (nonatomic, assign) BOOL showButtonLettering; 18 | @property (nonatomic, assign) TOPasscodeViewStyle style; 19 | @property (nonatomic, assign) TOPasscodeType type; 20 | 21 | @property (nonatomic, weak) IBOutlet UIImageView *imageView; 22 | @property (nonatomic, weak) IBOutlet UIView *dimmingView; 23 | 24 | @property (nonatomic, strong) LAContext *authContext; 25 | @property (nonatomic, assign) BOOL biometricsAvailable; 26 | @property (nonatomic, assign) BOOL faceIDAvailable; 27 | 28 | @end 29 | 30 | @implementation ViewController 31 | 32 | - (void)viewDidLoad { 33 | [super viewDidLoad]; 34 | 35 | self.passcode = @"1234"; 36 | self.showButtonLettering = YES; 37 | 38 | // Enable mipmaps so the rescaled image will look properly sampled 39 | self.imageView.layer.minificationFilter = kCAFilterTrilinear; 40 | 41 | // Show 'Touch ID' button if it's available 42 | self.authContext = [[LAContext alloc] init]; 43 | self.biometricsAvailable = [self.authContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil]; 44 | 45 | if (@available(iOS 11.0, *)) { 46 | self.faceIDAvailable = (self.authContext.biometryType == LABiometryTypeFaceID); 47 | } 48 | } 49 | 50 | - (IBAction)showButtonTapped:(id)sender 51 | { 52 | TOPasscodeViewController *passcodeViewController = [[TOPasscodeViewController alloc] initWithStyle:self.style passcodeType:self.type]; 53 | passcodeViewController.delegate = self; 54 | passcodeViewController.allowBiometricValidation = self.biometricsAvailable; 55 | passcodeViewController.biometryType = self.faceIDAvailable ? TOPasscodeBiometryTypeFaceID : TOPasscodeBiometryTypeTouchID; 56 | passcodeViewController.keypadButtonShowLettering = self.showButtonLettering; 57 | [self presentViewController:passcodeViewController animated:YES completion:nil]; 58 | } 59 | 60 | - (IBAction)settingsButtonTapped:(id)sender 61 | { 62 | SettingsViewController *controller = [[SettingsViewController alloc] init]; 63 | controller.passcode = self.passcode; 64 | controller.passcodeType = self.type; 65 | controller.style = self.style; 66 | controller.wallpaperImage = self.imageView.image; 67 | controller.showButtonLettering = self.showButtonLettering; 68 | 69 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; 70 | navController.modalPresentationStyle = UIModalPresentationFormSheet; 71 | [self presentViewController:navController animated:YES completion:nil]; 72 | 73 | __weak typeof(self) weakSelf = self; 74 | __weak typeof(controller) weakController = controller; 75 | controller.doneButtonTappedHandler = ^{ 76 | weakSelf.passcode = weakController.passcode; 77 | weakSelf.style = weakController.style; 78 | weakSelf.type = weakController.passcodeType; 79 | weakSelf.showButtonLettering = weakController.showButtonLettering; 80 | 81 | [weakSelf dismissViewControllerAnimated:YES completion:nil]; 82 | }; 83 | 84 | controller.wallpaperChangedHandler = ^(UIImage *image) { 85 | weakSelf.imageView.image = image; 86 | }; 87 | } 88 | 89 | - (void)didTapCancelInPasscodeViewController:(TOPasscodeViewController *)passcodeViewController 90 | { 91 | [self dismissViewControllerAnimated:YES completion:nil]; 92 | } 93 | 94 | - (BOOL)passcodeViewController:(TOPasscodeViewController *)passcodeViewController isCorrectCode:(NSString *)code 95 | { 96 | return [code isEqualToString:self.passcode]; 97 | } 98 | 99 | - (void)didPerformBiometricValidationRequestInPasscodeViewController:(TOPasscodeViewController *)passcodeViewController 100 | { 101 | __weak typeof(self) weakSelf = self; 102 | NSString *reason = @"Touch ID to continue using this app"; 103 | id reply = ^(BOOL success, NSError *error) { 104 | 105 | // Touch ID validation was successful 106 | // (Use this to dismiss the passcode controller and display the protected content) 107 | if (success) { 108 | dispatch_async(dispatch_get_main_queue(), ^{ 109 | // Create a new Touch ID context for next time 110 | [weakSelf.authContext invalidate]; 111 | weakSelf.authContext = [[LAContext alloc] init]; 112 | 113 | // Dismiss the passcode controller 114 | [weakSelf dismissViewControllerAnimated:YES completion:nil]; 115 | }); 116 | return; 117 | } 118 | 119 | // Actual UI changes need to be made on the main queue 120 | dispatch_async(dispatch_get_main_queue(), ^{ 121 | [passcodeViewController setContentHidden:NO animated:YES]; 122 | }); 123 | 124 | // The user hit 'Enter Password'. This should probably do nothing 125 | // but make sure the passcode controller is visible. 126 | if (error.code == kLAErrorUserFallback) { 127 | NSLog(@"User tapped 'Enter Password'"); 128 | return; 129 | } 130 | 131 | // The user hit the 'Cancel' button in the Touch ID dialog. 132 | // At this point, you could either simply return the user to the passcode controller, 133 | // or dismiss the protected content and go back to a safer point in your app (Like the login page). 134 | if (error.code == LAErrorUserCancel) { 135 | NSLog(@"User tapped cancel."); 136 | return; 137 | } 138 | 139 | // There shouldn't be any other potential errors, but just in case 140 | NSLog(@"%@", error.localizedDescription); 141 | }; 142 | 143 | [passcodeViewController setContentHidden:YES animated:YES]; 144 | 145 | [self.authContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:reason reply:reply]; 146 | } 147 | 148 | - (UIStatusBarStyle)preferredStatusBarStyle 149 | { 150 | if (self.presentedViewController) { 151 | return [self.presentedViewController preferredStatusBarStyle]; 152 | } 153 | 154 | return UIStatusBarStyleLightContent; 155 | } 156 | @end 157 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TOPINViewControllerExample 4 | // 5 | // Created by Tim Oliver on 5/15/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExample/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/190f687f58b8bbe0c3c7ad914875565511b8f4bf/TOPasscodeViewControllerExample/wallpaper.jpg -------------------------------------------------------------------------------- /TOPasscodeViewControllerExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerExampleTests/TOPasscodeViewControllerExampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOPINViewControllerExampleTests.m 3 | // TOPINViewControllerExampleTests 4 | // 5 | // Created by Tim Oliver on 5/15/17. 6 | // Copyright © 2017 Timothy Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TOPasscodeViewController.h" 11 | 12 | @interface TOPasscodeViewControllerExampleTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation TOPasscodeViewControllerExampleTests 17 | 18 | - (void)setUp { 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 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testPresentingViewController 29 | { 30 | UIViewController *parentViewController = [[UIViewController alloc] init]; 31 | TOPasscodeViewController *controller = [[TOPasscodeViewController alloc] initWithStyle:TOPasscodeViewStyleTranslucentDark passcodeType:TOPasscodeTypeFourDigits]; 32 | [parentViewController presentViewController:controller animated:NO completion:nil]; 33 | XCTAssertNotNil(controller); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /TOPasscodeViewControllerFramework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /breakdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/190f687f58b8bbe0c3c7ad914875565511b8f4bf/breakdown.jpg -------------------------------------------------------------------------------- /buildkite/pipeline.release.yml: -------------------------------------------------------------------------------- 1 | env: 2 | LC_ALL: "en_US.UTF-8" 3 | REPO_PATH: "TimOliver/TOPasscodeViewController" 4 | PODSPEC_PATH: "TOPasscodeViewController.podspec" 5 | FRAMEWORK_PLIST_PATH: "TOPasscodeViewControllerFramework/Info.plist" 6 | BUILDKITE_CLEAN_CHECKOUT: true 7 | 8 | steps: 9 | - label: ':fastlane: Cut New Release' 10 | command: '(curl -s -L http://tim.dev/install_lib | bash -s arg1 arg2) && bundle exec fastlane release' 11 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/190f687f58b8bbe0c3c7ad914875565511b8f4bf/screenshot.jpg -------------------------------------------------------------------------------- /video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/190f687f58b8bbe0c3c7ad914875565511b8f4bf/video.png --------------------------------------------------------------------------------