How To Secure iOS User Data: The Keychain and Biometrics – Face ID or Touch ID

Protecting an app with a login screen is a great way to secure user data – you can use the Keychain, which is built right in to iOS, to ensure their data stays secure. Apple also offers yet another layer of protection with Face ID and Touch ID.

Available since the iPhone 5S, biometric data is stored in a secure enclave in the A7 and newer chips. All of this means you can comfortably hand over the responsibility of handling login information to the Keychain and either Face ID or Touch ID.

In this tutorial you’ll start out with static authentication. Next you’ll be using the Keychain to store and verify login information. Finally, you’ll explore using Touch ID or Face ID in your app.

Note: Face ID requires you test on a physical device. Touch ID can now be emulated in Xcode 9 in the Simulator. The Keychain can also be used in the simulator. Throughout the tutorial I refer to Touch ID and it applies to Face ID in most cases. Under the hood, is the Local Authentication framework.

Getting Started

Download the starter project for this tutorial here.

This is a basic note taking app that uses Core Data to store user notes; the storyboard has a login view where users can enter a username and password, and the rest of the app’s views are already connected to each other and ready to use.

Build and run to see what your app looks like in its current state:

Note: You can ignore any compiler error about Note type missing. It will be autogenerated by Core Data

At this point, tapping the Login button simply dismisses the view and displays a list of notes – you can also create new notes from this screen. Tapping Logout takes you back to the login view. If the app is pushed to the background it will immediately return to the login view; this protects data from being viewed without being logged in.

With all of the housekeeping done, it’s time to code! :]

Logging? No. Log In.

To get the ball rolling, you’re going to add the ability to check the user-provided credentials against hard-coded values.

Open LoginViewController.swift and add the following constants just below managedObjectContext:

let usernameKey = "Batman"
let passwordKey = "Hello Bruce!"

These are simply the hard-coded username and password you’ll check the user-provided credentials against.

Next, add the following method below loginAction(_:):

func checkLogin(username: String, password: String) -> Bool {
  return username == usernameKey && password == passwordKey
}

Here you check the user-provided credentials against the constants previously defined.

Next, replace the contents of loginAction(_:) with the following:

if checkLogin(username: usernameTextField.text!, password: passwordTextField.text!) {
  performSegue(withIdentifier: "dismissLogin", sender: self)
}

Here you call checkLogin(username:password:), which dismisses the login view only if the credentials are correct.

Build and run. Enter the username Batman and the password Hello Bruce!, and tap the Login button. The login screen should dismiss as expected.

While this simple approach to authentication seems to work, it’s not terribly secure, as credentials stored as strings can easily be compromised by curious hackers with the right tools and training. As a best practice, passwords should NEVER be stored directly in the app. To that end, you’ll employ the Keychain to store the password.

Note: Passwords in most apps are simply strings and are hidden as bullets. The best way to handle a password in your app is to SALT it and/or encrypt it with a SHA-2 encryption as soon as it is captured. Only the user should know the actual string. This is beyond the scope of this tutorial, but you should keep this in mind.

Check out Chris Lowe’s Basic Security in iOS 5 – Part 1 tutorial for the lowdown on how the Keychain works.

Rapper? No. Wrapper.

The next step is to add a Keychain wrapper to your app.

Along with the starter project, you downloaded a folder with useful resources. Locate and open the Resources folder in Finder. You’ll see the file KeychainPasswordItem.swift; this class comes from Apple’s sample code GenericKeychain.

Drag the KeychainPasswordItem.swift into the project, like so:

When prompted, make sure Copy items if needed and TouchMeIn target are both checked:

Build and run to make sure you have no errors. All good? Great — now you can leverage the Keychain from within your app.

Keychain, Meet Password. Password, Meet Keychain

To use the Keychain, you first store a username and password in it. Next, you’ll check the user-provided credentials against the Keychain to see if they match.

You’ll track whether the user has already created some credentials so you can change the text on the Login button from “Create” to “Login”. You’ll also store the username in the user defaults so you can perform this check without hitting the Keychain each time.

The Keychain requires some configuration to properly store your app’s information. You’ll provide that configuration in the form of a serviceName and an optional accessGroup. You’ll use a struct to store these values.

Open LoginViewController.swift. Add the following just below the import statements:

// Keychain Configuration
struct KeychainConfiguration {
  static let serviceName = "TouchMeIn"
  static let accessGroup: String? = nil
}

Next, add the following below managedObjectContext:

var passwordItems: [KeychainPasswordItem] = []
let createLoginButtonTag = 0
let loginButtonTag = 1

@IBOutlet weak var loginButton: UIButton!

passwordItems is an empty array of KeychainPasswordItem types you’ll pass into the keychain. You’ll use the next two constants to determine if the Login button is being used to create some credentials, or to log in; you’ll use the loginButton outlet to update the title of the button depending on its state.

Next, you’ll handle the two cases for when the button is tapped: if the user hasn’t yet created their credentials, the button text will show “Create”, otherwise the button will show “Login”.

First you’ll need a way to tell the user if the login fails. Add the following after checkLogin(username:password:):

private func showLoginFailedAlert() {
  let alertView = UIAlertController(title: "Login Problem",
                                    message: "Wrong username or password.",
                                    preferredStyle:. alert)
  let okAction = UIAlertAction(title: "Foiled Again!", style: .default)
  alertView.addAction(okAction)
  present(alertView, animated: true)
}

Now, replace loginAction(sender:) with the following:

@IBAction func loginAction(sender: UIButton) {
  // 1
  // Check that text has been entered into both the username and password fields.
  guard let newAccountName = usernameTextField.text,
    let newPassword = passwordTextField.text,
    !newAccountName.isEmpty,
    !newPassword.isEmpty else {
      showLoginFailedAlert()
      return
  }
    
  // 2
  usernameTextField.resignFirstResponder()
  passwordTextField.resignFirstResponder()
    
  // 3
  if sender.tag == createLoginButtonTag {
    // 4
    let hasLoginKey = UserDefaults.standard.bool(forKey: "hasLoginKey")
    if !hasLoginKey && usernameTextField.hasText {
      UserDefaults.standard.setValue(usernameTextField.text, forKey: "username")
    }
      
    // 5
    do {
      // This is a new account, create a new keychain item with the account name.
      let passwordItem = KeychainPasswordItem(service: KeychainConfiguration.serviceName,
                                              account: newAccountName,
                                              accessGroup: KeychainConfiguration.accessGroup)
        
      // Save the password for the new item.
      try passwordItem.savePassword(newPassword)
    } catch {
      fatalError("Error updating keychain - \(error)")
    }
      
    // 6
    UserDefaults.standard.set(true, forKey: "hasLoginKey")
    loginButton.tag = loginButtonTag
    performSegue(withIdentifier: "dismissLogin", sender: self)
  } else if sender.tag == loginButtonTag {
     // 7
    if checkLogin(username: newAccountName, password: newPassword) {
      performSegue(withIdentifier: "dismissLogin", sender: self)
    } else {
      // 8
      showLoginFailedAlert()
    }
  }
}

Here’s what’s happening in the code:

  1. If either the username or password is empty, you present an alert to the user and return from the method.
  2. Dismiss the keyboard if it’s visible.
  3. If the login button’s tag is createLoginButtonTag, then proceed to create a new login.
  4. Next, you read hasLoginKey from UserDefaults which you use to indicate whether a password has been saved to the Keychain. If hasLoginKey is false and the username field has any text, then you save that text as username to UserDefaults.
  5. You create a KeychainPasswordItem with the serviceNamenewAccountName (username) and accessGroup. Using Swift’s error handling, you try to save the password. The catch is there if something goes wrong.
  6. You then set hasLoginKey in UserDefaults to true to indicate a password has been saved to the keychain. You set the login button’s tag to loginButtonTag to change the button’s text, so it will prompt the user to log in the next time they run your app, rather than prompting the user to create a login. Finally, you dismiss loginView.
  7. If the user is logging in (as indicated by loginButtonTag), you call checkLogin to verify the user-provided credentials; if they match then you dismiss the login view.
  8. If the login authentication fails, then present an alert message to the user.
Note: Why not just store the password along with the username in UserDefaults? That would be a bad idea because values stored in UserDefaults are persisted using a plist file. This is essentially an XML file that resides in the app’s Library folder, and is therefore readable by anyone with physical access to the device. The Keychain, on the other hand, uses the Triple Digital Encryption Standard (3DES) to encrypt its data. Even if somebody gets the data, they won’t be able to read it.

Next, replace checkLogin(username:password:) with the following updated implementation:

func checkLogin(username: String, password: String) -> Bool {
  guard username == UserDefaults.standard.value(forKey: "username") as? String else {
    return false
  }
    
  do {
    let passwordItem = KeychainPasswordItem(service: KeychainConfiguration.serviceName,
                                            account: username,
                                            accessGroup: KeychainConfiguration.accessGroup)
    let keychainPassword = try passwordItem.readPassword()
    return password == keychainPassword
  } catch {
    fatalError("Error reading password from keychain - \(error)")
  }
}

Here you check that the username entered matches the one stored in UserDefaults and that the password matches the one stored in the Keychain.

Next, delete the following lines:

let usernameKey = "Batman"
let passwordKey = "Hello Bruce!"

Now it’s time to set the button title and tags appropriately depending on the state of hasLoginKey.

Add the following code to viewDidLoad(), just below the call to super:

// 1
let hasLogin = UserDefaults.standard.bool(forKey: "hasLoginKey")
    
// 2
if hasLogin {
  loginButton.setTitle("Login", for: .normal)
  loginButton.tag = loginButtonTag
  createInfoLabel.isHidden = true
} else {
  loginButton.setTitle("Create", for: .normal)
  loginButton.tag = createLoginButtonTag
  createInfoLabel.isHidden = false
}
    
// 3
if let storedUsername = UserDefaults.standard.value(forKey: "username") as? String {
  usernameTextField.text = storedUsername
}

Taking each numbered comment in turn:

  1. You first check hasLoginKey to see if you’ve already stored a login for this user.
  2. If so, change the button’s title to Login, update its tag to loginButtonTag, and hide createInfoLabel, which contains the informative text “Start by creating a username and password“. In case you don’t have a stored login for this user, you set the button label to Create and display createInfoLabel to the user.
  3. Finally, you set the username field to what is saved in UserDefaults to make logging in a little more convenient for the user.

Finally, you need to connect your outlet to the Login button. Open Main.storyboard and select the Login View Controller Scene. Ctrl-drag from the Login View Controller to the Login button, as shown below:

From the resulting popup, choose loginButton:

Build and run. Enter a username and password of your own choosing, then tap Create.

Note: If you forgot to connect the loginButton IBOutlet then you might see the error Fatal error: unexpectedly found nil while unwrapping an Optional value. If you do, connect the outlet as described in the relevant step above.

Now tap Logout and attempt to login with the same username and password – you should see the list of notes appear.

Tap Logout and try to log in again; this time, use a different password and then tap Login. You should see the following alert:

Congratulations – you’ve now added authentication use the Keychain. Next up, Touch ID.

Touching You, Touching Me

Note: Face ID requires that you test on a physical device, such as an iPhone X. Touch ID can now be emulated in Xcode 9 in the Simulator. You can test biometric ID on any device with a A7 chip or newer and Face ID/Touch ID hardware.

In this section, you’ll add biometric ID to your project in addition to using the Keychain. While Keychain isn’t necessary for Face ID/Touch ID to work, it’s always a good idea to implement a backup authentication method for instances where biometric ID fails, or for users that don’t have at least a Touch ID compatible device.

Open Assets.xcassets.

Next, open the Resources folder from the starter project you downloaded earlier in Finder. Locate FaceIcon and Touch-icon-lg.png images (all three sizes), select all six and drag them into Images.xcassets so that Xcode knows they’re the same image, only with different resolutions:

Open Main.storyboard and drag a Button from the Object Library onto the Login View Controller Scene, just below the Create Info Label, inside the Stack View. You can open the Document Outline, swing open the disclosure triangles and make sure that the Button is inside the Stack View. It should look like this:

If you need to review stack views, take a look at UIStackView Tutorial: Introducing Stack Views.

In the Attributes inspector, adjust the button’s attributes as follows:

  • Set Type to Custom.
  • Leave the Title empty.
  • Set Image to Touch-icon-lg.

When you’re done, the button’s attributes should look like this:

Ensure your new button is selected, then click the Add New Constraints button in the layout bar at the foot of the storyboard canvas and set the constraints as below:

  • Width should be 66
  • Height should be 67

Click Add 2 Constrains. Your view should now look like the following:

Still in Main.storyboard, open the Assistant Editor and make sure LoginViewController.swift is showing.

Next, Control-drag from the button you just added to LoginViewController.swift, just below the other IBOutlets, like so:

In the popup enter touchIDButton as the Name and click Connect:

This creates an IBOutlet you’ll use to hide the button on devices that don’t have biometric ID available.

Next, you need to add an action for the button.

Control-drag from the same button to LoginViewController.swift to just above checkLogin(username:password:):

In the popup, change Connection to Action, set Name to touchIDLoginAction, set Arguments to none for now. Then click Connect.

Build and run to check for any errors. You can still build for the Simulator at this point since you haven’t yet added support for biometric ID. You’ll take care of that now.

Adding Local Authentication

Implementing biometric ID is as simple as importing the Local Authentication framework and calling a couple of simple yet powerful methods.

Here’s what the Local Authentication documentation has to say:

“The Local Authentication framework provides facilities for requesting authentication from users with specified security policies.”

The specified security policy in this case will be your user’s biometrics — A.K.A their face or fingerprint! :]

New in iOS 11 is support for Face ID. LocalAuthentication adds a couple of new things: a required FaceIDUsageDescription and a LABiometryType to determine whether the device supports Face ID or Touch ID.

In Xcode’s Project navigator select the project and click the Info tab. Hover over the right edge of one of the Keys and click the +. Start typing “Privacy” and from the pop up list that appears select “Privacy – Face ID Usage Description” as the key.

Note: you can also enter “NSFaceIDUsageDescription” as the key.

The type should be a String. In the value field enter We use Face ID to unlock the notes.

In the Project navigator right-click the TouchMeIn group folder and select New File…. Choose iOS\Swift File. Click Next. Save the file as TouchIDAuthentication.swift with the TouchMeIn target checked. Click Create.

Open TouchIDAuthentication.swift and add the following import just below import Foundation:

import LocalAuthentication

Next, add the following to create a new class:

class BiometricIDAuth {
  
}

Now you’ll need a reference to the LAContext class.

Inside the class add the following code between the curly braces:

let context = LAContext()

The context references an authentication context, which is the main player in Local Authentication. You’ll need a function to see if biometric ID is available to the user’s device or in the Simulator.

Add the following method to return a Bool if biometric ID is supported inside BiometricIDAuth:

func canEvaluatePolicy() -> Bool {
  return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}

Open LoginViewController.swift and add the following property to create a reference to BiometricIDAuth.

let touchMe = BiometricIDAuth()

At the bottom of viewDidLoad() add the following:

touchIDButton.isHidden = !touchMe.canEvaluatePolicy()

Here you use canEvaluatePolicy(_:) to check whether the device can implement biometric authentication. If so, show the Touch ID button; if not, leave it hidden.

