100 Days of Swift — Day 99-100

Learning about biometrics (Project 28)

Create a new iOS SingleView app project in Xcode, name it Secret Swift or Project28, as you see it fit, and get ready to rock!

Creating a basic text editor

First things first: the UI!

Embed the view controller in a navigation controller, drag a text view inside the canvas and make it occupy all the safe area, resolve auto-layout issues for this view, drag a button to the center of the screen, change its height to be 44 (and fix it with a constraint), set it to be horizontally and vertically centred, erase the text view text and change the button title to be “Authenticate”.

Then, connect the text view to an outlet called “secret” and the button to an action called “authenticateTapped”. Once this is done drag the button above the text view in the document outline so that it sits into a lower layer compared to the text view. Finally select the text view and, in the Attributes Inspector, makes it hidden.

Setting up the keyboard

This is almost the same code as in Project 19 (not that I recall it too well, but let’s move on!). Instantiate the default Notification Center for the app and add two observers to it: this method look like this: func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?). For the observer (so, who is watching for notification), write self (the view controller), for selector write #selector(adjustForKeyboard) (not written yet, of course), for name write respectively UIResponder.keyboardWillHideNotification and UIResponder.keyboardWillChangeFrameNotification and leave nil for the object parameter.

The first case will post a notification immediately prior to the dismissal of the keyboard, while the second case will do so prior to a change in the keyboard’s frame (which could happen if we rotate our device).

Now write the adjustForKeyboard(notification:) method. First check that in the userInfo dictionary of the passing notification parameter, at the voice UIResponder.keyboardFrameEndUserInfoKey (🤯), there is something that can be downcast as NSValue, you know, that “simple container for a single C or Objective-C data item”. I think I will have to go back and read my own notes on project 19!

Second, if that something was found, capture its .cgRectValue, i.e., the defining value of the rectangle making up its frame, then convert it from the coordinate system of the view.window to that of the receiver (the view controller is the receiver, I assume…).

Third, if the name of the notification is (the one that hides it) set the text view’s content inset to be zero (no keyboard = no need for insets!). Otherwise, set its UIEdgeInsets to be 0 for top, left and right, but equal to the height of the keyboard view final frame minus the bottom Safe Area insets of the view in which we placed the text view.

This was all already done n project 19, but it helps reviewing!

Fourth, set the text view’s scroll indicator insets to be equal to its content inset (so that we get a scroll bar which sits nicely inside that edge and doesn’t bother the reader as it happens in many websites), declare an NSRange with the current selection range of the text view (no idea nor no memory of what this means) then set the scrollRangeToVisible(_:) method of the text view to use this range. I guess this means that when the cursor would go below the lower edge of the text view (i.e., below the keyboard), the view would scroll automatically?

Writing somewhere safe: the iOS keychain

Change the title of the view controller to be “Nothing to see here”, just in case someone would be curious about it. Then import the two files from the GitHub repository called: KeychainItemAccessibility.swift and KeychainWrapper.swift. They allow working with the Keychain in a much easier and nicer way that what it should really be. I gave a quick look at the two files but they would require a couple of hours of studying to really get what they mean and, by now, this is not necessary I think.

To summarise: KeychainWrapper is a class to help make Keychain access in Swift more straightforward. It is designed to make accessing the Keychain services more like using NSUserDefaults, which is much more familiar to people.

Write the new unlockSecretMessage method by unhiding the text view, setting the title to “Secret stuff!” and optional binding the string for the “SecretMessage” key of the standard keychain wrapper to the text constant. If that succeeds set secret.text equal to it.

Now write another new method, @objc func saveSecretMessage(), with a check to see whether the text view is not hidden, then setting its text to the keychain with the key “SecretMessage”. After this, resign the first responder status from the text view, hide it, and change back the title to what it was before.

Inside viewDidLoad add another Notification Center observer with the #selector(saveSecretMessage) and the name UIApplication.willResignActiveNotification (that is, when the app has been terminated or put into suspended state).

Finally, complete the authenticateTapped method with a call to unlockSecretMessage().

Touch to activate: Touch ID, Face ID and LocalAuthentication

