#SwiftPasscodeLock An iOS passcode lock with Touch ID authentication written in Swift.
At its core the SwiftPasscodeLock represents a form of the State Design Pattern. It also uses autolayout, is localization ready and works on both iPhone and iPad. The best part is that it's highly customizable. Go ahead and take a look at the UML class diagram on Figure 1:
Figure 1: SwiftPasscodeLock class diagram
##Installation
-
Copy the core PasscodeLock components from the
SwiftPasscodeLock/PasscodeLockfolder to your project:PasscodeLock.swiftPasscodeLockPresenter.swiftGenericPasscodeLock.swiftChangePasscodeState.swift.swiftConfirmPasscodeState.swiftEnterPasscodeState.swiftPasscodesMismatchState.swiftSetPasscodeState.swift
-
Copy the
en.lprojfolder withPasscodeLock.stringslocalization file to your project; -
If you want to use the demo app
PasscodeViewControllercopy the following files to your project:ViewControllers/PasscodeViewController.swiftViews/PasscodeButton.swiftViews/PasscodePlaceholderView.swiftViews/LockSplashView.swiftViews/PasscodeView.xib- The
LockSplashViewwill look for a resource image calledlockthat you can find in the demo app image assets; - Add the
LocalAuthentication.frameworkto your project target as it's required for the Touch ID authentication.
The demo app uses the Keychain API to store the app passcode. The included concrete implementation of the PasscodeRepository protocol the PasscodeKeychainRepository uses my SwiftKeychain wrapper that you can find here: https://github.com/yankodimitrov/SwiftKeychain.
- Include the contents of the
Keychainfolder to your project; - Include the
/Models/PasscodeKeychainRepository.swiftto your project; - Add the
Security.frameworkto your target;
The items stored in the Keychain will not be removed after your app is deleted, so make sure to implement a way to determine if your app is launched for the first time and if so, delete the stored passcode from the Keychain.
For example if your user forget your app passcode and decide to delete your app and then to install it again, your app should not ask the user to enter the previously stored passcode.
To delete the stored passcode from the Keychain, create an instance of the PasscodeKeychainRepository and call its deletePasscode() method.
##Usage
The PasscodeRepository protocol defines a way for you to implement a custom passcode CRUD operations. For example you may want to save the passcode in your custom database.
The GenericPasscodeLock conforms both to PasscodeLock and PasscodeLockStateFactory protocols. You can instantiate it with a custom passcode length and repository or using the convenience initializer with only an instance of PasscodeRepository. The latter sets the passcode length to 4.
The PasscodeLockViewController conforms to PasscodeLockPresentable and PasscodeLockDelegate protocols. It loads the PasscodeView.xib nib file as its view and uses Auto Layout to layout its view elements. The custom PasscodeButton and PasscodePlaceholderView that are used inside the nib are both @IBDesignable and @IBInspectable, so you can customize them directly from the interface builder (Figure 2).
Figure 2: PasscodeView.xib
Instead of creating a separate UIViewController for entering a passcode, changing the passcode and etc. we are reusing the PasscodeLockViewController but with different initial PasscodeLockState. The PasscodeLockViewController updates its view to represent the current state of its PasscodeLock instance.
Here is an example of presenting the PasscodeLockViewController with option for the user to set a passcode:
let passcodeRepository = PasscodeKeychainRepository()
let passcodeLock = GenericPasscodeLock(repository: passcodeRepository)
// set the initial state to SetPasscodeState
passcodeLock.state = passcodeLock.makeSetPasscodeState()
let controller = PasscodeViewController(passcodeLock: passcodeLock)
controller.onCorrectPasscode = {
// the user has successfully added a passcode
controller.dismissViewControllerAnimated(true, completion: nil)
}
presentViewController(controller, animated: true, completion: nil)By default the PasscodeLockViewController will display a Cancel button that user can use to dismiss the passcode view. You can hide the button by setting the hideCancelButton to true.
Here is an example of prompting the user to enter its passcode:
let passcodeRepository = PasscodeKeychainRepository()
let passcodeLock = GenericPasscodeLock(repository: passcodeRepository)
passcodeLock.state = passcodeLock.makeEnterPasscodeState()
let controller = PasscodeViewController(passcodeLock: passcodeLock)
controller.hideCancelButton = true
controller.onCorrectPasscode = {
// the user has successfully entered the passcode
controller.dismissViewControllerAnimated(true, completion: nil)
}
presentViewController(controller, animated: true, completion: nil)####Touch ID
When the PasscodeLock state inside the PasscodeViewController enters the EnterPasscodeState state, the user will be asked to use the Touch ID sensor on its device to authenticate (if the device has one). Upon successful authentication with Touch ID the onCorrectPasscode closure will be called.
At the same time the user may cancel the Touch ID prompt and enter the passcode manually.
You can use your own UIViewController just make sure to conform to PasscodeLockPresentable and PasscodeLockDelegate protocols.
###Localization
The included PasscodeLockState protocol implementations are using the en.lproj/PasscodeLock.strings file for their title and description properties values. Also in the same strings file you can find the localized Touch ID authentication reason string.
###Interface Orientations
The PasscodeLockViewController is displayed only in portrait on iPhone and in any orientation on iPad.
###PasscodeLockPresenter Now that our app has a passcode lock set up, it will be nice if we could implement a way to hide the possibly sensitive app data from the app snapshot that is taken when our app enters in background state.
#####LockSplashView
During my testing I found that if we present the PasscodeLockViewController, even without animation, the created by the iOS app snapshot will still contain the last seen view in our app, so I decided to use a splash view to hide it.
The splash view is added as a subview to the main UIWindow when our app enters the background state and then it is removed. Because we are adding a subview directly to the UIWindow without a UIViewController we have to manage the splash view rotation by ourselves, fortunately the PasscodeLockPresenter will take care for that.
At the same time when we are adding the splash view we are also presenting the PasscodeViewController on the most top UIViewController in the rootViewController's hierarchy. This way when the app becomes active again the user will have to enter its passcode.
PasscodeLockPresenter will also detect when our app is launched and in case that we have a passcode stored in the provided PasscodeRepository, will present the PasscodeViewController. The user will have first to enter its passcode or touch for Touch ID in order to use the app.
Here is an example usage of the PasscodeLockPresenter:
// AppDelegate.swift
...
var window: UIWindow?
lazy var passcodePresenter: PasscodeLockPresenter = {
let splash = LockSplashView()
let repository = PasscodeKeychainRepository()
let passcodeLock = GenericPasscodeLock(repository: repository)
passcodeLock.state = passcodeLock.makeEnterPasscodeState()
let passcodeController = PasscodeViewController(passcodeLock: passcodeLock)
passcodeController.hideCancelButton = true
let presenter = PasscodeLockPresenter(
passcodeViewController: passcodeController,
repository: repository,
splashView: splash
)
presenter.window = self.window
return presenter
}()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let passcodeLockPresenter = passcodePresenter
return true
}
...##Demo App The project contains a demo app with options to set, remove, update and change the passcode.
##Tests
You can find the tests inside the SwiftPasscodeTests folder.
##License SwiftPasscodeLock is released under the MIT license. See the LICENSE.txt file for more info.