Build and run on the Simulator; you’ll see the Touch ID logo is hidden. Now build and run on your physical Face ID/Touch ID-capable device; you’ll see the Touch ID button is displayed. In the Simulator you can choose Touch ID > Enrolled from the Hardware menu and test the button.

Face ID or Touch ID

If you’re running on an iPhone X or later Face ID equipped device you may notice a problem. You’ve taken care of the Face ID Usage Description, and now the Touch ID icon seems out of place. You’ll use the biometryType enum to fix this.

Open, TouchIDAuthentication.swift and add a BiometricType enum above the class.

enum BiometricType {
  case none
  case touchID
  case faceID
}

Next, add the following function to return which biometric type is supported using the canEvaluatePolicy.

func biometricType() -> BiometricType {
  let _ = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
  switch context.biometryType {
  case .none:
    return .none
  case .touchID:
    return .touchID
  case .faceID:
    return .faceID
  }
}

Open, LoginViewController and add the following to the bottom of viewDidLoad() to fix the button’s icon.

switch touchMe.biometricType() {
case .faceID:
  touchIDButton.setImage(UIImage(named: "FaceIcon"),  for: .normal)
default:
  touchIDButton.setImage(UIImage(named: "Touch-icon-lg"),  for: .normal)
}

Build and run on the Simulator with Touch ID Enrolled to see the Touch ID icon; you’ll see the correct icon is shown on the iPhone X – the Face ID icon.

Putting Touch ID to Work

Open, TouchIDAuthentication.swift and add the following variable below context:

var loginReason = "Logging in with Touch ID"

The above provides the reason the application is requesting authentication. It will display to the user on the presented dialog.

Next, add the following method to the bottom of BiometricIDAuth to authenticate the user:

func authenticateUser(completion: @escaping () -> Void) { // 1
  // 2
  guard canEvaluatePolicy() else {
    return
  }

  // 3
  context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
    localizedReason: loginReason) { (success, evaluateError) in
      // 4
      if success {
        DispatchQueue.main.async {
          // User authenticated successfully, take appropriate action
          completion()
        }
      } else {
        // TODO: deal with LAError cases
      }
  }
}

Here’s what’s going on in the code above:

  1. authenticateUser(completion:) takes a completion handler in the form of a closure.
  2. You’re using canEvaluatePolicy() to check whether the device is capable of biometric authentication.
  3. If the device does support biometric ID, you then use evaluatePolicy(_:localizedReason:reply:) to begin the policy evaluation — that is, prompt the user for biometric ID authentication. evaluatePolicy(_:localizedReason:reply:) takes a reply block that is executed after the evaluation completes.
  4. Inside the reply block, you are handling the success case first. By default, the policy evaluation happens on a private thread, so your code jumps back to the main thread so it can update the UI. If the authentication was successful, you will call the segue that dismisses the login view.

You’ll come back and deal with errors in little while.

Open, LoginViewController.swift, locate touchIDLoginAction(_:) and replace it with the following:

@IBAction func touchIDLoginAction() {    
  touchMe.authenticateUser() { [weak self] in
    self?.performSegue(withIdentifier: "dismissLogin", sender: self)
  }
}

If the user is authenticated, you can dismiss the Login view.

Go ahead and build and run to see if all’s well.

Dealing with Errors

Wait! What if you haven’t set up biometric ID on your device? What if you are using the wrong finger or are wearing a disguise? Let’s deal with that.

An important part of Local Authentication is responding to errors, so the framework includes an LAError type. There also is the possibility of getting an error from the second use of canEvaluatePolicy.

You’ll present an alert to show the user what has gone wrong. You will need to pass a message from the TouchIDAuth class to the LoginViewController. Fortunately you have the completion handler that you can use it to pass the optional message.

Open, TouchIDAuthentication.swift and update the authenticateUser method.

Change the signature to include an optional message you’ll pass when you get an error.

func authenticateUser(completion: @escaping (String?) -> Void) {

Next, find the // TODO: and replace it with the following:

// 1
let message: String
            
// 2
switch evaluateError {
// 3
case LAError.authenticationFailed?:
  message = "There was a problem verifying your identity."
case LAError.userCancel?:
  message = "You pressed cancel."
case LAError.userFallback?:
  message = "You pressed password."
case LAError.biometryNotAvailable?:
  message = "Face ID/Touch ID is not available."
case LAError.biometryNotEnrolled?:
  message = "Face ID/Touch ID is not set up."
case LAError.biometryLockout?:
  message = "Face ID/Touch ID is locked."
default:
  message = "Face ID/Touch ID may not be configured"
}
// 4
completion(message)

Here’s what’s happening

  1. Declare a string to hold the message.
  2. Now for the “failure” cases. You use a switch statement to set appropriate error messages for each error case, then present the user with an alert view.
  3. If the authentication failed, you display a generic alert. In practice, you should really evaluate and address the specific error code returned, which could include any of the following:
    • LAError.biometryNotAvailable: the device isn’t Face ID/Touch ID-compatible.
    • LAError.passcodeNotSet: there is no passcode enabled as required for Touch ID
    • LAError.biometryNotEnrolled: there are no face or fingerprints stored.
    • LAError.biometryLockout: there were too many failed attempts.
  4. Pass the message in the completion closure.

iOS responds to LAError.passcodeNotSet and LAError.biometryNotEnrolled on its own with relevant alerts.

There’s one more error case to deal with. Add the following inside the else block of the guard statement, just above return.

completion("Touch ID not available")

The last thing to update is the success case. That completion should contain nil, indicating that you didn’t get any errors. Inside the first success block add the nil.

completion(nil)

Once you’ve completed these changes your finished method should look like this:

func authenticateUser(completion: @escaping (String?) -> Void) {
    
  guard canEvaluatePolicy() else {
    completion("Touch ID not available")
    return
  }
    
  context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
    localizedReason: loginReason) { (success, evaluateError) in
      if success {
        DispatchQueue.main.async {
          completion(nil)
        }
      } else {
                              
        let message: String
                              
        switch evaluateError {
        case LAError.authenticationFailed?:
          message = "There was a problem verifying your identity."
        case LAError.userCancel?:
          message = "You pressed cancel."
        case LAError.userFallback?:
          message = "You pressed password."
        case LAError.biometryNotAvailable?:
          message = "Face ID/Touch ID is not available."
        case LAError.biometryNotEnrolled?:
          message = "Face ID/Touch ID is not set up."
        case LAError.biometryLockout?:
          message = "Face ID/Touch ID is locked."
        default:
          message = "Face ID/Touch ID may not be configured"
        }
          
        completion(message)
      }
  }
}
Note: When you compile this new error handling, you will see three warnings complaining of using deprecated constants. This is due to a combination of the way Apple added support for Face ID and the way Swift imports Objective-C header files. There are some potential workarounds, but they are much less “Swift-like”. Since Apple is aware of the issue and plans to fix it at a future date, the cleaner code is presented here.

Open LoginViewController.swift and update the touchIDLoginAction(_:) to look like this:

@IBAction func touchIDLoginAction() {
  // 1
  touchMe.authenticateUser() { [weak self] message in
    // 2
    if let message = message {
      // if the completion is not nil show an alert
      let alertView = UIAlertController(title: "Error",
                                        message: message,
                                        preferredStyle: .alert)
      let okAction = UIAlertAction(title: "Darn!", style: .default)
      alertView.addAction(okAction)
      self?.present(alertView, animated: true)
    } else {
      // 3
      self?.performSegue(withIdentifier: "dismissLogin", sender: self)
    }
  }
}

Here’s what you’re doing in this code snippet:

  1. You’ve updated the trailing closure to accept an optional message. If biometric ID works, there is no message.
  2. You use if let to unwrap the message and display it with an alert.
  3. No change here, but if you have no message, you can dismiss the Login view.

Build and run on a physical device and test logging in with Touch ID.

Since LAContext handles most of the heavy lifting, it turned out to be relatively straight forward to implement biometric ID. As a bonus, you were able to have Keychain and biometric ID authentication in the same app, to handle the event that your user doesn’t have a Touch ID-enabled device.

Note: If you want test the errors in Touch ID, you can try to login with an incorrect finger. Doing so five times will disable Touch ID and require password authentication. This prevents strangers from trying to break into other applications on your device. You can re-enable it by going to Settings -> Touch ID & Passcode.

Look Mom! No Hands.

One of the coolest things about the iPhone X is using Face ID without touching the screen. You added a button which you can use to trigger the Face ID, but you can trigger Face ID automagically as well.

Open LoginViewController.swift and add the following code right below viewDidLoad():

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  let touchBool = touchMe.canEvaluatePolicy()
  if touchBool {
   touchIDLoginAction()
  }
}

The above will verify if biometric ID is supported and if so try and authenticate the user.

Build and run on an iPhone X or Face ID equipped device and test logging in hands free!

Advertisements
How To Secure iOS User Data: The Keychain and Biometrics – Face ID or Touch ID

UISearchController Tutorial

Scrolling through massive lists of items can be a slow and frustrating process for users. When dealing with large datasets, it’s vitally important to let the user search for specific items. UIKit includes UISearchBar, which seamlessly integrates with UINavigationItem and allows for quick, responsive filtering of information.

In this UISearchController tutorial, you’ll build a searchable Candy app based on a standard table view. You’ll add table view search capability and dynamic filtering, and add an optional scope bar, all while taking advantage of UISearchController. In the end, you’ll know how to make your apps much more user friendly and satisfy your users’ urgent demands.

Ready for some sugar-coated search results? Read on.

Getting Started

Download the starter project from the tutorial here and open the project. The app has already been set up with a navigation controller. In the Xcode project navigator, select the project file, CandySearch, then select the target CandySearch, and in the signing section update the team to your own development team. Build and run the app, and you’ll see an empty list:

Back in Xcode, the file Candy.swift contains a struct to store the information about each piece of candy you’ll be displaying. This struct has two properties: the category and name of the candy.

When the user searches for a candy in your app, you’ll be searching the name property using the user’s query string. You’ll see how the category string will become important near the end of this UISearchController tutorial when you implement the Scope Bar.

Populating the Table View

Open MasterViewController.swift. The candies property will be where you manage all the different Candy objects for your users to search. Speaking of which, it’s time to create some candy!

In this UISearchController tutorial, you only need to create a limited number of values to illustrate how the search bar works; in a production app, you might have thousands of these searchable objects. But whether an app has thousands of objects to search or just a few, the methods used will remain the same. Scalability at its finest!

To populate your candies array, add the following code to viewDidLoad(), after the call to super.viewDidLoad():

candies = [
  Candy(category:"Chocolate", name:"Chocolate Bar"),
  Candy(category:"Chocolate", name:"Chocolate Chip"),
  Candy(category:"Chocolate", name:"Dark Chocolate"),
  Candy(category:"Hard", name:"Lollipop"),
  Candy(category:"Hard", name:"Candy Cane"),
  Candy(category:"Hard", name:"Jaw Breaker"),
  Candy(category:"Other", name:"Caramel"),
  Candy(category:"Other", name:"Sour Chew"),
  Candy(category:"Other", name:"Gummi Bear"),
  Candy(category:"Other", name:"Candy Floss"),
  Candy(category:"Chocolate", name:"Chocolate Coin"),
  Candy(category:"Chocolate", name:"Chocolate Egg"),
  Candy(category:"Other", name:"Jelly Beans"),
  Candy(category:"Other", name:"Liquorice"),
  Candy(category:"Hard", name:"Toffee Apple")
]

Build and run your project. Since the table view’s delegate and dataSource methods have already been implemented, you’ll see that you now have a working table view:

Selecting a row in the table will also display a detail view of the corresponding candy:

So much candy, so little time to find what you want! You need a UISearchBar.

Introducing UISearchController

If you look at the UISearchController documentation, you’ll discover it’s pretty lazy. It doesn’t do any of the work of searching at all. The class simply provides a standard interface that users have come to expect from their iOS apps.

UISearchController communicates with a delegate protocol to let the rest of your app know what the user is doing. You have to write all of the actual functionality for string matching yourself.

Although this may seem a tad scary at first, writing custom search functions gives you tight control over how results are returned specifically in your app. Your users will appreciate searches that are intelligent — and fast.

If you’ve worked with searching table views iOS in the past, you may be familiar with UISearchDisplayController. Since iOS 8, this class has been deprecated in favor of UISearchController, which simplifies the entire search process.

Unfortunately, Interface Builder does not support UISearchController at the time of this writing, so you’ll have to create your UI programmatically.

In MasterViewController.swift, add a new property under the candies array declaration:

let searchController = UISearchController(searchResultsController: nil)

By initializing UISearchController with a nil value for the searchResultsController, you tell the search controller that you want use the same view you’re searching to display the results. If you specify a different view controller here, that will be used to display the results instead.

To allow MasterViewController to respond to the search bar, it will have to implement UISearchResultsUpdating. This protocol defines methods to update search results based on information the user enters into the search bar.

Still in MasterViewController.swift, add the following class extension, outside of the main MasterViewController class:

extension MasterViewController: UISearchResultsUpdating {
  // MARK: - UISearchResultsUpdating Delegate
  func updateSearchResults(for searchController: UISearchController) {
    // TODO
  }
}

updateSearchResults(for:) is the one and only method that your class must implement to conform to the UISearchResultsUpdating protocol. You’ll fill in the details shortly.

Next, you’ll need to set up a few parameters for your searchController. Still in MasterViewController.swift, add the following to viewDidLoad(), after the call to super.viewDidLoad():

  // Setup the Search Controller
  searchController.searchResultsUpdater = self
  searchController.obscuresBackgroundDuringPresentation = false
  searchController.searchBar.placeholder = "Search Candies"
  navigationItem.searchController = searchController
  definesPresentationContext = true

Here’s a rundown of what you just added:

  1. searchResultsUpdater is a property on UISearchController that conforms to the new protocol UISearchResultsUpdating. This protocol allows your class to be informed as text changes within the UISearchBar.
  2. By default, UISearchController will obscure the view it is presented over. This is useful if you are using another view controller for searchResultsController. In this instance, you have set the current view to show the results, so you do not want to obscure your view.
  3. Set the placeholder to something specific to this app
  4. New for iOS 11, you add the searchBar to the NavigationItem. This is necessary as Interface Builder is not yet compatible with UISearchController.
  5. Finally, by setting definesPresentationContext on your view controller to true, you ensure that the search bar does not remain on the screen if the user navigates to another view controller while the UISearchController is active.

UISearchResultsUpdating and Filtering

After setting up the search controller, you’ll need to do some coding to get it working. First, add the following property near the top of MasterViewController:

var filteredCandies = [Candy]()

This property will hold the candies that the user is searching for.

Next, add the following helper methods to the main MasterViewController class:

// MARK: - Private instance methods
  
func searchBarIsEmpty() -> Bool {
  // Returns true if the text is empty or nil
  return searchController.searchBar.text?.isEmpty ?? true
}
  
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
  filteredCandies = candies.filter({( candy : Candy) -> Bool in
    return candy.name.lowercased().contains(searchText.lowercased())
  })

  tableView.reloadData()
}

searchBarIsEmpty() is fairly self-explanatory. filterContentForSearchText(_:scope:) filters the candies array based on searchText and will put the results in the filteredCandies array you just added. Don’t worry about the scope parameter for now; you’ll use that in a later section of this tutorial.

filter() takes a closure of type (candy: Candy) -> Bool. It then loops over all the elements of the array, and calls the closure, passing in the current element, for every one of the elements.

You can use this to determine whether a candy should be part of the search results presented to the user. To do so, you need to return true if the current candy is to be included in the filtered array, or false otherwise.

To determine this, you use contains(_:) to see if the name of the candy contains searchText. But before doing the comparison, you convert both strings to their lowercase equivalents using the lowercased() method.

Note: Most of the time, users don’t bother with the case of letters when performing a search so by only comparing the lowercase version of what they type with the lowercase version of the name of each candy you can easily return a case-insensitive match. Now, you can type “Chocolate” or “chocolate”, and either will return a matching candy. How useful is that?! :]

Remember UISearchResultsUpdating? You left a TODO in updateSearchResults(for:). Well, you’ve now just written a method that should be called when we want to update the search results. Voilà!

Replace the TODO in updateSearchResults(for:) with a call to filterContentForSearchText(_:scope:):

filterContentForSearchText(searchController.searchBar.text!)

Now, whenever the user adds or removes text in the search bar, the UISearchController will inform the MasterViewController class of the change via a call to updateSearchResults(for:), which in turn calls filterContentForSearchText(_:scope:).

Build and run the app now, scroll down and you’ll notice that there is now a Search Bar above the table.

However, if you enter some search text you still don’t see any filtered results. What gives? This is simply because you haven’t yet written the code to let the table view know when to use the filtered results.

Updating the Table View

Back in MasterViewController.swift, add a method to determine if you are currently filtering results or not:

func isFiltering() -> Bool {
  return searchController.isActive && !searchBarIsEmpty()
}

Next, replace tableView(_:numberOfRowsInSection:) with the following:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  if isFiltering() {
    return filteredCandies.count
  }
    
  return candies.count
}

Not much has changed here; you simply check whether the user is searching or not, and use either the filtered or normal candies as a data source for the table.

Next, replace tableView(_:cellForRowAt:) with the following:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
  let candy: Candy
  if isFiltering() {
    candy = filteredCandies[indexPath.row]
  } else {
    candy = candies[indexPath.row]
  }
  cell.textLabel!.text = candy.name
  cell.detailTextLabel!.text = candy.category
  return cell
}

Both methods now use isFiltering(), which refers to the active property of searchController to determine which array to display.

When the user clicks in the search field of the Search Bar, active will automatically be set to true. If the search controller is active, and the user has actually typed something into the search field, the data returned is taken from the filteredCandies array. Otherwise, the data comes from the full list of items.

Recall that the search controller automatically handles showing and hiding the results table, so all your code has to do is provide the correct data (filtered or non-filtered) depending on the state of the controller and whether the user has searched for anything.

Build and run the app. You’ve got a functioning Search Bar that filters the rows of the main table. Huzzah!

Play with the app for a bit to see how you can search for various candies. There’s still one more problem. When you select a row from the search results list, you may notice that the detail view shown is of the wrong candy! Time to fix that.

Sending Data to a Detail View

When sending information to a detail view controller, you need to ensure that the view controller knows which context the user is working with: the full table list, or the search results.

Still in MasterViewController.swift, in prepare(for:sender:), find the following code:

let candy = candies[indexPath.row]

Then replace it with the following:

let candy: Candy
if isFiltering() {
  candy = filteredCandies[indexPath.row]
} else {
  candy = candies[indexPath.row]
}

Here you performed the same isFiltering() check as before, but now you’re providing the proper candy object when performing a segue to the detail view controller.

Build and run the code at this point and see how the app now navigates correctly to the Detail View from either the main table or the search table with ease.

Creating a Scope Bar to Filter Results

If you wish to give your users another way to filter their results, you can add a Scope Bar in conjunction with your search bar in order to filter items by category. The categories you will filter on are the ones you assigned to the Candy object when you created the candies array: Chocolate, Hard, and Other.

First, you have to create a scope bar in MasterViewController. The scope bar is a segmented control that narrows down a search by only searching in certain scopes. A scope is really what you define it as. In this case it’s a candy’s category, but scopes could also be types, ranges, or something completely different.

Using the scope bar is as easy as implementing one additional delegate method.

In MasterViewController.swift, you’ll need to add another extension that conforms to UISearchBarDelegate. After the UISearchResultsUpdating extension which you added earlier, add the following:

extension MasterViewController: UISearchBarDelegate {
  // MARK: - UISearchBar Delegate
  func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
    filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
  }
}

This delegate method is called when the user switches the scope in the scope bar. When that happens, you want to redo the filtering, so you call filterContentForSearchText(_:scope:) with the new scope.

Now modify filterContentForSearchText(_:scope:) to take the supplied scope into account:

func filterContentForSearchText(_ searchText: String, scope: String = "All") {
  filteredCandies = candies.filter({( candy : Candy) -> Bool in
    let doesCategoryMatch = (scope == "All") || (candy.category == scope)
      
    if searchBarIsEmpty() {
      return doesCategoryMatch
    } else {
      return doesCategoryMatch && candy.name.lowercased().contains(searchText.lowercased())
    }
  })
  tableView.reloadData()
}

This now checks to see if the category of the candy matches the category passed in via the scope bar (or if the scope is set to “All”). You then check to see if there is text in the search bar, and filter the candy appropriately. You also need to update isFiltering() to return true when the scope bar is selected.

func isFiltering() -> Bool {
  let searchBarScopeIsFiltering = searchController.searchBar.selectedScopeButtonIndex != 0
  return searchController.isActive && (!searchBarIsEmpty() || searchBarScopeIsFiltering)
}

You’re almost there, but the scope filtering mechanism doesn’t quite work yet. You’ll need to modify updateSearchResults(for:) in the first class extension you created to send the currently selected scope:

func updateSearchResults(for searchController: UISearchController) {
  let searchBar = searchController.searchBar
  let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
  filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}

The only problem left is that the user doesn’t actually see a scope bar yet! To add the scope bar, move to the spot in the code just after the search controller setup. In viewDidLoad() within MasterViewController.swift, add the following code just before the assignment to candies:

// Setup the Scope Bar
searchController.searchBar.scopeButtonTitles = ["All", "Chocolate", "Hard", "Other"]
searchController.searchBar.delegate = self

This will add a scope bar to the search bar, with the titles that match the categories you assigned to your candy objects. You also include a catch-all category called “All” that you will use to ignore the candy category in the search altogether.

Now, when you type, the selected scope button will be used in conjunction with the search text.

Build and run your app. Try entering some search text, and changing the scope.

Type in “caramel” with the scope set to All. It shows up in the list, but when you change the scope to Chocolate, “caramel” disappears because it’s not a chocolate. Hurrah!

There is still one small problem with the app. We don’t indicate to the user how many results they should expect to see. This is particularly important when there are no results returned at all, as it can be difficult for the user to distinguish between something such as no results returned and a slow network connection fetching the answer.

Adding a Results Indicator

To fix this, we’re going to add a footer to our view. This will be visible when filtering is applied to our list of candies and will tell the user how many candies are in the filtered array. Open SearchFooter.swift. Here you have a simple UIView which contains a label and has a public API to be notified about the number of results returned.

Head back to MasterViewController.swift. At the top of the class you already set up an IBOutlet for the search footer, which can be seen in the master scene in Main.storyboard at the bottom of the screen. Add the following to viewDidLoad(), after the spot where you set up the scope bar:

// Setup the search footer
tableView.tableFooterView = searchFooter

This sets your custom search footer view as the table view’s footer view. Next, you need to update it on the number of results when the search input changes. Replace tableView(_:numberOfRowsInSection:) with the following:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  if isFiltering() {
    searchFooter.setIsFilteringToShow(filteredItemCount: filteredCandies.count, of: candies.count)
    return filteredCandies.count
  }
    
  searchFooter.setNotFiltering()
  return candies.count
}

All you’ve done here is add in calls to the searchFooter.

Build and run the application, perform a few searches and watch the footer update as the searches return. You’ll need to hit the search button to dismiss the keyboard and see the search footer.

UISearchController Tutorial

HealthKit Tutorial with Swift: Workouts

Welcome back to our HealthKit tutorial series!

In the first part of the series, you learned the basics of working with HealthKit: reading and writing data.

In this second and final part of the series, you will learn how to work with a more complex type of data: workouts.

This project picks up where the previous HealthKit tutorial left off. If you don’t have the project already, you can
download it here.

Get ready to take another rep in your HealthKit workout! :]

Getting Started

In your day to day life, a workout is a really simple thing. It’s some period of time where you increase physical exertion doing some sort of activity.

Most workouts have one more of the following attributes:

  • Activity type (running, cycling, prancercising, etc.)
  • Distance
  • Start and end time
  • Duration
  • Energy burned

HealthKit thinks of workouts in much the same way. A workout is a container for these types of information, taken as a collection of samples. A given workout might contain heart rate samples, distance samples, and an activity type to categorize them.

Continuing from the previous article, you are going to track a special kind of workout: Prancercise.

The starter project already contains a view controller that gives you a place to track your Prancercise workouts. To see it, navigate to Workouts and then tap the + button.

This view contains a button that starts a Prancercise workout. If you tap the button once, the app will start to track your Prancercise session, showing you the start date and duration.

If you tap the button a second time, the current Prancercise session will stop. You can tap done to record the workout, or tap the New Prancercise button to start a new workout session (be aware this erases the old one).

Saving Workouts

For the moment, the app doesn’t actually do anything when you tap Done to save your workout. You are going to change that.

First, a little bit of context. Open Workout.swift and take a look around. You should see a struct named PrancerciseWorkout.

struct PrancerciseWorkout {
  
  var start: Date
  var end: Date
  
  init(start: Date, end: Date) {
    self.start = start
    self.end = end
  }
  
  var duration: TimeInterval {
    return end.timeIntervalSince(start)
  }
  
  var totalEnergyBurned: Double {
    
    let prancerciseCaloriesPerHour: Double = 450
    
    let hours: Double = duration/3600
    
    let totalCalories = prancerciseCaloriesPerHour*hours
    
    return totalCalories
  }
}

A PrancerciseWorkout is a model the app uses to store information related to a workout. It gets created every time you tap the done button after finishing your Prancercise session.

Each PrancerciseWorkout object keeps track of its:

  1. Start and end time
  2. Duration
  3. Total calories burned

These values are then fed into HealthKit when your workout is saved.

Note: we are assuming a somewhat intense Prancercise pace with aggressive ankle weights and loud fist pumping musical accompaniment. Hence the workout burns 450 Calories per hour. Eat it, Zumba!

Now that you understand what is stored in a PrancerciseWorkout object, let’s save one.

Open WorkoutDataStore.swift and take a look at the save(prancerciseWorkout:completion:) method. This is what you will use to save your Prancercise workout to HealthKit.

Paste the following lines of code into that method:

//1. Setup the Calorie Quantity for total energy burned
let calorieQuantity = HKQuantity(unit: HKUnit.kilocalorie(),
                                 doubleValue: prancerciseWorkout.totalEnergyBurned)
    
//2. Build the workout using data from your Prancercise workout
let workout = HKWorkout(activityType: .other,
                        start: prancerciseWorkout.start,
                        end: prancerciseWorkout.end,
                        duration: prancerciseWorkout.duration,
                        totalEnergyBurned: calorieQuantity,
                        totalDistance: nil,
                        device: HKDevice.local(),
                        metadata: nil)
    
//3. Save your workout to HealthKit
let healthStore = HKHealthStore()

healthStore.save(workout) { (success, error) in
  completion(success, error)
}

You probably remember HKQuantity from the earlier HealthKit tutorial. You used it to read and write your user’s height, weight, and body mass index.

HealthKit interacts with HKWorkout in similar fashion. In this case, you can see that your HKWorkout tracks the start time, end time, duration, and total energy burned. All of these attributes get taken from the PrancerciseWorkout entity that gets passed in.

Quite fittingly, a Prancercise Workout is an activity that can only be categorized as Other, but you can pick from any number of supported activity types if you like :].

You may have also noticed that you can tell HealthKit which device the workout was recorded on. This can be useful when querying data later.

The rest of this code is fairly self-explanatory. Just like you did in the last HealthKit tutorial, you use HKHealthStore to save workouts. Once finished, you call the completion handler.

Querying Workouts

Now you can save a workout, but you also need a way to load workouts from HealthKit. Let’s add a new method to WorkoutDataStore to do that.

Paste the following method after the save(prancerciseWorkout:completion:) method in WorkoutDataStore:

class func loadPrancerciseWorkouts(completion: @escaping (([HKWorkout]?, Error?) -> Swift.Void)){
    
  //1. Get all workouts with the "Other" activity type.
  let workoutPredicate = HKQuery.predicateForWorkouts(with: .other)
    
  //2. Get all workouts that only came from this app.
  let sourcePredicate = HKQuery.predicateForObjects(from: HKSource.default())

  //3. Combine the predicates into a single predicate.
  let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [workoutPredicate,
                                                                     sourcePredicate])
    
  let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate,
                                        ascending: true)
        
  let query = HKSampleQuery(sampleType: HKObjectType.workoutType(),
                            predicate: compound,
                            limit: 0,
                            sortDescriptors: [sortDescriptor]) { (query, samples, error) in

    DispatchQueue.main.async {
        
      //4. Cast the samples as HKWorkout
      guard let samples = samples as? [HKWorkout],
            error == nil else {
          completion(nil, error)
          return
      }
        
      completion(samples, nil)
    }
  }
    
  HKHealthStore().execute(query)
}

If you followed the previous HealthKit tutorial, much of this code will look familiar. The predicates determine what types of HeathKit data you are looking for, and the sort descriptor tells HeathKit how to sort the samples you get back.

  1. HKQuery.predicateForWorkouts(with:) is a special method that gives you a predicate for workouts with a certain activity type. In this case, you are loading any type of workout where the activity type is Other (all Prancercise workouts use the Other activity type).
  2. HKSource denotes the application that provided the workout data to HealthKit. Whenever you call HKSource.default(), you are basically saying “this app.” The sourcePredicate gets all workouts where the source is, you guessed it, this app.
  3. Those of you with Core Data experience may also be familiar with NSCompoundPredicate. It provides a way to bring one or more filters together. The final result is a query that gets you all workouts with Other as the activity type and Prancercise Tracker as the source app.
  4. In the completion handler, the samples get unwrapped as an array of HKWorkout objects. That’s because HKSampleQuery returns an array of HKSample by default, and you need to cast them to HKWorkout to get all of the useful properties like start time, end time, duration, and energy burned.

Loading Workouts Into The User Interface

You just wrote a method that loads workouts from HealthKit. Now it’s time to take those workouts and use them to populate a table view. Thankfully, I’ve done all the setup for you!

Open WorkoutsTableViewController.swift and take a look around. You will see a few things.

  1. There is an optional array called workouts for storing workouts. Those are what you will load using the loadPrancerciseWorkouts(completion:) method.
  2. There is a method named reloadWorkouts(). It gets called whenever the view for this screen appears. Every time you navigate to this screen the workouts get refreshed.

To populate the user interface with data, you just need to load the workouts and hook up the UITableView’s datasource.

Paste the following lines of code into the reloadWorkouts() method in WorkoutsTableViewController.swift:

WorkoutDataStore.loadPrancerciseWorkouts { (workouts, error) in
  self.workouts = workouts
  self.tableView.reloadData()
}

This code loads workouts using the method you just wrote. When the workouts are loaded, it assigns them to the local workouts property on WorkoutsTableViewController. Then it reloads the table view with the new data.

At this point you may have noticed there is still no way to get the data from the workouts to the UITableView. We need to implement the table view’s datasource.

Paste these lines of code beneath the reloadWorkouts() method:

//MARK: UITableView DataSource
override func numberOfSections(in tableView: UITableView) -> Int {
  return 1
}
  
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
  guard let workouts = workouts else {
    return 0
  }
    
  return workouts.count
}

This just says you want one section in the table view, and you want the rows to correspond to the number of workouts you have loaded from HealthKit. Also, if you haven’t loaded any workouts from HealthKit, no rows are shown and the table view will appear empty.

Note: You might be used to seeing these methods without the override keyword in front of them. The reason you need to use override here is because WorkoutsTableViewController is a subclass of UITableViewController.

UITableViewController already implements all of the functions associated with UITableViewDatasource. To get custom behavior, you need to override those default implementations.

Now let’s tell the cells what to display. Paste this method after the two datasource methods:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
  guard let workouts = workouts else {
    fatalError("CellForRowAtIndexPath should never get called if there are no workouts")
  }
    
  //1. Get a cell to display the workout in.
  let cell = tableView.dequeueReusableCell(withIdentifier: prancerciseWorkoutCellID,
                                           for: indexPath)
  
  //2. Get the workout corresponding to this row.
  let workout = workouts[indexPath.row]
  
  //3. Show the workout's start date in the label. 
  cell.textLabel?.text = dateFormatter.string(from: workout.startDate)
    
  //4. Show the Calorie burn in the lower label.
  if let caloriesBurned = workout.totalEnergyBurned?.doubleValue(for: HKUnit.kilocalorie()) {
    let formattedCalories = String(format: "CaloriesBurned: %.2f", caloriesBurned)
    cell.detailTextLabel?.text = formattedCalories
  } else {
    cell.detailTextLabel?.text = nil
  }
    
  return cell
}

Alright! This is where the magic happens. You dequeue a cell from the table view, and then you populate it with data related to a workout.

Most of this should be familiar from the previous HealthKit tutorial. The only new thing is the unit conversion for Calories burned.

If a workout has its total energy burned set to something, then it gets converted to a double using Kilocalories as the conversion. The string gets formatted and shows up in the cell’s detail label.

Build and run the app. Go to Prancercise Workouts, tap the + button, track a short Prancercise workout, tap Done, and take a look at the table view.

It’s a short workout, but boy can I feel the burn. This new workout routine gives Crossfit a run for its money.

Adding Samples to Workouts

Up to this point, we have assumed that a Prancercise workout is composed of a single workout session. But if you are like me, and you find Prancercise a little too intense, you might want to break it into a few short intervals.

With samples, you can record multiple exercise intervals under the same workout. It’s a way to give HealthKit a more detailed view of what you did during your workout routine.

You can add all kinds of samples to a workout. If you want, you can add distance, calories burned, heart rate, and more.

Because Prancercise is more of a dance routine, this HealthKit tutorial will focus on calorie burn samples.

Modifying The Model

This is a totally new way of thinking about Prancercise workouts, and that means it is time to change the model.

Instead of using a single Prancercise workout model, there should be some concept of a workout interval representing a short session. That way, a single PrancerciseWorkout becomes a wrapper or container for the workout intervals that store the starts and stops you took during your routine.

Open Workout.swift. Rename PrancerciseWorkout to PrancerciseWorkoutInterval so it looks like this:

struct PrancerciseWorkoutInterval {
  
  var start: Date
  var end: Date
  
  var duration: TimeInterval {
    return end.timeIntervalSince(start)
  }
  
  var totalEnergyBurned: Double {
    
    let prancerciseCaloriesPerHour: Double = 450
    
    let hours: Double = duration/3600
    
    let totalCalories = prancerciseCaloriesPerHour*hours
    
    return totalCalories
  }
  
}

You can see that nothing else has changed. What was once an entire workout is now a piece of a workout.

Paste the following code beneath the struct declaration for PrancerciseWorkoutInterval:

struct PrancerciseWorkout {
  
  var start: Date
  var end: Date
  
  var intervals: [PrancerciseWorkoutInterval]
  
  init(with intervals: [PrancerciseWorkoutInterval]) {
    self.start = intervals.first!.start
    self.end = intervals.last!.end
    self.intervals = intervals
  }
}

Now a full PrancerciseWorkout is composed of an array of PrancerciseWorkoutInterval objects. The workout starts when the first item in the array starts, and it ends when the last item in the array ends.

This is a nice way of representing a workout as something composed of time intervals, but it’s still missing a concept of duration and total energy burned. The code won’t compile until you have defined those.

Functional programming comes to the rescue here. You can use the reduce method to sum up the duration and total energy burned properties on PrancerciseWorkoutInterval.

Paste the following computed properties below the init(with:) constructor in PrancerciseWorkout:

var totalEnergyBurned: Double {
  return intervals.reduce(0, { (result, interval) -> Double in
    return result+interval.totalEnergyBurned
  })
}
  
var duration: TimeInterval {
  return intervals.reduce(0, { (result, interval) -> TimeInterval in
    return result+interval.duration
  })
}

Reduce takes a starting value (in this case zero) and a closure that takes in the result of the previous computation. It does this for each item in the array.

To calculate the total energy burned, reduce starts at zero and adds zero to the first energy burn value in the array. Then it takes the result and adds it to the next value in the array, and so on. Once it has hit the end of the array, you get a sum total of all energy burned throughout your workout.

Reduce is a handy function for neatly summing up arrays. If you would like to read more about functional programming and its awesomeness, check out this article.

Workout Sessions

You are almost finished upgrading the models. Open WorkoutSession.swift and take a look.

WorkoutSession is used to store data related to the current PrancerciseWorkout being tracked. Since you just added in this concept of workout intervals to PrancerciseWorkout, WorkoutSession needs to add new intervals each time you start and end a Prancercise session.

Inside of the WorkoutSession class, locate the line just below the state variable. It looks like this:

var state: WorkoutSessionState = .notStarted

Add a new line declaring an array of PrancerciseWorkoutInterval objects:

var intervals = [PrancerciseWorkoutInterval]()

When you finish a Prancercise session, a new interval will get added to this array. Let’s add a function to do that.

Paste the following method below the clear() method in WorkoutSession:

private func addNewInterval() {
  let interval = PrancerciseWorkoutInterval(start: startDate,
                                            end: endDate)
  intervals.append(interval)
}

This method takes the start and end dates from the workout session, and it creates a PrancerciseWorkoutInterval from them. Note that the start and end dates get reset every time a Prancercise session begins and ends.

You now have a way to add a PrancericseWorkoutInterval. You just need to use it.

Replace the end() method in WorkoutSession with this:

func end() {
  endDate = Date()
  addNewInterval()
  state = .finished
}

You can see that right after you set the end date for the session, you add a new interval to the array of PrancerciseWorkoutInterval objects.

It is also important to clear out the array whenever the workout session needs to get cleared.

Locate the end of the clear() method and add this line:

intervals.removeAll()

removeAll() does exactly what it says :].

There’s just one more modification. The completeWorkout property needs to use the intervals to create a new PrancerciseWorkout object.

Replace the completeWorkout variable with this:

var completeWorkout: PrancerciseWorkout? {    
  get {
      
    guard state == .finished,
          intervals.count > 0 else {
          return nil
    }
      
    return PrancerciseWorkout(with: intervals)
  }
}

There you have it. Since this property is optional, you only want it to return a full PrancerciseWorkout when the WorkoutSession is finished and you have recorded at least one interval.

If you have followed along, your WorkoutSession class should look like this:

class WorkoutSession {
  
  private (set) var startDate: Date!
  private (set) var endDate: Date!
  
  var state: WorkoutSessionState = .notStarted
  
  var intervals = [PrancerciseWorkoutInterval]()
  
  func start() {
    startDate = Date()
    state = .active
  }
  
  func end() {
    endDate = Date()
    addNewInterval()
    state = .finished
  }
  
  func clear() {
    startDate = nil
    endDate = nil
    state = .notStarted
    intervals.removeAll()
  }
  
  private func addNewInterval() {
    let interval = PrancerciseWorkoutInterval(start: startDate,
                                              end: endDate)
    intervals.append(interval)
  }
  
  var completeWorkout: PrancerciseWorkout? {
    
    get {
      
      guard state == .finished,
            intervals.count > 0 else {
            return nil
      }
      
      return PrancerciseWorkout(with: intervals)
    }
  }
}

You can see that every time the stop() method gets called, a new PrancerciseWorkoutInterval with the current start and stop dates gets added to the list. Once the user taps the Done button to save the workout, this code generates a full-blown PrancerciseWorkout entity using the intervals recorded during the multiple sessions.

There’s no need to change the code in CreateWorkoutViewController. The button actions call the same start(), end(), and clear() methods. The only difference is that instead of working with a single time interval, WorkoutSession generates and stores multiple.

Adding Samples While Saving A Workout

If you were to build and run the app right now, it would still record accurate Prancercise workouts to HealthKit. There just wouldn’t be any samples attached. You need a way to convert the PrancerciseWorkoutInterval objects into samples.

Open WorkoutDataStore.swift and paste this new method right after the save(prancerciseWorkout:completion:) method:

private class func samples(for workout: PrancerciseWorkout) -> [HKSample] {
    
  var samples = [HKSample]()
    
  //1. Verify that the energy quantity type is still available to HealthKit.
  guard let energyQuantityType = HKSampleType
                                 .quantityType(forIdentifier:HKQuantityTypeIdentifier
                                                            .activeEnergyBurned) else {
    fatalError("*** Energy Burned Type Not Available ***")
  }
    
  //2. Create a sample for each PrancerciseWorkoutInterval
  for interval in workout.intervals {
      
    let calorieQuantity = HKQuantity(unit: HKUnit.kilocalorie(),
                                     doubleValue: interval.totalEnergyBurned)
      
    let sample = HKQuantitySample(type: energyQuantityType,
                                  quantity: calorieQuantity,
                                  start: interval.start,
                                  end: interval.end)
      
    samples.append(sample)
  }
    
  return samples
}

Hey you’ve seen this before! It’s the same thing you did when submitting a body mass index sample in the previous HealthKit tutorial. You’re just doing it inside of a loop, creating a sample for each PrancerciseWorkoutInterval associated with your PrancerciseWorkout.

Now you just need to make a few adjustments to the save(prancerciseWorkout:completion:) method to attach the samples to your workout.

Find the lines below the line where you declare:

let healthStore = HKHealthStore()

And replace them with this:

let samples = self.samples(for: prancerciseWorkout)

healthStore.save(workout) { (success, error) in
      
  guard error == nil else {
    completion(false, error)
    return
  }
      
  healthStore.add(samples,
                  to: workout,
                  completion: { (samples, error) in
         
      guard error == nil else {
        completion(false, error)
        return
      }
         
      completion(true, nil)
  })
      
}

This code prepares a list of samples using your Prancercise workout. Then it attempts to save the workout to HealthKit just as you’ve done before. Once the workout has been successfully saved, it adds the samples.

Viewing Workout Samples In The Health App

Build and run the app. Tap on Prancerise Workouts. Then tap the + button to track a new Prancercise workout. Record a few Prancercise sessions and tap Done to save them to HealthKit as a single Prancercise workout.

You aren’t going to see anything new in the Prancercise Tracker’s user interface, but trust me there’s loads of data in your Health app to peruse.

Open the Health app. Tap on Activity. You should see a breakdown of your workouts for the day.

I’ve recorded a few very short Prancercise sessions, so Activity says I have spent one minute exercising. That’s fine. We have already established the relative intensity of the Prancercise regimen, so it ought to be enough physical exertion for a day :].

Tap Workouts. The next screen will give you a breakdown of your workouts for the day. In your case, you want to see where all of those data points came from.

Tap Show All Data. This will take you to a screen that displays all of your workouts for the day, along with their source app.

Neat. The RW Logo clearly shows that the workouts came from Prancercise Tracker.

Tap on a workout to view its details, scroll down to the Workout Samples section, and then tap on the cell displaying the total active energy.

At this point, you should see a list of active energy samples associated with the Prancercise workout you just tracked.

Tap on a sample, and you can see when your short Prancercise session started and finished.

Awesome. You’ve just built an app that not only tracks a workout but also tracks interval training within that workout.

HealthKit Tutorial with Swift: Workouts

HealthKit Tutorial With Swift

HealthKit is an API that was introduced in iOS 8. It acts as a central repository for all health-related data, letting users build a biological profile and store workouts.

In this HealthKit tutorial, you will create a simple workout tracking app and learn:

  • How to request permission and access HealthKit data
  • How to read HealthKit data and display it in a UITableView
  • How to write data to HealthKit’s central repository

Ready to start this HealthKit Exercise? Read on!

Note: To work through this HealthKit tutorial, you’ll need an active iOS developer account. Without one, you won’t be able to enable the HealthKit Capability and access the HealthKit Store.

Getting Started

Download the starter project and open it in Xcode.

Build and run the app. You will see a skeleton of the user interface. Throughout the course of these next two articles, you will slowly add in the functionality.

Assigning a Team

HealthKit is a special framework. Your app can’t use it unless you have an active developer account. Once you have a developer account, you can assign your team.

Select PrancerciseTracker in the Project Navigator, and then select the PrancerciseTracker target. Select the General tab and click on the Team combo box.

Select the team associated with your developer account:

That was a piece of cake, right? Oops. I meant to say that was a low-calorie potato and red lentil soup stewed in a vegetable broth :].

Entitlements

HealthKit also has its own set of entitlements, and you will need to enable them in order to build apps that use the framework.

Open the Capabilities tab in the target editor, and turn on the HealthKit switch, as shown in the screenshot below:

Wait for Xcode to configure HealthKit for you. There usually isn’t a problem here, but you might run into some snags if you forgot to setup your Team and Bundle Identifier like you did in the previous section.

Done and done. Now you just need to ask the user for permission to use HealthKit.

Permissions

HealthKit deals with sensitive and private data. Not everyone feels so comfortable letting their apps access this information.

That’s why HealthKit has a robust privacy system. HealthKit only has access to the kinds of data your users agree to share with it. To build up a health profile for your Prancercise Tracker’s users, you need to be nice and ask for permission to access each type of data first.

Updating the Share Usage Descriptions

First, you need to describe why you are asking for health metrics from your users. Xcode gives you a way to specify this in your application’s Info.plist file.

Open Info.plist. Then add the following keys:
Privacy – Health Share Usage Description
Privacy – Health Update Usage Description

Both keys store text that display when the HeathKit authorization screen appears. The Health Share Usage Description goes under the section for data to be read from HealthKit. The Health Update Usage Description corresponds to data that gets written to HealthKit.

You can put anything you want in there. Typically it’s some explanation saying, “We will use your health information to better track Prancercise workouts.”

Do be aware that if those keys aren’t set, your app will crash when attempting to authorize HealthKit.

Authorizing HealthKit

Open HealthKitSetupAssistant.swift and take a peek inside. You will find an empty class with an error type and the body of a method you will use to authorize HealthKit.

class func authorizeHealthKit(completion: @escaping (Bool, Error?) -> Swift.Void) {
    
}

the authorizeHealthKit(completion:) method accepts no parameters, and it has a completion handler that returns a boolean (success or failure) and an optional error in case something goes wrong. That’s what the two possible errors are for. You will pass them into the completion handler under two special circumstances.

Solution Inside: What might go wrong when trying to authorize HealthKit? Hide
1. HealthKit might not be available on the device. This happens with iPads.
2. Some data types might not be available in the current version of HealthKit.

Let’s break this process down. To authorize HealthKit, the authorizeHealthKit(completion:) method will need to do these four things:

  1. Check to see if Healthkit is available on this device. If it isn’t, complete with failure and an error.
  2. Prepare the types of health data Prancercise Tracker will read and write to HealthKit.
  3. Organize those data into a list of types to be read and types to be written.
  4. Request Authorization. If it’s successful, complete with success.

Checking HealthKit Availability

First things first. You are going to check if HealthKit is available on the device.

Paste the following bit of code at the top of the authorizeHealthKit(completion:) method:

//1. Check to see if HealthKit Is Available on this device
guard HKHealthStore.isHealthDataAvailable() else {
  completion(false, HealthkitSetupError.notAvailableOnDevice)
  return
}

You are going to interact with HKHealthStore quite a lot. It represents the central repository that stores a user’s health-related data. HKHealthStore’s isHealthDataAvailable() method helps you figure out if the user’s current device supports HeathKit data.

The guard statement stops the app from executing the rest of the authorizeHealthKit(completion:) method’s logic if HealthKit isn’t available on the device. When this happens, the method completes with the notAvailableOnDevice error. Your view controller can do something with that, or you can just log it to the console.

Preparing Data Types

Once you know HealthKit is available on your user’s device, it is time to prepare the types of data that will get read from and written to HealthKit.

HealthKit works with a type called HKObjectType. Every type that goes into and out HealthKit’s central repository is some kind of HKObjectType. You will also see HKSampleType and HKWorkoutType. Both inherit from HKObjectType, so they’re basically the same thing.

Paste this next piece of code right after the first piece of code:

//2. Prepare the data types that will interact with HealthKit
guard   let dateOfBirth = HKObjectType.characteristicType(forIdentifier: .dateOfBirth),
        let bloodType = HKObjectType.characteristicType(forIdentifier: .bloodType),
        let biologicalSex = HKObjectType.characteristicType(forIdentifier: .biologicalSex),
        let bodyMassIndex = HKObjectType.quantityType(forIdentifier: .bodyMassIndex),
        let height = HKObjectType.quantityType(forIdentifier: .height),
        let bodyMass = HKObjectType.quantityType(forIdentifier: .bodyMass),
        let activeEnergy = HKObjectType.quantityType(forIdentifier: .activeEnergyBurned) else {
        
        completion(false, HealthkitSetupError.dataTypeNotAvailable)
        return
}

Wow, that’s big guard statement! It’s also an excellent example of using a single guard to unwrap multiple optionals.

In order to create an HKObjectType for a given biological characteristic or quantity, you need to use either HKObjectType.characteristicType(forIdentifier:) or HKObjectType.quantityType(forIdentifier:)

The characteristic types and the quantity types are both enums defined by the framework. HealthKit is loaded with these. There are so many different dimensions of health for your app to track that it makes my head spin whilst prancercising around the possibilities.

You will also notice that if a single characteristic or sample type is not available, the method will complete with an error. That’s intentional. Your app should always know exactly which HealthKit types it can work with, if any at all.

Preparing a list of data types to read and write

Now it’s time to prepare a list of types to read and types to write.

Paste this third bit of code into the authorizeHealthKit(completion:) method, right after the second piece:

//3. Prepare a list of types you want HealthKit to read and write
let healthKitTypesToWrite: Set<HKSampleType> = [bodyMassIndex,
                                                activeEnergy,
                                                HKObjectType.workoutType()]
    
let healthKitTypesToRead: Set<HKObjectType> = [dateOfBirth,
                                               bloodType,
                                               biologicalSex,
                                               bodyMassIndex,
                                               height,
                                               bodyMass,
                                               HKObjectType.workoutType()]

HealthKit expects a set of HKSampleType objects that represent the kinds of data your user can write, and it also expects a set of HKObjectType objects for your app to read.

HKObjectType.workoutType() is a special kind of HKObjectType. It represents any kind of workout.

Authorizing HealthKit

The final part is the easiest. You just need to request authorization from HealthKit. Paste this last piece of code after the third piece:

//4. Request Authorization
HKHealthStore().requestAuthorization(toShare: healthKitTypesToWrite,
                                     read: healthKitTypesToRead) { (success, error) in
  completion(success, error)
}

These lines of code request authorization from HealthKit and then call your completion handler. They use the success and error variables passed in from HKHealthStore’s requestAuthorization(toShare: read: completion:) method.

You can think of it as a redirect. Instead of handling the completion inside of HealthKitSetupAssistant, you are passing the buck to a view controller that can present an alert or take some other action.

The starter project already has an Authorize HealthKit button for this, and it invokes the method authorizeHealthKit() in MasterViewController. That sounds like the perfect place to call the new authorization method you just wrote.

Open MasterViewController.swift, locate authorizeHealthKit() and paste this code into the body:

HealthKitSetupAssistant.authorizeHealthKit { (authorized, error) in
      
  guard authorized else {
        
    let baseMessage = "HealthKit Authorization Failed"
        
    if let error = error {
      print("\(baseMessage). Reason: \(error.localizedDescription)")
    } else {
      print(baseMessage)
    }
        
    return
  }
      
  print("HealthKit Successfully Authorized.")
}

This code uses the authorizeHealthKit(completion:) method you just implemented. When it is finished, it prints a message to the console to let you know if HealthKit was successfully authorized.

Build and run. Tap Authorize HealthKit in the main view, and you will see an authorization screen pop up:

Turn on all the switches, scrolling the screen to see all of them, and click Allow. You’ll see a message like this in Xcode’s console:

HealthKit Successfully Authorized.

Great! Your app has access to HealthKit’s central repository. Now it’s time to start tracking things.

Characteristics and Samples

In this section, you will learn:

  • How to read your user’s biological characteristics.
  • How to read and write different types of samples (weight, height, etc.)

Biological characteristics tend to be the kinds of things that don’t change, like your blood type. Samples represent things that often do change, like your weight.

In order to properly track the effectiveness of a Prancercise workout regimen, the Prancercise Tracker app needs to get a sample of your user’s weight and height. Put together, these samples can be used to calculate Body Mass Index (BMI).

Note: Body Mass Index (BMI) is a widely used indicator of body fat, and it’s calculated from the weight and height of a person. Learn more about it here.

Reading Characteristics

The Prancercise Tracker app doesn’t write biological characteristics. It reads them from HealthKit. That means those characteristics need to be stored in HeathKit’s central repository first.

If you haven’t already done this, it’s time to tell HeathKit some more about yourself.

Open the Health App on your device or in the simulator. Select the Health Data tab. Then tap on the profile icon in the top right hand corner to view your health profile. Hit Edit, and enter information for Date of Birth, Sex, Blood Type:

Now that HealthKit knows your Date of Birth, Sex, and Blood Type, it’s time to read those characteristics into Prancercise Tracker.

Go back to Xcode and open ProfileDataStore.swift. The ProfileDataStore class represents your point of access to all of the health-related data for your users.

Paste the following method into ProfileDataStore:

class func getAgeSexAndBloodType() throws -> (age: Int, 
                                              biologicalSex: HKBiologicalSex, 
                                              bloodType: HKBloodType) {
    
  let healthKitStore = HKHealthStore()
    
  do {

    //1. This method throws an error if these data are not available.
    let birthdayComponents =  try healthKitStore.dateOfBirthComponents()
    let biologicalSex =       try healthKitStore.biologicalSex()
    let bloodType =           try healthKitStore.bloodType()
      
    //2. Use Calendar to calculate age.
    let today = Date()
    let calendar = Calendar.current
    let todayDateComponents = calendar.dateComponents([.year],
                                                        from: today)
    let thisYear = todayDateComponents.year!
    let age = thisYear - birthdayComponents.year!
     
    //3. Unwrap the wrappers to get the underlying enum values. 
    let unwrappedBiologicalSex = biologicalSex.biologicalSex
    let unwrappedBloodType = bloodType.bloodType
      
    return (age, unwrappedBiologicalSex, unwrappedBloodType)
  }
}

The getAgeSexAndBloodType() method accesses HKHealthStore, asks for the user’s date of birth, biological sex, and blood type. It also calculates the user’s age using the date of birth.

  1. You may have noticed this method can throw an error. It happens whenever the date of birth, biological sex, or blood type haven’t been saved in HealthKit’s central repository. Since you just entered this information into your Health app, no error should be thrown.
  2. Using the Calendar class, you can transform any given date into a set of Date Components. These are really handy when you want to get the year for a date. This code simply gets your birth year, the current year, and then calculates the difference.
  3. The “unwrapped” variables are named that way to make it clear that you have to access the underlying enum from a wrapper class (HKBiologicalSexObject and HKBloodTypeObject).

Updating The User Interface

If you were to build and run now, you wouldn’t see any change to the UI because you haven’t connected this logic to it yet.

Open ProfileViewController.swift and find the loadAndDisplayAgeSexAndBloodType() method.

This method will use your ProfileDataStore to load the biological characteristics into the user interface.

Paste the following lines of code into the loadAndDisplayAgeSexAndBloodType() method:

do {
  let userAgeSexAndBloodType = try ProfileDataStore.getAgeSexAndBloodType()
  userHealthProfile.age = userAgeSexAndBloodType.age
  userHealthProfile.biologicalSex = userAgeSexAndBloodType.biologicalSex
  userHealthProfile.bloodType = userAgeSexAndBloodType.bloodType
  updateLabels()
} catch let error {
  self.displayAlert(for: error)
}

This block of code loads age, sex, and blood type as a tuple. It then sets those fields on a local instance of the UserHealthProfile model. Finally, it updates the user interface with the new fields on UserHealthProfile by calling the updateLabels() method.

Because ProfileDataStore’s getAgeSexAndBloodType() method can throw an error, your ProfileViewController has to handle it. In this case, you simply take the error and present it inside of an alert with an “O.K.” button.

All of this is great, but there’s one catch. The updateLabels() method doesn’t do anything yet. It’s just an empty declaration. Let’s hook it up to the user interface for real this time.

Locate the updateLabels() method and paste these lines of code into its body:

if let age = userHealthProfile.age {
  ageLabel.text = "\(age)"
}

if let biologicalSex = userHealthProfile.biologicalSex {
  biologicalSexLabel.text = biologicalSex.stringRepresentation
}

if let bloodType = userHealthProfile.bloodType {
  bloodTypeLabel.text = bloodType.stringRepresentation
}

This code is pretty straightforward. If you user has set an age, it will get formatted an put into the label. The same goes for biological sex and bloodType. The stringRepresentation variable converts the enum to a string for display purposes.

Build and run the app. Go into the Profile & BMI screen. Tap on the Read HealthKit Data button.

If you entered your information into the Health app earlier, it should appear in the labels on this screen. If you didn’t, you will get an error message.

Cool! You’re reading and displaying data directly from HealthKit.

Querying Samples

Now it’s time to read your user’s weight and height. This will be used to calculate and display their BMI in the profile view.

Biological characteristics are easy to access because they almost never change. Samples require a much more sophisticated approach. They use HKQuery, more specifically HKSampleQuery.

To query samples from HealthKit, you will need:

  1. To specify the type of sample you want to query (weight, height, etc.)
  2. Some additional parameters to help filter and sort the data. You can pass in an optional NSPredicate or an array of NSSortDescriptors to do this.

Note: If you’re familiar with Core Data, you probably noticed some similarities. An HKSampleQuery is very similar to an NSFetchedRequest for an entity type, where you specify the predicate and sort descriptors, and then ask the Object context to execute the query to get the results.

Once your query is setup, you simply call HKHealthStore’s executeQuery() method to fetch the results.

For Prancercise Tracker, you are going to create a single generic function that loads the most recent samples of any type. That way, you can use it for both weight and height.

Open ProfileDataStore.swift and paste the following method into the class, just below the getAgeSexAndBloodType() method:

class func getMostRecentSample(for sampleType: HKSampleType,
                               completion: @escaping (HKQuantitySample?, Error?) -> Swift.Void) {
  
//1. Use HKQuery to load the most recent samples.  
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast,
                                                      end: Date(),
                                                      options: .strictEndDate)
    
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate,
                                      ascending: false)
    
let limit = 1
    
let sampleQuery = HKSampleQuery(sampleType: sampleType,
                                predicate: mostRecentPredicate,
                                limit: limit,
                                sortDescriptors: [sortDescriptor]) { (query, samples, error) in
    
    //2. Always dispatch to the main thread when complete.
    DispatchQueue.main.async {
        
      guard let samples = samples,
            let mostRecentSample = samples.first as? HKQuantitySample else {
                
            completion(nil, error)
            return
      }
        
      completion(mostRecentSample, nil)
    }
  }
 
HKHealthStore().execute(sampleQuery)
}

This method takes in a sample type (height, weight, bmi, etc.). Then it builds a query to get the most recent sample for that type. If you pass in the sample type for height, you will get back your latest height entry.

There is a lot going on here. I will pause to break down a few things.

  1. HKQuery has a number of methods that can help you filter your HealthKit sample queries. It’s worth taking a look at them. In this case, we are using the built-in date window predicate.
  2. Querying samples from HealthKit is an asynchronous process. That is why the code in the completion handler occurs inside of a Dispatch block. You want the completion handler to happen on the main thread, so the user interface can respond to it. If you don’t do this, the app will crash.

If all goes well, your query will execute and you will get a nice and tidy sample returned to the main thread where your ProfileViewController can put its contents into a label. Let’s do that part now.

Displaying Samples in the User Interface

If you recall from the earlier section, you loaded the data from HealthKit, saved it to a model in ProfileViewController, and then updated the content in the labels using ProfileViewController’s updateLabels() method.

All you need to do now is extend that process by adding a function that loads the samples, processes them for the user interface, and then calls updateLabels() to populate the labels with text.

Open ProfileViewController.swift, locate the loadAndDisplayMostRecentHeight() method, and paste the following code into the body:

//1. Use HealthKit to create the Height Sample Type
guard let heightSampleType = HKSampleType.quantityType(forIdentifier: .height) else {
  print("Height Sample Type is no longer available in HealthKit")
  return
}
    
ProfileDataStore.getMostRecentSample(for: heightSampleType) { (sample, error) in
      
  guard let sample = sample else {
      
    if let error = error {
      self.displayAlert(for: error)
    }
        
    return
  }
      
  //2. Convert the height sample to meters, save to the profile model,
  //   and update the user interface.
  let heightInMeters = sample.quantity.doubleValue(for: HKUnit.meter())
  self.userHealthProfile.heightInMeters = heightInMeters
  self.updateLabels()
}
  1. This method starts by creating a Height sample type. It then passes that sample type to the method you just wrote, which will return the most recent height sample recorded to HealthKit.
  2. Once a sample is returned, the height is converted to meters and stored on the UserHealthProfile model. Then the labels get updated.

Note: You usually want to convert your quantity sample to some standard unit. To do that, the code above takes advantage of HKQuantitySample’s doubleValue(for:) method which lets you pass in a HKUnit matching what you want (in this case meters).

You can construct various types of HKUnits using some common class methods made available through HealthKit. To get meters, you just use the meter() method on HKUnit and you’re good to go.

That covers height. What about weight? It’s very similar, but you will need to fill in the body for the loadAndDisplayMostRecentWeight() method in ProfileViewController.

Paste the following code into the loadAndDisplayMostRecentWeight() method body:

guard let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass) else {
  print("Body Mass Sample Type is no longer available in HealthKit")
  return
}
    
ProfileDataStore.getMostRecentSample(for: weightSampleType) { (sample, error) in
      
  guard let sample = sample else {
        
    if let error = error {
      self.displayAlert(for: error)
    }
    return
  }
      
  let weightInKilograms = sample.quantity.doubleValue(for: HKUnit.gramUnit(with: .kilo))
  self.userHealthProfile.weightInKilograms = weightInKilograms
  self.updateLabels()
}

It’s the exact same pattern. You create the type of sample you want to retrieve, ask HealthKit for it, do some unit conversions, save to your model, and update the user interface.

At this point, you might think you’re finished but there’s one more thing you need to do. The updateLabels() function isn’t aware of the new data you’ve made available to it. Let’s change that.

Add the following lines to the updateLabels() function, just below the part where you unwrap bloodType to display it in a label:

if let weight = userHealthProfile.weightInKilograms {
  let weightFormatter = MassFormatter()
  weightFormatter.isForPersonMassUse = true
  weightLabel.text = weightFormatter.string(fromKilograms: weight)
}
    
if let height = userHealthProfile.heightInMeters {
  let heightFormatter = LengthFormatter()
  heightFormatter.isForPersonHeightUse = true
  heightLabel.text = heightFormatter.string(fromMeters: height)
}
   
if let bodyMassIndex = userHealthProfile.bodyMassIndex {
  bodyMassIndexLabel.text = String(format: "%.02f", bodyMassIndex)
}

Following the original pattern in the updateLabels() function, it unwraps the height, weight, and body mass index on your UserHealthProfile model. If those are available, it generates the appropriate strings and puts them in the labels. MassFormatter and LengthFormatter do the work of converting your quantities to strings.

Body Mass Index isn’t actually stored on the UserHealthProfile model. It’s a computed property that does the calculation for you.

Command click on the bodyMassIndex property, and you will see what I mean:

var bodyMassIndex: Double? {
    
  guard let weightInKilograms = weightInKilograms,
    let heightInMeters = heightInMeters,
    heightInMeters > 0 else {
    return nil
  }
    
  return (weightInKilograms/(heightInMeters*heightInMeters))
}

Body Mass Index is an optional property, meaning it can return nil if neither height nor weight are set (or if they are set to some number that doesn’t make any sense). The actual calculation is just the weight divided by height squared.

Note: You’ll be stuck soon if you’ve not added data in the HealthKit store for the app to read. If you haven’t already, you need to create some height and weight samples at the very least.

Open the Health App, and go to the Health Data Tab. There, select the Body Measurements option, then choose Weight and then Add Data Point to add a new weight sample. Repeat the process for the Height.

At this point, Prancercise Tracker should be able to read a recent sample of your user’s weight and height, then display it in the labels.

Build and run. Navigate to Profile & BMI. Then tap the Read HealthKit Data button.

Awesome! You just read your first samples from the HealthKit store and used them to calculate the BMI.

Saving Samples

Prancercise Tracker already has a convenient body mass index calculator. Let’s use it to record a sample of your user’s BMI.

Open ProfileDataStore.swift and add the following method:

class func saveBodyMassIndexSample(bodyMassIndex: Double, date: Date) {
  
  //1.  Make sure the body mass type exists  
  guard let bodyMassIndexType = HKQuantityType.quantityType(forIdentifier: .bodyMassIndex) else {
    fatalError("Body Mass Index Type is no longer available in HealthKit")
  }
    
  //2.  Use the Count HKUnit to create a body mass quantity
  let bodyMassQuantity = HKQuantity(unit: HKUnit.count(),
                                    doubleValue: bodyMassIndex)
    
  let bodyMassIndexSample = HKQuantitySample(type: bodyMassIndexType,
                                             quantity: bodyMassQuantity,
                                             start: date,
                                             end: date)
    
  //3.  Save the same to HealthKit
  HKHealthStore().save(bodyMassIndexSample) { (success, error) in
      
    if let error = error {
      print("Error Saving BMI Sample: \(error.localizedDescription)")
    } else {
      print("Successfully saved BMI Sample")
    }
  }
}

Some of this will seem familiar. As with other sample types, you first need to make sure the sample type is available in HealthKit.

  1. In this case, the code checks to see if there is a quantity type for body mass index. If there is, it gets used to create a quantity and quantity sample. If not, the app intentionally crashes.
  2. The count() method on HKUnit is for a special case when there isn’t a clear unit for the type of sample you are storing. At some point in the future, there may be a unit assigned to body mass index, but for now this more generic unit works just fine.
  3. HKHealthStore saves the sample and lets you know if the process was successful from a trailing closure. You could do more with this, but for the now the app just prints to the console.

Almost done. Let’s hook this thing up the user interface.

Open ProfileViewController.swift, find the saveBodyMassIndexToHealthKit() method. This method gets called when the user taps the Save BMI button in the table view.

Paste the following lines of code into the method:

guard let bodyMassIndex = userHealthProfile.bodyMassIndex else {
  displayAlert(for: ProfileDataError.missingBodyMassIndex)
  return
}
    
ProfileDataStore.saveBodyMassIndexSample(bodyMassIndex: bodyMassIndex,
                                         date: Date())

You will recall that the body mass index is a computed property which returns a value when both height and weight samples have been loaded from HealthKit. This code attempts to compute that property, and if it can, it gets passed to the savedBodyMassIndexSample(bodyMassIndex: date:) method you just wrote.

It also shows a handy alert message if body mass index can’t be computed for some reason.

Build and run Prancercise Tracker one final time. Go into the Profile & BMI screen. Load your data from HeathKit, then tap the Save BMI button.

Take a look at the console. Do you see this?

Successfully saved BMI Sample

If you do, congratulations! Your BMI sample is now stored in HealthKit’s central repository. Let’s see if we can find it.

Open the Health app, tap the Health Data tab, Tap on Body Measurements in the table view, and then tap on Body Mass Index.

Unless you regularly record your body mass index (as all high profile prancercisers do), you should see a single data point like this one:

Sweet. You can see the sample from an app of your own creation, right there in Apple’s Health app. Imagine the possibilities.

Where To Go From Here?

Want to learn even faster? Save time with our video courses

Here is the example app with all of the modifications we have made up to this point.

Congratulations, you’ve got some hands-on experience with HealthKit! You now know how to request permissions, read biological characteristics, and read and write samples.

If you want to learn more, stay tuned for the next part of this HealthKit tutorial series where you’ll learn more about a more complex type of data: workouts.

HealthKit Tutorial With Swift

How To Implement A Circular Image Loader Animation with CAShapeLayer

First download the starter project for this CAShapeLayer tutorial.

Take a minute and browse through the project once you’ve extracted it. There’s a ViewController that has a UIImageView subclass named CustomImageView, along with a SDWebImage method call to load the image. The starter project already has the views and image loading logic in place.

Build and run. After a moment, you should see a simple image displayed as follows:

You might notice when you first run the app, the app seems to pause for a few seconds while the image downloads, then the image appears on the screen without fanfare. Of course, there’s no circular progress indicator at the moment – that’s what you’ll create in this CAShapeLayer tutorial!

You’ll create this animation in two distinct phases:

  1. Circular progress. First, you’ll draw a circular progress indicator and update it based on the progress of the download.
  2. Expanding circular image. Second, you’ll reveal the downloaded image through an expanding circular window.

Follow along closely to prevent yourself from going “round in circles”! :]

Creating the Circular Indicator

Think for a moment about the basic design of the progress indicator. The indicator is initially empty to show a progress of 0%, then gradually fills in as the image is downloaded. This is fairly simple to achieve with a CAShapeLayer whose path is a circle.

Note: If you’re new to the concept of CAShapeLayer (or CALayers in general, check out Scott Gardner’s CALayer in iOS with Swift article.

You can control the start and end position of the outline, or stroke, of your shape with the CAShapeLayer properties strokeStart and strokeEnd. By varying strokeEnd between 0 and 1, you can fill in the stroke appropriately to show the progress of the download.

Let’s try this out. Create a new file with the iOS\Source\Cocoa Touch Class template. Name it CircularLoaderView and set subclass of to UIView as shown below:

Click Next, and then Create. This new subclass of UIView will house all of your new animation code.

Open CircularLoaderView.swift and add the following properties to the top of the class:

let circlePathLayer = CAShapeLayer()
let circleRadius: CGFloat = 20.0

circlePathLayer represents the circular path, while circleRadius will be the radius of the circular path. Rocket science! I know.

Next, add the following initialization code right below circleRadius to configure the shape layer:

override init(frame: CGRect) {
  super.init(frame: frame)
  configure()
}

required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  configure()
}

func configure() {
  circlePathLayer.frame = bounds
  circlePathLayer.lineWidth = 2
  circlePathLayer.fillColor = UIColor.clear.cgColor
  circlePathLayer.strokeColor = UIColor.red.cgColor
  layer.addSublayer(circlePathLayer)
  backgroundColor = .white
}

Both of the initializers call configure(). configure() sets up circlePathLayer to have a frame that matches the view’s bounds, a line width of 2 points, a clear fill color and a red stroke color. Next, it adds the shape layer as a sublayer of the view’s own layer and sets the view’s backgroundColor to white so the rest of the screen is blanked out while the image loads.

Adding the Path

Now you’ve configured the layer, it’s time to set its path. Start by adding the following helper method right below configure():

func circleFrame() -> CGRect {
  var circleFrame = CGRect(x: 0, y: 0, width: 2 * circleRadius, height: 2 * circleRadius)
  let circlePathBounds = circlePathLayer.bounds
  circleFrame.origin.x = circlePathBounds.midX - circleFrame.midX
  circleFrame.origin.y = circlePathBounds.midY - circleFrame.midY
  return circleFrame
}

In this simple method you calculate the CGRect to contain the indicator’s path. You set the bounding rectangle to have a width and a height equals to 2 * circleRadius and position it at the center of the view. The reason why you wrote a separate method to handle this simple operation is you’ll need to recalculate circleFrame each time the view’s size changes.

Next, add the following method below circleFrame() to create your path:

func circlePath() -> UIBezierPath {
  return UIBezierPath(ovalIn: circleFrame())
}

This simply returns the circular UIBezierPath as bounded by circleFrame(). Since circleFrame() returns a square, the “oval” in this case will end up as a circle.

Since layers don’t have an autoresizingMask property, you’ll override layoutSubviews to respond appropriately to changes in the view’s size.

Override layoutSubviews() by adding the following code:

override func layoutSubviews() {
  super.layoutSubviews()
  circlePathLayer.frame = bounds
  circlePathLayer.path = circlePath().cgPath
}

You’re calling circlePath() here because a change in the frame should also trigger a recalculation of the path.

Open CustomImageView.swift. Add the following property to the top of the class:

let progressIndicatorView = CircularLoaderView(frame: .zero)

This property is an instance of the CircularLoaderView class you just created.

Next, add the following to init(coder:), right before let url...:

addSubview(progressIndicatorView)

addConstraints(NSLayoutConstraint.constraints(
  withVisualFormat: "V:|[v]|", options: .init(rawValue: 0),
  metrics: nil, views: ["v": progressIndicatorView]))
addConstraints(NSLayoutConstraint.constraints(
  withVisualFormat: "H:|[v]|", options: .init(rawValue: 0),
  metrics: nil, views:  ["v": progressIndicatorView]))
progressIndicatorView.translatesAutoresizingMaskIntoConstraints = false

Here you add the progress indicator view as a subview of the custom image view. Then you add two layout constraints to ensure the progress indicator view remains the same size as the image view. Finally, you set translatesAutoresizingMaskIntoConstraints to false so the autoresizing mask doesn’t interfere with the Auto Layout engine.

Build and run your project; you should see a red, hollow circle appear like so:

Awesome! Your progress indicator is showing on the screen.

Modifying the Stroke Length

Open CircularLoaderView.swift and add the following lines directly below the other properties in the file:

var progress: CGFloat {
  get {
    return circlePathLayer.strokeEnd
  }
  set {
    if newValue > 1 {
      circlePathLayer.strokeEnd = 1
    } else if newValue < 0 {
      circlePathLayer.strokeEnd = 0
    } else {
      circlePathLayer.strokeEnd = newValue
    }
  }
}

Here you create a computed property — that is, a property without any backing variable — that has a custom setter and getter. The getter simply returns circlePathLayer.strokeEnd, and the setter validates the input is between 0 and 1 and sets the layer’s strokeEnd property accordingly.

Add the following line at the top of configure() to initialize progress on first run:

progress = 0

Build and run your project; you should see nothing but a blank white screen. Trust me! This is good news! :] Setting progress to 0 in turn sets the strokeEnd to 0, which means no part of the shape layer was drawn.

CAShapeLayer tutorial

The only thing left to do with your indicator is to update progress in the image download callback.

Open CustomImageView.swift and replace the comment Update progress here with the following:

self?.progressIndicatorView.progress = CGFloat(receivedSize) / CGFloat(expectedSize)

Here you calculate the progress by dividing receivedSize by expectedSize.

Note: You’ll notice the block uses a weak reference to self – this is to avoid a retain cycle.

Build and run your project. You’ll see the progress indicator begin to move like so:

Even though you didn’t add any animation code yourself, CALayer handily detects any animatable property on the layer and smoothly animates it as it changes. Neat!

That takes care of the first phase. Now on to the second and final phase — the big reveal! :]

Creating the Reveal Animation

The reveal phase gradually displays the image in a window in the shape of an expanding circular ring. If you’ve read this tutorial on creating a Ping-style view controller animation, you’ll know this is a perfect use-case of the mask property of a CALayer.

Open CircularLoaderView.swift and add the following method:

func reveal() {
  // 1
  backgroundColor = .clear
  progress = 1
  // 2
  circlePathLayer.removeAnimation(forKey: "strokeEnd")
  // 3
  circlePathLayer.removeFromSuperlayer()
  superview?.layer.mask = circlePathLayer
}

This is an important method to understand, so let’s go over this section by section:

  1. You clear the view’s background color so the image behind the view isn’t hidden anymore, and you set progress to 1.
  2. You remove any pending implicit animations for the strokeEnd property, which may have otherwise interfered with the reveal animation. For more about implicit animations, check out iOS Animations by Tutorials.
  3. You remove circlePathLayer from its superLayer and assign it instead to the superView’s layer mask, so the image is visible through the circular mask “hole”. This lets you reuse the existing layer and avoid duplicating code.

Now you need to call reveal() from somewhere. Replace the Reveal image here comment in CustomImageView.swift with the following:

if let error = error {
  print(error)
}
self?.progressIndicatorView.reveal()

Build and run. Once the image downloads you’ll see it partially revealed through a small ring:

You can see your image in the background — but just barely! :]

Expanding Rings

Your next step is to expand this ring both inwards and outwards. You could do this with two separate, concentric UIBezierPath, but you can do it in a more efficient manner with just a single Bezier path.

How? You simply increase the circle’s radius to expand outward by changing the path property, while simultaneously increasing the line’s width to make the ring thicker and expand inward by changing the lineWidth property. Eventually, both values grow enough to reveal the entire image underneath.

Open CircularLoaderView.swift and add the following code to the end of reveal():

// 1
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let finalRadius = sqrt((center.x*center.x) + (center.y*center.y))
let radiusInset = finalRadius - circleRadius
let outerRect = circleFrame().insetBy(dx: -radiusInset, dy: -radiusInset)
let toPath = UIBezierPath(ovalIn: outerRect).cgPath

// 2
let fromPath = circlePathLayer.path
let fromLineWidth = circlePathLayer.lineWidth

// 3
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
circlePathLayer.lineWidth = 2*finalRadius
circlePathLayer.path = toPath
CATransaction.commit()

// 4
let lineWidthAnimation = CABasicAnimation(keyPath: "lineWidth")
lineWidthAnimation.fromValue = fromLineWidth
lineWidthAnimation.toValue = 2*finalRadius
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.fromValue = fromPath
pathAnimation.toValue = toPath

// 5
let groupAnimation = CAAnimationGroup()
groupAnimation.duration = 1
groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
groupAnimation.animations = [pathAnimation, lineWidthAnimation]
circlePathLayer.add(groupAnimation, forKey: "strokeWidth")

This might look like a lot of code, but what you’re doing here is fairly simple:

  1. You determine the radius of the circle that can fully circumscribe the image view and use it to calculate the CGRect that would fully bound this circle. toPath represents the final shape of the CAShapeLayer mask like so:
  2. You set the initial values of lineWidth and path to match the current values of the layer.
  3. You set lineWidth and path to their final values. This prevents them from jumping back to their original values when the animation completes. By wrapping this changes in a CATransaction with kCATransactionDisableActions set to true you disable the layer’s implicit animations.
  4. You create two instances of CABasicAnimation: one for path and the other for lineWidth. lineWidth has to increase twice as fast as the radius increases in order for the circle to expand inward as well as outward.
  5. You add both animations to a CAAnimationGroup, and add the animation group to the layer.

Build and run your project. You’ll see the reveal animation kick-off once the image finishes downloading:

Notice a portion of the circle remains on the screen once the reveal animation is done. To fix this, add the following extension to the end of CircularLoaderView.swift implementing animationDidStop(_:finished:):

extension CircularLoaderView: CAAnimationDelegate {
  func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
    superview?.layer.mask = nil
  }
}

This code removes the mask on the super layer, which removes the circle entirely.

Finally, at the bottom of reveal(), just above the line circlePathLayer.add(groupAnimation, forKey: "strokeWidth") add the following line:

groupAnimation.delegate = self

This assigns the delegate so the animationDidStop(_:finished:) gets called.

Build and run your project. Now you’ll see the full effect of your animation:

Congratulations, you’ve finished creating the circular image loading animation!

How To Implement A Circular Image Loader Animation with CAShapeLayer

ARKit Tutorial: Detecting Horizontal Planes and Adding 3D Objects with SceneKit

Augmented reality has the power to amplify the world in ways never possible before. The way in which we interact with our world may never be the same again. With the release of iPhone X, the world is ready to embrace AR now more than ever before. We are at a special time in history and just at the beginning of something huge. The potential of AR is endless.

Prerequisites

This tutorial builds on top of knowledge from previous ARKit tutorials. If you haven’t already, you can check out the previous tutorial. It would also be great if you can find a flat surface for your app.

What we are going to learn

In this tutorial, our focus is on horizontal plane in ARKit. We are going to first create an ocean (horizontal plane). Then place a nice ship on top (3D object).

Or create a fleet of ships with lighting!

Along the way, you will learn about horizontal plane in ARKit. It is my hope that by the end of this tutorial, you will feel more comfortable utilizing horizontal plane when working on your ARKit project.

What’s a Horizontal Plane

So what exactly are we talking about when we talk about horizontal plane in ARKit? When we detect a horizontal plane in ARKit — we technically detect an ARPlaneAnchor. So what is an ARPlaneAnchor? An ARPlaneAnchor is basically an object containing information about the detected horizontal plane.

Here is a more formal description of ARPlaneAnchor from Apple:

Information about the position and orientation of a real-world flat surface detected in a world-tracking AR session.

– Apple’s Documentation

Let’s begin to build the app

We will begin with a starter project, so we can focus on the implementation of ARKit. Open the starter project in Xcode to take a look. I have already created the ARSCNView in the storyboard.

Build and run the starter project to have a quick test. You should see the following on your iOS device:

Make sure you should tap OK to grant the access to the camera. You should then see your camera’s view.

Horizontal Planes Detection

Detecting a horizontal plane is simple. Thanks to the “appley” Apple Engineers.

Simply add the following inside the setUpSceneView() method of ViewController:

By setting the planeDetection property of ARWorldTrackingConfiguration to .horizontal, this tells ARKit to look for any horizontal plane. Once ARKit detects a horizontal plane, that horizontal plane will be added into sceneView’s session.

In order to detect the horizontal plane, we have to adopt the ARSCNViewDelegate protocol. Below the ViewController class, create a ViewController class extension to implement the protocol:

Now inside of the class extension, implement the renderer(_:didAdd:for:) method:

This protocol method gets called every time the scene view’s session has a new ARAnchor added. An ARAnchor is an object that represents a physical location and orientation in 3D space. We will use the ARAnchor later for detecting a horizontal plane.

Next, head back to setUpSceneView(). Assign the sceneView’s delegate to your ViewController inside of setUpSceneView().

If you’d like, you can also set sceneView’s debug options to show feature points in the world. This could help you find a place with enough feature points to detect a horizontal plane. A horizontal plane is made up of many feature points. Once enough feature points has been detected to recognize a horizontal surface, renderer(_:didAdd:for:) will be called.

Your setUpSceneView() method should now look like this:

Horizontal Planes Visualization

Now that the app gets notified every time a new ARAnchor is being added onto sceneView, we may be interested in seeing what that newly added ARAnchor looks like.

Hence, update the renderer(_:didAdd:for:) method like this:

Let’s me walk you through the code line by line:

  1. We safely unwrap the anchor argument as an ARPlaneAnchor to make sure that we have information about a detected real world flat surface at hand.
  2. Here, we create an SCNPlane to visualize the ARPlaneAnchor. A SCNPlane is a rectangular “one-sided” plane geometry. We take the unwrapped ARPlaneAnchor extent’s x and z properties and use them to create an SCNPlane. An ARPlaneAnchor extent is the estimated size of the detected plane in the world. We extract the extent’s x and z for the height and width of our SCNPlane. Then we give the plane a transparent light blue color to simulate a body of water.
  3. We initialize a SCNNode with the SCNPlane geometry we just created.
  4. We initialize x, y, and z constants to represent the planeAnchor’s center x, y, and z position. This is for our planeNode’s position. We rotate the planeNode’s x euler angle by 90 degrees in the counter-clockerwise direction, else the planeNode will sit up perpendicular to the table. And if you rotate it clockwise, David Blaine will perform a magic illusion because SceneKit renders the SCNPlane surface using the material from one side by default.
  5. Finally, we add the planeNode as the child node onto the newly added SceneKit node.

Build and run the project. You should now be able to detect and visualize the detected horizontal plane.

Horizontal Planes Expansion

With ARKit receiving additional information about our environment, we may want to expand our previously detected horizontal plane(s) to make use of a larger surface or have a more accurate representation with the new information.

Hence, implement renderer(_:didUpdate:for:):

This method gets called every time a SceneKit node’s properties have been updated to match its corresponding anchor. This is where ARKit refines its estimation of the horizontal plane’s position and extent.

The node argument gives us the updated position of the anchor. The anchor argument gives us the anchor’s updated width and height. With these two arguments, we can update the previously implemented SCNPlane to reflect the updated position with the updated width and height.

Next, add the following code inside renderer(_:didUpdate:for:):

Again, let’s me go through the code above with you:

  1. First, we safely unwrap the anchor argument as ARPlaneAnchor. Next, we safely unwrap the node’s first child node. Lastly, we safely unwrap the planeNode’s geometry as SCNPlane. We are simply extracting the previously implemented ARPlaneAnchor, SCNNode, and SCNplaneand updating its properties with the corresponding arguments.
  2. Here we update the plane’s width and height using the planeAnchor extent’s x and z properties.
  3. At last, we update the planeNode’s position to the planeAnchor’s center x, y, and z coordinates.

Build and run to check out expanding horizontal plane implementation.

Adding Objects on Horizontal Planes

Now let’s add a ship on top of the horizontal plane. Inside of the starter project, I have already bundled a 3D ship object for you to use.

Insert the following method in the ViewController class to place a ship on top of the horizontal plane:

There are many familiar faces here as explained in the previous tutorial, so I will not go through the code line by line. If you want to learn more about that, check out the previous tutorial. The only difference now is that we pass in a different argument in the types parameter to detect an existing plane anchor in the sceneView.

Before the cherry on top, add the following code:

This method will add a tap gesture recognizer to sceneView.

For the cherry on top, call the following method inside of viewDidLoad() to add a tap gesture recognizer to sceneView:

Now if you build and run, you should be able to detect a horizontal plane, visualize it, and place an insanely cool ship on top.

Or a fleet of ships (with lighting).

You can enable lighting by uncommenting configureLighting() inside of viewDidLoad(). The function is very simple with two lines of code to enable lighting:

ARKit Tutorial: Detecting Horizontal Planes and Adding 3D Objects with SceneKit

UIGestureRecognizer Tutorial

If you need to detect gestures in your app, such as taps, pinches, pans, or rotations, it’s extremely easy with Swift and the built-in UIGestureRecognizer classes.

In this tutorial, you’ll learn how you can easily add gesture recognizers into your app, both within the Storyboard editor in Xcode, and programatically. You’ll create a simple app where you can move a monkey and a banana around by dragging, pinching, and rotating with the help of gesture recognizers.

You’ll also try out some cool extras like:

  • Adding deceleration for movement
  • Setting dependencies between gesture recognizers
  • Creating a custom UIGestureRecognizer so you can tickle the monkey!

This tutorial assumes you are familiar with the basic concepts of Storyboards.

I think the monkey just gave us the thumbs up gesture, so let’s get started! :]

Getting Started

Click here to download the starter project. Open it in Xcode and build and run.

You should see the following on your device or simulator:

UIGestureRecognizer Overview

Before you get started, here’s a brief overview of how you use UIGestureRecognizers and why they’re so handy.

In the old days before UIGestureRecognizers, if you wanted to detect a gesture such as a swipe, you’d have to register for notifications on every touch within a UIView – such as touchesBegan, touchesMoved, and touchesEnded. Each programmer wrote slightly different code to detect touches, resulting in subtle bugs and inconsistencies across apps.

In iOS 3.0, Apple came to the rescue with UIGestureRecognizer classes! These provide a default implementation of detecting common gestures such as taps, pinches, rotations, swipes, pans, and long presses. By using them, not only does it save you a ton of code, but it makes your apps work properly too! Of course you can still use the old touch notifications, if your app requires them.

Using UIGestureRecognizer is extremely simple. You just perform the following steps:

  1. Create a gesture recognizer. When you create a gesture recognizer, you specify a callback function so the gesture recognizer can send you updates when the gesture starts, changes, or ends.
  2. Add the gesture recognizer to a view. Each gesture recognizer is associated with one (and only one) view. When a touch occurs within the bounds of that view, the gesture recognizer will look to see if it matches the type of touch it’s looking for, and if a match is found it will notify the callback function.

You can perform these two steps programatically (which you’ll do later on in this tutorial), but it’s even easier adding a gesture recognizer visually with the Storyboard editor.

UIPanGestureRecognizer

Open up Main.storyboard. Inside the Object Library, look for the Pan Gesture Recognizer object. Then drag the Pan Gesture Recognizer object onto the monkey Image View. This both creates the pan gesture recognizer, and associates it with the monkey Image View:

You can verify you got it connected OK by clicking on the monkey Image View, looking at the Connections Inspector (View Menu > Utilities > Show Connections Inspector), and making sure the Pan Gesture Recognizer is in the gestureRecognizers Outlet Collection.

The starter project has connected the monkey Image View with the Pinch Gesture Recognizer and Rotation Gesture Recognizer for you. It has also connected the banana Image View with the Pan Gesture Recognizer, Pinch Gesture Recognizer, and Rotation Gesture Recognizer for you. These connections from the starter project are achieved by dragging a gesture recognizer on top of an image view as shown earlier.

You may wonder why the UIGestureRecognizer is associated with the image view instead of the view itself. Either approach would be OK, it’s just what makes most sense for your project. Since you tied it to the monkey, you know that any touches are within the bounds of the monkey so you’re good to go. The drawback of this method is sometimes you might want touches to be able to extend beyond the bounds. In that case, you could add the gesture recognizer to the view itself, but you’d have to write code to check if the user is touching within the bounds of the monkey or the banana and react accordingly.

Now that you’ve created the pan gesture recognizer and associated it to the image view, you just have to write the callback function so something actually happens when the pan occurs.

Open up ViewController.swift and add the following function right below viewDidLoad() inside of the ViewController class:

@IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
  let translation = recognizer.translation(in: self.view)
  if let view = recognizer.view {
    view.center = CGPoint(x:view.center.x + translation.x,
                            y:view.center.y + translation.y)
  }
  recognizer.setTranslation(CGPoint.zero, in: self.view)
}

The UIPanGestureRecognizer will call this function when a pan gesture is first detected, and then continuously as the user continues to pan, and one last time when the pan is complete (usually the user lifting their finger).

The UIPanGestureRecognizer passes itself as an argument to this function. You can retrieve the amount the user has moved their finger by calling the translation(in:) function. Here you use that amount to move the center of the monkey the same amount the finger has been dragged.

It’s important to set the translation back to zero once you are done. Otherwise, the translation will keep compounding each time, and you’ll see your monkey rapidly move off the screen!

Note that instead of hard-coding the monkey image view into this function, you get a reference to the monkey image view by calling recognizer.view. This makes your code more generic, so that you can re-use this same routine for the banana image view later on.

OK, now that this function is complete, you will hook it up to the UIPanGestureRecognizer. In Main.storyboard, control drag from the Pan Gesture Recognizer to View Controller. A popup will appear – select handlePan(recognizer:).$

At this point your Connections Inspector for the Pan Gesture Recognizer should look like this:

One more thing: If you compile and run, and try to drag the monkey, it won’t work yet. The reason is that touches are disabled by default on views that normally don’t accept touches, like image views. So select both image views, open up the Attributes Inspector, and check the User Interaction Enabled checkbox.

Compile and run again, and this time you should be able to drag the monkey around the screen!