First and foremost, import the LocalAuthentication framework. Then, replace the code inside authenticateTapped with the following operations: declare a local authentication context (LAContext(), which is a mechanism for evaluating authentication policies and access controls) and an error variable of type NSError (which contains information about an error condition including a domain, a domain-specific error code and application-specific information).

Next, check whether we can use biometric authentication or not by using a control flow if-else statement with the Boolean return value of the call to .canEvaluatePolicy method on the local authentication context. This methods assesses whether authentication can proceed for a given policy and accepts two parameters: an LAPolicy and an NSErrorPointer, that is a reference to a specific address in memory where an NSError object should be. Pass .deviceOwnerAuthenticationWithBiometrics as the first parameter and &error for the second. That & symbol means that we are passing in the error variable and getting out an error if there is one (that’s the reason of the optionality). It is the equivalent of the inout Swift parameter, which we studied but never used in practice as of now.

Declare a reason string for why the user should authenticate then call the .evaluatePolicy method on our context. Pass the same policy parameter and the reason to the method and invoke the trailing closure syntax. Weakly capture self, declare two parameters for the closure (a success Boolean and an authenticationError optional Error and then dispatch the main queue to execute asynchronously the desired code which is: in case success is true, call the unlockSecretMessage() method, otherwise present an alert controller informing the user that the authentication failed.

In the outer else statement present an alert controller to inform the user that biometry was found as not available.

That’s it, the project is completed!

Now on with the review and the challenges!

Review for Project 28

Here is what we learned in this project (plus some review):

  1. UIEdgeInsets stores top, left, bottom, and right values in one instance. It’s useful for describing things like content insets on text views.
  2. NotificationCenter lets us observe as many system events as we need. Obviously we shouldn’t look for everything, not least because it would make our code messy.
  3. We can use isHidden to show or hide a view at runtime. This is a Boolean property set to true by default. Uh?! Really? Shouldn’t it be false by default? I mean, if I do not call it the view is visible so, in theory, the property is false by default…
  4. The iOS keychain is a safe place to store sensitive user data such as passwords. The keychain is the smartest place to save any user-sensitive data.
  5. The resignFirstResponder() method stops a selected text control from being active, which in turn makes the keyboard go away. You should call this method when you want the user to stop editing a text field or text view.
  6. We’re sent the UIResponder.keyboardWillChangeFrameNotification notification for small changes such as the Quick Type area being shown or hidden. This allows us to update our text view insets as needed.
  7. We can simulate Touch ID and Face ID using the iOS Simulator. You can opt into biometric authentication, and provide both matching and unmatching faces/touches.
  8. Face ID support is provided through the LocalAuthentication framework. This needs to be imported separately from UIKit.
  9. Using an ampersand before a parameter being passed into a function works like Swift’s inout parameters. Just like with inout, Swift requires these values to be created as variables.
  10. We can move views above and below other views using Interface Builder. You can just drag and drop them inside the document outline.
  11. NSValue is an Objective-C wrapper that can hold a CGRect, a CGPoint, or something else. NSValue was needed because Objective-C was not able to store CGRect and other structs in arrays and dictionaries.
  12. Scroll indicator insets control how large the scroll bar is relative to its container. These insets let the user see how much content there is in the scroll view, as well as how far through it they are.

Challenges

Challenge 1: add a Done button as a navigation bar item that causes the app to re-lock immediately rather than waiting for the user to quit. This should only be shown when the app is unlocked.

Once more, I have no idea why this took me so much to solve. I am hopelessly tired, half-sick, I slept 3 hours tonight and I’m just falling in front of the screen.

I tried to create this as a storyboard item and then connect it via an outlet but then I would have had to add another action and I felt there could have been a better way. Still, I’m not sure whether my solution is the good one but, well, we will see.

In viewDidLoad I created a new bar button item and placed it in the right slot of the navigation bar; I made it of the .done type and call saveSecretMessage as its action. As per the assignment we needed to show this only when the app would be unlocked. Still here I added the following line:

navigationItem.rightBarButtonItem?.isEnabled = false

The optionality is an automatic thing because that button could or could not be there.

As finishing touches—but not so just cosmetic—I set that same line to true in the unlockSecretMessage method and back to false again in the saveSecretMessage one.

Done!

Challenge 2: create a password system for your app so that the Touch ID / Face ID fallback is more useful. You’ll need to use an alert controller with a text field like we did in project 5, and I suggest you save the password in the keychain!

This one requires quite a bit of thinking but, in the end, I found myself more at ease with this challenge than with the previous one. Who knows why?

First, create an optional password string at the top of your class. Then, in viewDidLoad, checkoff there is a saved password in the keychain and, if so, set the password string to be equal to the value found in the dictionary.

Otherwise, present an alert controller that prompts the user for setting a password in case biometrics would not work. This should be second nature for us but I have to admit I still do not feel this process automatic yet. In this challenge alone I wrote it three times so I hope it is starting to sink in. Once the password is set save it in the keychain. You can also add a Cancel action if you want.

Inside authenticationTapped replace the code for the else statement with this:

let authFailedAC = UIAlertController(title: "Authentication failed", message: "Please enter your password to unlock the secret content", preferredStyle: .alert)
authFailedAC.addTextField { (textField) in
	textField.isSecureTextEntry = true
	textField.placeholder = "Enter password"
}

let enterPwdAction = UIAlertAction(title: "Unlock", style: .default) { [weak self, weak authFailedAC] (_) in
    guard let password = authFailedAC?.textFields?[0].text else { return }
    
    if password == self?.password {
        self?.unlockSecretMessage()
    } else {
        let ac = UIAlertController(title: "Authentication failed", message: "You could not be verified; please try again.", preferredStyle: .alert)
        ac.addAction(UIAlertAction(title: "OK", style: .default))
        self?.present(ac, animated: true)
    }
}

authFailedAC.addAction(enterPwdAction)
self?.present(authFailedAC, animated: true)

The only thing I could not make was to invoke once more the text field if the password was wrong. I could have created a method and call it recursively but … at this moment I could not… 10pm here!

You can find both these first two challenges, along with the main project here.

Challenge 3: go back to project 10 (Names to Faces) and add biometric authentication so the user’s pictures are shown only when they have unlocked the app. You’ll need to give some thought to how you can hide the pictures — perhaps leave the array empty until they are authenticated?

I liked this one as it required a good amount of thinking but, in the end, I felt it within my grasp, which is a welcome feeling to close this 100th day with.

First, import LocalAuthentication, then, going in order, inside viewDidLoad, add a right bar button item, with the “Unlock” title, a plain style and, as action, #selector(unlockCollection) —we are going to write this very soon.

Second, initialise the default Notification Center and add an observer to it with a #selector(lockCollection) method and a name of UIApplication.willResignActiveNotification. Finally, set the left bar button item .isEnabled property to be false so that we are not going to be allowed to add pictures until we unlock the app.

Third, write the unlockCollection() method. Initialise an LAContext() and an optional NSError?, then ask if we can evaluate the policy (don’t forget to change the Info.plist file as we did above in project 28). Give a reason then evaluate the policy exactly as before. Now for some changes: if we have a success unhide the collection view (wait, when did we hide it? Read on and you will see!), disable the right bar button item (no point to unlock an unlocked app, right?) and enable the left bar button item then, at this point, move the loading code from viewDidLoad to here. We could also create a load method and just call it here. Your choice. The else statement is the same.

Fourth, write the lockCollection() method. Inside hide the collection view and then, using a dispatch queue of one second enable the right bar button item and disable the left one.

Done! It works!

I know, it could have been better, with multiple arrays copying from one to the other but, I feel this solution is elegant enough. What do you think?!

Let me know down in the comments!

You can find this version of Project 10 here, at the “Challenge28-3(biometrics)” branch.


So, I can’t believe it I made it to the end of the 100th day of Swift learning. Not finished the proposed path yet but hell am I going to keep moving!

Please don’t forget to drop a hello and a thank you to Paul for all his great work (you can find him on Twitter) and be sure to visit the 100 Days Of Swift initiative page. We are learning so much thanks to him and he deserves to know of our gratitude.

He has written about 20 great books on Swift, all of which you can check about here.

The 100 Days of Swift initiative is based on the Hacking with Swift book, which you should definitely check out.


If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).

If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s books from this Affiliate Link.

Anyways, thank you so much for reading!

Till the next one!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: