Day 93 - 100 Days of Swift
Project 28 (part 2)
Day 93 is the second part of the twenty-eighth project. You review what you learned and then he gives you three challenges. He challenges you to add a “Done” button to the app, to let the user lock it themselves if they want. He challenges you to let the user set a password so they can fallback on that if they don’t have Face ID or Touch ID. And he challenges you to go back to project 10 and require the user to authenticate before the names and faces are shown.
For the first challenge I just added a bar button whenever the user successfully unlocked the message:
1
2
3
4
5
// In unlockSecretMessage
navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .done,
target: self,
action: #selector(saveSecretMessage))
And then removed it whenever they save it:
1
2
// In saveSecretMessage
navigationItem.rightBarButtonItem = nil
The second challenge was a little more involved. First, I added a method to set a password:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@objc private func setPassword() {
let alertController = UIAlertController(title: "Choose a password",
message: nil,
preferredStyle: .alert)
var passwordField: UITextField!
alertController.addTextField { textfield in
textfield.placeholder = "Your new password"
textfield.isSecureTextEntry = true
passwordField = textfield
}
let set = UIAlertAction(title: "Set Password",
style: .default) { _ in
guard let password = passwordField.text else { return }
KeychainWrapper.standard.set(password,
forKey: Key.password)
}
alertController.addAction(set)
alertController.addAction(.cancel)
present(alertController, animated: true)
}
Then I added a method to unlock with a password:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@objc private func unlockWithPassword() {
let alertController = UIAlertController(title: "What's the password?",
message: nil,
preferredStyle: .alert)
var passwordField: UITextField!
alertController.addTextField { textfield in
textfield.placeholder = "Your password"
textfield.isSecureTextEntry = true
passwordField = textfield
}
let unlock = UIAlertAction(title: "Unlock",
style: .default) { _ in
if passwordField.text == KeychainWrapper.standard.string(forKey: Key.password) ?? "" {
self.unlockSecretMessage()
}
}
alertController.addAction(unlock)
alertController.addAction(.cancel)
present(alertController, animated: true)
}
Then I just called those methods in the appropriate places:
1
2
3
4
5
6
7
8
9
10
11
12
// In unlockSecretMessage
navigationItem.leftBarButtonItem =
UIBarButtonItem(title: "Set Password",
style: .plain,
target: self,
action: #selector(setPassword))
// In saveSecretMessage
navigationItem.leftBarButtonItem = nil
// In authenicate, if biometry isn't available
unlockWithPassword()
With that, it looks like this (for some reason, it did not record the keyboard while typing in the password, I am guessing because they are a secure entry fields, but in the gif I (1) set a password, (2) enter the wrong password and (3) enter the right password):

For the third challenge, to keep things relatively simple, I just made a view that covers everything else up until the user has authenticated:
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
lazy var lockedView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
let button = UIButton(type: .system)
button.setTitle("Authenticate", for: .normal)
button.addTarget(self,
action: #selector(authenticate),
for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
return view
}()
private func setupLockView() {
view.addSubview(lockedView)
NSLayoutConstraint.activate([
lockedView.topAnchor.constraint(equalTo: view.topAnchor),
lockedView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
lockedView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
lockedView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
I added a couple of methods to lock and unlock the view:
1
2
3
4
5
6
7
8
9
10
11
12
13
@objc private func unlockView() {
navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .add,
target: self,
action: #selector(addNewPerson))
lockedView.isHidden = true
}
@objc private func lockView() {
navigationItem.rightBarButtonItem = nil
lockedView.isHidden = false
}
Then I added the authenticate
method which is almost identical to the one from Project 28:
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
@objc func authenticate() {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
error: &error) {
let reason = "Use your fingerprint to unlock your saved faces."
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
localizedReason: reason) {
[weak self] success, authenticationError in
DispatchQueue.main.async {
if success {
self?.unlockView()
} else {
let title = "Authentication failed"
let message = "You could not be verified; please try again."
self?.presentErrorAlert(title: title,
message: message)
}
}
}
} else {
let title = "Biometry unavailable"
let message = "Your device is not configured for biometric authentication."
self.presentErrorAlert(title: title,
message: message)
}
}
Finally, I added an observer to make sure the view gets locked whenever the application will resign active:
1
2
3
4
NotificationCenter.default.addObserver(self,
selector: #selector(lockView),
name: UIApplication.willResignActiveNotification,
object: nil)
With that, it looks like this:

You can find my version of these projects at the end of day 93 on Github here.