Note that you can’t drag the banana. This is because gesture recognizers should be tied to one (and only one) view.

The starter project has attached a Pan Gesture Recognizer to the banana Image View for you. This is achieved using the same method as attaching a Pan Gesture Recognizer to the monkey Image View as shown earlier.

Now connect the handlePan(recognizer:) callback function to the banana Image View by performing the following:

  1. Control drag from the banana Pan Gesture Recognizer to the View Controller and select handlePan:.
  2. Make sure User Interaction Enabled is checked on the banana as well.

Give it a try and you should now be able to drag both image views across the screen. Pretty easy to implement such a cool and fun effect, eh?

Gratuitous Deceleration

In a lot of Apple apps and controls, when you stop moving something there’s a bit of deceleration as it finishes moving. Think about scrolling a web view, for example. It’s common to want to have this type of behavior in your apps.

There are many ways of doing this, but you’re going to do one very simple implementation for a rough but nice effect. The idea is to detect when the gesture ends, figure out how fast the touch was moving, and animate the object moving to a final destination based on the touch speed.

  • To detect when the gesture ends: The callback passed to the gesture recognizer is called potentially multiple times – when the gesture recognizer changes its state to began, changed, or ended for example. You can find out what state the gesture recognizer is in simply by looking at its state property.
  • To detect the touch velocity: Some gesture recognizers return additional information – you can look at the API guide to see what you can get. There’s a handy function called velocity(in:) that you can use in the UIPanGestureRecognizer!

So add the following to the bottom of the handlePan(recognizer:) function in ViewController.swift:

if recognizer.state == UIGestureRecognizerState.ended {
    // 1
    let velocity = recognizer.velocity(in: self.view)
    let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
    let slideMultiplier = magnitude / 200
    print("magnitude: \(magnitude), slideMultiplier: \(slideMultiplier)")
      
    // 2
    let slideFactor = 0.1 * slideMultiplier     //Increase for more of a slide
    // 3
    var finalPoint = CGPoint(x:recognizer.view!.center.x + (velocity.x * slideFactor),
                               y:recognizer.view!.center.y + (velocity.y * slideFactor))
    // 4
    finalPoint.x = min(max(finalPoint.x, 0), self.view.bounds.size.width)
    finalPoint.y = min(max(finalPoint.y, 0), self.view.bounds.size.height)
      
    // 5
    UIView.animate(withDuration: Double(slideFactor * 2),
                     delay: 0,
                     // 6
                     options: UIViewAnimationOptions.curveEaseOut,
                     animations: {recognizer.view!.center = finalPoint },
                     completion: nil)
}

This simple deceleration function uses the following strategy:

  1. Figure out the length of the velocity vector (i.e. the magnitude)
  2. If the length is < 200, then decrease the base speed, otherwise increase it.
  3. Calculate a final point based on the velocity and the slideFactor.
  4. Make sure the final point is within the view’s bounds
  5. Animate the view to the final resting place.
  6. Use the “ease out” animation option to slow down the movement over time.

Compile and run to try it out, you should now have some basic but nice deceleration! Feel free to play around with it and improve it – if you come up with a better implementation, please share in the forum discussion at the end of this article.

Pinch and Rotation Gestures

Your app is coming along great so far, but it would be even cooler if you could scale and rotate the image views by using pinch and rotation gestures as well!

The starter project has created the handlePinch(recognizer:) and the handleRotate(recognizer:) callback functions for you. It has also connected the callback functions to the monkey Image View and the banana Image View.

Open up ViewController.swift. Add the following to handlePinch(recognizer:):

if let view = recognizer.view {
  view.transform = view.transform.scaledBy(x: recognizer.scale, y: recognizer.scale)
  recognizer.scale = 1
}

Next add the following to handleRotate(recognizer:):

if let view = recognizer.view {
  view.transform = view.transform.rotated(by: recognizer.rotation)
  recognizer.rotation = 0
}

Just like you could get the translation from the UIPanGestureRecognizer, you can get the scale and rotation from the UIPinchGestureRecognizer and UIRotationGestureRecognizer.

Every view has a transform that is applied to it, which you can think of as information on the rotation, scale, and translation that should be applied to the view. Apple has a lot of built in functions to make working with a transform easy, such as CGAffineTransform(scaleX:y:) to scale a given transform and CGAffineTransform(rotationAngle:) to rotate a given transform. Here you will use these to update the view’s transform based on the gesture.

Again, since you’re updating the view each time the gesture updates, it’s very important to reset the scale and rotation back to the default state so you don’t have craziness going on.

Now hook these up in the Storyboard editor. Open up Main.storyboard and perform the following steps:

  1. In the same way that you did previously, connect the two Pinch Gesture Recognizers to the View Controller’s handlePinch(recognizer:) function.
  2. Connect the two Rotation Gesture Recognizers to the View Controller’s handleRotate(recognizer:) function.

Your View Controller connections should now look like this:

Build and run. Run it on a device if possible, because pinches and rotations are kinda hard to do on the simulator. If you are running on the simulator, hold down the option key and drag to simulate two fingers, and hold down shift and option at the same time to move the simulated fingers together to a different position. Now you should be able to scale and rotate the monkey and banana!

Note: There seems to be a bug with the Xcode 9 Simulator. If you’re experiencing issues with pinch and rotation gestures on the Xcode 9 Simulator, try running on a device instead.

Simultaneous Gesture Recognizers

You may notice that if you put one finger on the monkey, and one on the banana, you can drag them around at the same time. Kinda cool, eh?

However, you’ll notice that if you try to drag the monkey around, and in the middle of dragging bring down a second finger to attempt to pinch to zoom, it doesn’t work. By default, once one gesture recognizer on a view “claims” the gesture, no others can recognize a gesture from that point on.

However, you can change this by overriding a function in the UIGestureRecognizer delegate.

Open up ViewController.swift. Below the ViewController class, create a ViewController class extension and adopt it to the UIGestureRecognizerDelegate as shown below:

extension ViewController: UIGestureRecognizerDelegate {

}

Then implement one of the delegate’s optional functions:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  return true
}

This function tells the gesture recognizer whether it is OK to recognize a gesture if another (given) recognizer has already detected a gesture. The default implementation always returns false – here you switch it to always return true.

Next, open Main.storyboard, and for each gesture recognizer connect its delegate outlet to the view controller (6 gesture recognizers in total).

Build and run the app again, and now you should be able to drag the monkey, pinch to scale it, and continue dragging afterwards! You can even scale and rotate at the same time in a natural way. This makes for a much nicer experience for the user.

Programmatic UIGestureRecognizers

So far you’ve created gesture recognizers with the Storyboard editor, but what if you wanted to do things programatically?

It’s just as easy, so you’ll try it out by adding a tap gesture recognizer to play a sound effect when either of these image views are tapped.

To be able to play a sound, you’ll need to access the AVFoundation framework. At the top of Viewcontroller.swift, add:

import AVFoundation

Add the following changes to ViewController.swift just before viewDidLoad():

var chompPlayer:AVAudioPlayer? = nil
  
func loadSound(filename: String) -> AVAudioPlayer {
  let url = Bundle.main.url(forResource: filename, withExtension: "caf")
  var player = AVAudioPlayer()
  do {
    try player = AVAudioPlayer(contentsOf: url!)
    player.prepareToPlay()
  } catch {
    print("Error loading \(url!): \(error.localizedDescription)")
  }
  return player
}

Replace viewDidLoad() with the following:

super.viewDidLoad()
//1
let filteredSubviews = self.view.subviews.filter({
  $0 is UIImageView })
  //2
  for view in filteredSubviews  {
  //3
  let recognizer = UITapGestureRecognizer(target: self,
    action:#selector(handleTap(recognizer:)))
  //4
  recognizer.delegate = self
  view.addGestureRecognizer(recognizer)
      
  //TODO: Add a custom gesture recognizer too
}
self.chompPlayer = self.loadSound(filename: "chomp")

The starter project has created the handleTap(recognizer:) callback function. The starter project has also connected the callback function to the monkey Image View and the banana Image View for you. Add the following inside of handleTap(recognizer:):

self.chompPlayer?.play()

The audio playing code is outside of the scope of this tutorial so I won’t discuss it (although it is incredibly simple).

The important part is in viewDidLoad():

  1. Create a filtered array of just the monkey and banana image views.
  2. Cycle through the filtered array.
  3. Create a UITapGestureRecognizer for each image view, specifying the callback. This is an alternative way of adding gesture recognizers. Previously you added the recognizers to the storyboard.
  4. Set the delegate of the recognizer programatically, and add the recognizer to the image view.

That’s it! Compile and run, and now you should be able to tap the image views for a sound effect!

UIGestureRecognizer Dependencies

It works pretty well, except there’s one minor annoyance. If you drag an object a very slight amount, it will pan it and play the sound effect. But what you really want is to only play the sound effect if no pan occurs.

To solve this you could remove or modify the delegate callback to behave differently in the case a touch and pinch coincide, but here is another useful thing you can do with gesture recognizers: setting dependencies.

There’s a function called require(toFail:) that you can call on a gesture recognizer. Can you guess what it does? ;]

Open Main.storyboard, open up the Assistant Editor, and make sure that ViewController.swift is showing there. Then control drag from the monkey pan gesture recognizer to below the class declaration, and connect it to an outlet named monkeyPan. Repeat this for the banana pan gesture recognizer, but name the outlet bananaPan.

Add these two lines to viewDidLoad(), right before the TODO:

recognizer.require(toFail: monkeyPan)
recognizer.require(toFail: bananaPan)

Now the tap gesture recognizer will only get called if no pan is detected. Pretty cool eh? You might find this technique useful in some of your projects.

Custom UIGestureRecognizer

At this point you know pretty much everything you need to know to use the built-in gesture recognizers in your apps. But what if you want to detect some kind of gesture not supported by the built-in recognizers?

Well, you could always write your own! Now you’ll try it out by writing a very simple gesture recognizer to detect if you try to “tickle” the monkey or banana by moving your finger several times from left to right.

Create a new file with the iOS\Source\Swift File template. Name the file TickleGestureRecognizer.

Then replace the contents of TickleGestureRecognizer.swift with the following:

import UIKit

class TickleGestureRecognizer:UIGestureRecognizer {
  // 1
  let requiredTickles = 2
  let distanceForTickleGesture:CGFloat = 25.0
  
  // 2
  enum Direction:Int {
    case DirectionUnknown = 0
    case DirectionLeft
    case DirectionRight
  }
  
  // 3
  var tickleCount:Int = 0
  var curTickleStart:CGPoint = CGPoint.zero
  var lastDirection:Direction = .DirectionUnknown
}

This is what you just declared step by step:

  1. These are the constants that define what the gesture will need. Note that requiredTickles will be inferred as an Int, but you need to specify distanceForTickleGesture as a CGFloat. If you do not, then it will be inferred as a Double, and cause difficulties when doing calculations with CGPoints later on.
  2. These are the possible tickle directions.
  3. Here are the three variables to keep track of to detect this gesture:
    • tickleCount: How many times the user has switched the direction of their finger (while moving a minimum amount of points). Once the user moves their finger direction three times, you count it as a tickle gesture.
    • curTickleStart: The point where the user started moving in this tickle. You’ll update this each time the user switches direction (while moving a minimum amount of points).
    • lastDirection: The last direction the finger was moving. It will start out as unknown, and after the user moves a minimum amount you’ll check whether they’ve gone left or right and update this appropriately.

Of course, these properties here are specific to the gesture you’re detecting here – you’ll have your own if you’re making a recognizer for a different type of gesture, but you can get the general idea here.

One of the things that you’ll be changing is the state of the gesture – when a tickle is completed, you’ll need to change the state of the gesture to ended. In the original Objective-C UIGestureRecognizer, state is a read-only property, so you will need to create a Bridging Header to be able to redeclare this property.

The easiest way to do this is to create an Objective-C Class, and then delete the implementation part.

Create a new file, using the iOS\Source\Objective-C File template. Call the file Bridging-Header, and click Create. You will then be asked whether you would like to configure an Objective-C bridging header. Choose Yes. Two new files will be added to your project:

  • MonkeyPinch-Bridging-Header.h
  • Bridging-Header.m

Delete Bridging-Header.m.

Add this Objective-C code to MonkeyPinch-Bridging-Header.h:

#import <UIKit/UIGestureRecognizerSubclass.h>

Now you will be able to change the UIGestureRecognizer‘s state property in TickleGestureRecognizer.swift.

Switch to TickleGestureRecognizer.swift and add the following functions to the class:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
  if let touch = touches.first {
    self.curTickleStart = touch.location(in: self.view)
  }
}
  
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
  if let touch = touches.first {
    let ticklePoint = touch.location(in: self.view)
      
    let moveAmt = ticklePoint.x - curTickleStart.x
    var curDirection:Direction
    if moveAmt < 0 {
      curDirection = .DirectionLeft
    } else {
      curDirection = .DirectionRight
    }
      
    //moveAmt is a Float, so self.distanceForTickleGesture needs to be a Float also
    if abs(moveAmt) < self.distanceForTickleGesture {
      return
    }
      
    if self.lastDirection == .DirectionUnknown ||
      (self.lastDirection == .DirectionLeft && curDirection == .DirectionRight) ||
      (self.lastDirection == .DirectionRight && curDirection == .DirectionLeft) {
      self.tickleCount += 1
      self.curTickleStart = ticklePoint
      self.lastDirection = curDirection
        
      if self.state == .possible && self.tickleCount > self.requiredTickles {
        self.state = .ended
      }
    }
  }
}
  
override func reset() {
  self.tickleCount = 0
  self.curTickleStart = CGPoint.zero
  self.lastDirection = .DirectionUnknown
  if self.state == .possible {
    self.state = .failed
  }
}
  
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
  self.reset()
}
  
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
  self.reset()
}

There’s a lot of code here, but I’m not going to go over the specifics because frankly they’re not quite important. The important part is the general idea of how it works: you’re overriding the UIGestureRecognizer’s touchesBegan(_:with:), touchesMoved(_:with:), touchesEnded(_:with:), and touchesCancelled(_:with:) functions, and writing custom code to look at the touches and detect the gesture.

Once you’ve found the gesture, you want to send updates to the callback function. You do this by changing the state property of the gesture recognizer. Usually once the gesture begins, you want to set the state to .began, send any updates with .changed, and finalize it with .ended.

But for this simple gesture recognizer, once the user has tickled the object, that’s it – you just mark it as ended. The callback you will add to ViewController.swift will get called and you can implement the code there.

OK, now to use this new recognizer! Open ViewController.swift and make the following changes.

Add to the top of the class:

var hehePlayer:AVAudioPlayer? = nil

In viewDidLoad(), right after TODO, add:

let recognizer2 = TickleGestureRecognizer(target: self,
  action:#selector(handleTickle(recognizer:)))
recognizer2.delegate = self
view.addGestureRecognizer(recognizer2)

At end of viewDidLoad() add:

self.hehePlayer = self.loadSound(filename: "hehehe1")

Finally, create a new method at the end of the class:

@objc func handleTickle(recognizer: TickleGestureRecognizer) {
  self.hehePlayer?.play()
}

So you can see that using this custom gesture recognizer is as simple as using the built-in ones!

Compile and run and “he he, that tickles!”

UIGestureRecognizer Tutorial