├── .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 | [](https://github.com/TimOliver/TOPasscodeViewController/actions?query=workflow%3ACI)
9 | [](http://cocoadocs.org/docsets/TOPasscodeViewController)
10 | [](https://raw.githubusercontent.com/TimOliver/TOPasscodeViewController/master/LICENSE)
11 | [](http://cocoadocs.org/docsets/TOPasscodeViewController)
12 | [](https://beerpay.io/TimOliver/TOPasscodeViewController)
13 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M4RKULAVKV7K8)
14 | [](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. 
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 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
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
--------------------------------------------------------------------------------