Are Singletons Bad

When I first started dabbling with Cocoa development, I almost immediately came into contact with the singleton pattern. Many Cocoa frameworks, including UIKit and Foundation, use the singleton pattern.

What Is the Singleton Pattern

The singleton pattern is an easy to understand design pattern. I recommend reading or watching What Is a Singleton and How To Create One In Swift if you’d like to learn more. In a nutshell, the singleton pattern ensures only one instance of a class is instantiated for the lifetime of the routine or the application.

The singleton pattern ensures only one instance of a class is instantiated for the lifetime of the routine or the application.

The Cocoa frameworks often refer to a shared object or a shared instance. Take a look at these examples. URLSession and UserDefaults are both defined in the Foundation framework.

// Shared Session Object
let session = URLSession.shared

// Shared Defaults Object
let userDefaults = UserDefaults.standard

With the definition of the singleton pattern in mind, it appears to be a useful design pattern. Unfortunately, many developers misuse the singleton pattern and use it to conveniently access an object from anywhere in the project. Having global access to the singleton object is no more than a side effect of the singleton pattern. It’s not what the singleton pattern is about.

Singletons Everywhere

Whenever I talk about singletons, I like to bring up Maslow’s hammer analogy.

I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail. — Abraham Maslow

A surprising number of developers struggle with this problem. The singleton pattern appears to be solving a common problem, accessing an object in various places of the project. A singleton, no more than a fancy global, looks like the perfect fit for the problem. And it is the perfect fit if all you want to do is solve that problem. There’s more to the story, though.

Have you ever wondered why some experienced developers consider the singleton pattern an anti-pattern? Why is it that such a useful pattern is often avoided by more senior developers?

Sacrificing Transparency for Convenience

By using singletons, you almost always sacrifice transparency for convenience. But how much are you willing to sacrifice for that little bit of convenience? Convenience should not be high on your priority list if you’re working on a software project. Let me show you with an example what the problem is.

By using singletons, you almost always sacrifice transparency for convenience.

A common problem in software projects is user management. It means that a user object needs to be created and managed. There are many solutions to this problem. If you’re a fan of the singleton pattern, then you might create a singleton or a manager class that manages the currently signed in user. Only a single user can be signed in at a time. Right?

Problem solved? It’s true that you can now access the currently signed in user from anywhere in your project. While this may seem like the best thing since sliced bread, it introduces several problems.

Consider the following question. Which object is allowed to modify the user object? That’s easy. The manager object managing the user. But isn’t the manager object accessible from anywhere in the project? It implies that the user object can be modified from anywhere in your project. If you’re still with me, then I hope this is starting to sound less and less like the great idea it initially appeared to be.

How are you going to make sure that the objects that depend on the currently signed in user are notified when the user object is modified? That’s easy. Notifications. Another popular option is to use a pattern similar to Java’s observer pattern. You could also use key value observing, but do you really want to observe the properties of an object that’s managed by a singleton? That sounds like asking for trouble.

Losing Track of Everything

I agree that the singleton pattern seems convenient and it may occasionally be warranted to use it, but the drawbacks far outweigh the benefits. Unfortunately, the drawbacks are very subtle at first and that’s what misleads many, many developers.

Unfortunately, the drawbacks are very subtle at first and that’s what misleads many, many developers.

The most important drawback of the singleton pattern is sacrificing transparency for convenience. Consider the earlier example. Over time, you lose track of the objects that access the user object and, more importantly, the objects that modify its properties.

The initial advantage of using a singleton, convenience, becomes the most important problem. Ironic. Isn’t it? By using singletons in your project, you start to create technical debt. Singletons tend to spread like a virus because it’s so easy to access them. It’s difficult to keep track of where they’re used and getting rid of a singleton can be a refactoring nightmare in large or complex projects.

Once the singleton pattern becomes an accepted practice in a project, it’s difficult to move the project forward without using even more singletons. The structure of the project may seem flexible and loosely coupled, but it’s anything but that.

Substituting Singletons With Dependency Injection

As I mentioned earlier, singletons have the tendency to spread like a virus. The cure to solve singleton disease is dependency injection. While a key advantage of the singleton pattern is convenience, the most important advantage of dependency injection is transparency. Transparency is good.

If an object requires a valid user object to do its job, then that user object should be injected as a dependency. It’s easy to translate this requirement into code. Take a look at this example.

class User {
    var firstName = ""
    var lastName = ""
}

class NetworkController {

    let user: User

    init(user: User) {
        self.user = user
    }

}

This snippet shows any developer working with the NetworkController class that it depends on a valid User instance. This is also known as passing an object by reference. It’s less convenient than using a singleton, but it pays dividends in the long run. It adds clarity to the codebase, unambiguously showing which objects depend on which objects.

It’s an advantage that passing an object by reference is less convenient. Why is that? It forces you to carefully consider your decision. Does the NetworkController class need access to the user object? Would it suffice to pass the user’s username and password instead? Dependency injection can be a very useful tool to define the requirements of a type.

Dependency injection can be a very useful tool to define the requirements of a type.

Moving Away From Singletons

A few years ago, I started working on a client project that was littered with singletons. Some singletons even referenced other singletons, adding to the problem. After several rounds of refactoring, I managed to eliminate most of the singletons from the project, which drastically increased transparency and robustness.

Have you ever worked on a project nobody in your team wanted to touch? Modifying a handful of lines in one class could cause mayhem in a distant, unrelated component of the project. This is not uncommon for projects that make heavy use of singletons.

Another benefit of ridding a project of singletons relates to testing. Writing unit tests without having to worry about singletons is fantastic. It really is. Combine this with dependency injection and you have a great combination to work with.

Are Singletons Bad

My stance on singletons varies from day to day. If I had a rough day battling singletonitis, I dislike them with a passion. The truth is that singletons aren’t inherently bad if they’re used correctly. The goal of the singleton pattern is to ensure only one instance of a class is alive at any one time. That, however, is not the goal many developers have in mind when using singletons.

Singletons are very much like the good things in life, they’re not bad if used in moderation. The next time you’re about to create a singleton, consider your motivation for creating one. Is it convenience? Then you shouldn’t create the singleton. Period. That’s the simple rule I apply.

If you want to learn more about dependency injection, then I recommend reading or watching Nuts and Bolts of Dependency Injection in Swift. It shows you how you can use dependency injection in your own projects.

Advertisements
Are Singletons Bad

What Is a Singleton and How To Create One In Swift

The singleton pattern is a widely used design pattern in software development. Despite its popularity, it’s often considered an anti-pattern. Why is that? In this episode, I explain what the singleton pattern entails and how to create singletons in Swift.

What Is a Singleton

Singletons are easy to understand. The singleton pattern guarantees that only one instance of a class is instantiated. That’s simple. Right?

The singleton pattern guarantees that only one instance of a class is instantiated.

If you’ve worked with Apple’s frameworks, then chances are that you’ve already used the singleton pattern. Take a look at these examples. They probably look familiar.

// Shared URL Session
let sharedURLSession = URLSession.shared

// Default File Manager
let defaultFileManager = FileManager.default

// Standard User Defaults
let standardUserDefaults = UserDefaults.standard

// Default Payment Queue
let defaultPaymentQueue = SKPaymentQueue.default()

The singleton pattern is a very useful pattern. There are times that you want to make sure only one instance of a class is instantiated and that your application only uses that instance. That’s the primary and only goal of the singleton pattern.

The default payment queue of the StoreKit framework is a fine example. An application should never create an instance of the SKPaymentQueue class. The operating system uses the StoreKit framework to create a payment queue, which your application can use. The default payment queue is accessible through the default() class method of the SKPaymentQueue class. This is a good example of how the singleton pattern should be applied.

Global Access

But the singleton pattern has a side effect that’s often the true reason for adopting the singleton pattern, global access. But having global access to the singleton object is no more than a side effect of the singleton pattern.

Unfortunately, many developers use the singleton pattern to have easy access to the singleton object from anywhere in their project. The default payment queue is accessible through the default() class method. This means that any object in a project can access the default payment queue. While this is convenient, that convenience comes at a price.

If you want to learn more about the problems surrounding the singleton pattern, then I recommend reading Are Singletons Bad. In that article, I discuss this topic in more detail.

How to Create a Singleton In Swift

In this episode, I list two recipes for implementing the singleton pattern in Swift. The first implementation shouldn’t be used, though. It merely illustrates a few concepts of the Swift language.

Global Variables

The most straightforward technique to create a singleton is by defining a global variable.

let sharedNetworkManager = NetworkManager(baseURL: API.baseURL)

class NetworkManager {

    // MARK: - Properties

    let baseURL: URL

    // Initialization

    init(baseURL: URL) {
        self.baseURL = baseURL
    }

}

By defining a variable in the global namespace of the project, any object in the module has access to the singleton object. We could, for example, access the singleton object in the application(_:didFinishLaunchingWithOptions:) method of the AppDelegate class.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    print(sharedNetworkManager)
    return true
}

In Swift, global variables are initialized lazily. This means that the initializer is run the first time the global variable is referenced.

An added benefit of Swift’s approach is that the initialization is performed using the dispatch_once function. It guarantees that the initializer is invoked only once. That’s important since you only want to initialize the singleton object once.

Using a global variable has several shortcomings. The most important problem is cluttering the global namespace. Another downside is that the initializer of the NetworkManager class cannot be declared private. This means that multiple instances of the class can be instantiated. Let me show you a much better, and my preferred, implementation in Swift.

Static Property and Private Initializer

A few years ago, Swift introduced static properties and access control to the language. This opened up an alternative approach to implementing the singleton pattern in Swift. It’s much cleaner and elegant than using a global variable. Take a look at the updated example.

class NetworkManager {

    // MARK: - Properties

    static let shared = NetworkManager(baseURL: API.baseURL)

    // MARK: -

    let baseURL: URL

    // Initialization

    private init(baseURL: URL) {
        self.baseURL = baseURL
    }

}

Accessing the singleton is intuitive and it clearly conveys that we’re dealing with a singleton.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    print(NetworkManager.shared)
    return true
}

Several implementation details have changed. First, the initializer is private. It means that only the NetworkManager class can create instances of itself. That’s a significant advantage.

Second, we declared the shared static constant property. This property gives other objects access to the singleton object of the NetworkManager class.

It isn’t necessary to mark static properties with the lazy keyword. Remember what I said earlier, the initializer of global variables and static properties are executed lazily by default. That’s another benefit.

I want to share one more implementation of the singleton pattern. This implementation is a bit more complex. The main difference is that the singleton object is instantiated in a closure, allowing for a more complex initialization and configuration of the singleton object.

class NetworkManager {

    // MARK: - Properties

    private static var sharedNetworkManager: NetworkManager = {
        let networkManager = NetworkManager(baseURL: API.baseURL)

        // Configuration
        // ...

        return networkManager
    }()

    // MARK: -

    let baseURL: URL

    // Initialization

    private init(baseURL: URL) {
        self.baseURL = baseURL
    }

    // MARK: - Accessors

    class func shared() -> NetworkManager {
        return sharedNetworkManager
    }

}

The static property is declared private. The singleton object is accessible through the shared() class method.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    print(NetworkManager.shared())
    return true
}

Cocoa and Singletons

With these implementations in mind, we have mimicked the interface many Cocoa frameworks have adopted in Swift.

// Shared URL Session
let sharedURLSession = URLSession.shared

// Default File Manager
let defaultFileManager = FileManager.default

// Standard User Defaults
let standardUserDefaults = UserDefaults.standard

// Default Payment Queue
let defaultPaymentQueue = SKPaymentQueue.default()

Are Singletons Bad

In Are Singletons Bad, I explain in detail what type of problems the singleton pattern can introduce in a project. My advice is to use singletons sparingly. Very sparingly. If you’re about to create a singleton, take a moment and step back. Is there another option you may be overlooking? Is it absolutely necessary to use the singleton pattern?

Even though there’s nothing inherently wrong with singletons, most developers use it for the wrong reasons, that is, convenience. They disguise global variables as singletons.

Dependency Injection

Even if you decide to use singletons in a project, that doesn’t mean you have to access them from anywhere in your project. You can still use dependency injection to pass the singleton object to the objects that need it.

By adopting dependency injection to pass singletons around, the interface of your class remains clear and transparent. In other words, the interface of the class describes its dependencies. This is very, very useful. It immediately shows which objects the class needs to perform its duties.

One of the things I miss about Objective-C is the header file. The header file summarizes the public interface of a class, including the dependencies of the class. This is sometimes less obvious in Swift.

What Is a Singleton and How To Create One In Swift

Nuts and Bolts of Dependency Injection in Swift

My favorite quote about dependency injection is a quote by James Shore. It summarizes much of the confusion that surrounds dependency injection.

“Dependency Injection” is a 25-dollar term for a 5-cent concept. – James Shore

When I first heard about dependency injection, I figured it was a technique too advanced for my needs at that time. I could do without dependency injection, whatever it was.

What Is Dependency Injection

I later learned that dependency injection is a simple concept when it’s reduced to its bare essentials. James Shore offers a succinct and straightforward definition of dependency injection.

Dependency injection means giving an object its instance variables. Really. That’s it. – James Shore

For developers new to dependency injection, it’s important to learn the basics before relying on a framework or library. Start simple. Chances are that you already use dependency injection without realizing it.

Dependency injection is nothing more than injecting dependencies into an object instead of tasking the object with the responsibility of creating its dependencies. Or, as James Shore puts it, you give an object its instance variables.

An Example

Let me show you an example. In this example, we define a UIViewController subclass that declares a property, requestManager, of type RequestManager?.

import UIKit

class ViewController: UIViewController {

    var requestManager: RequestManager?

}

We can set the value of the requestManager property one of two ways.

Without Dependency Injection

The first option, without dependency injection, is to task the ViewController class with the instantiation of the RequestManager instance. We can make the property lazy or initialize the request manager in the view controller’s initializer. That’s not the point, though. The point is that the view controller is in charge of creating the RequestManager instance.

import UIKit

class ViewController: UIViewController {

    var requestManager: RequestManager? = RequestManager()

}

This means that the ViewController class not only knows about the behavior of the RequestManager class. It also knows about its instantiation. That’s a subtle but important detail.

With Dependency Injection

The second option, with dependency injection, is to inject the RequestManager instance into the ViewController instance. Even though the end result may appear identical, it isn’t. By injecting the request manager, the view controller doesn’t know how to instantiate the request manager.

// Initialize View Controller
let viewController = ViewController()

// Configure View Controller
viewController.requestManager = RequestManager()

Many developers immediately discard the second option because it’s cumbersome and unnecessarily complex. But if you consider the benefits, dependency injection becomes more appealing.

Another Example

I’d like to show you another example to emphasize the point I made earlier. Take a look at this example.

import Foundation

protocol Serializer {

    func serialize(data: AnyObject) -> Data?

}
import Foundation

class RequestSerializer: Serializer {

    func serialize(data: AnyObject) -> Data? {
        ...
    }

}
class DataManager {

    var serializer: Serializer? = RequestSerializer()

}

The DataManager class has a property, serializer, of type Serializer?. In this example, Serializer is a protocol. The DataManager class is in charge of instantiating an instance of a type that conforms to the Serializer protocol, the RequestSerializer class in this example.

Should the DataManager class know how to instantiate an object of type Serializer? Take a look at this example to show the power of protocols and dependency injection.

// Initialize Data Manager
let dataManager = DataManager()

// Configure Data Manager
dataManager.serializer = RequestSerializer()

The DataManager class is no longer in charge of instantiating the RequestSerializer class. It no longer assigns a value to its serializer property. In fact, we can replace RequestSerializer with another type as long as it conforms to the Serializer protocol. The DataMananger class no longer knows or cares about these details.

What Do We Gain

I hope that these examples have at least captured your attention. Let me list a few additional benefits of dependency injection.

Raise Transparency

By injecting the dependencies of an object, the responsibilities and requirements of a class or structure become more clear and transparent. By injecting a request manager into a view controller, we understand that the view controller depends on the request manager and we can assume the view controller is responsible for request managing and/or handling.

Improve Testability

Unit testing is much easier with dependency injection. Dependency injection allows developers to replace an object’s dependencies with mock objects, which makes isolating behavior and setting up unit tests easier and less complicated.

import Foundation

class MockSerializer: Serializer {

    func serialize(data: AnyObject) -> Data? {
        ...
    }

}
// Initialize Data Manager
let dataManager = DataManager()

// Configure Data Manager
dataManager.serializer = MockSerializer()

Separation of Concerns

As I mentioned and illustrated earlier, another subtle benefit of dependency injection is a stricter separation of concerns. The DataManager class in the example isn’t responsible for instantiating the RequestSerializer instance. It doesn’t need to know how to do this.

Even though the DataManager class is concerned with the behavior of its serializer, it isn’t and shouldn’t be concerned with its instantiation. What if the RequestManager of the first example also has a number of dependencies. Should the ViewController class be aware of those dependencies too?

Looser Coupling

The example of the DataManager class illustrated how the use of protocols and dependency injection can reduce coupling in a project. Protocols are incredibly useful and versatile in Swift. The example we discussed is a scenario in which protocols really shine.

Types of Dependency Injection

Most developers consider three forms or types of dependency injection:

  • dependency injection through an initializer (initializer injection)
  • dependency injection using properties (property injection)
  • dependency injection in methods (method injection)

These types shouldn’t be considered equal, though. Let me list the pros and cons of each type.

Initializer Injection

I prefer to pass dependencies during the initialization of an object because this has several key benefits. The most important benefit is that dependencies passed in during initialization can be made immutable. This is very easy to do in Swift by declaring the properties for the dependencies as constants. Take a look at this example.

class DataManager {

    private let serializer: Serializer

    init(serializer: Serializer) {
        self.serializer = serializer
    }

}
// Initialize Request Serializer
let serializer = RequestSerializer()

// Initialize Data Manager
let dataManager = DataManager(serializer: serializer)

The only way to set the serializer property is by passing it as an argument during initialization. The init(serializer:) method is the designated initializer and guarantees that the DataManager instance is correctly configured. Another benefit is that the serializer property cannot be mutated.

Because we’re required to pass the serializer as an argument during initialization, the designated initializer clearly shows what the dependencies of the DataManager class are.

Property Injection

Dependencies can also be injected by declaring an internal or public property on the class or structure that requires the dependency. This may seem convenient, but it adds a loophole in that the dependency can be modified or replaced. In other words, the dependency isn’t immutable.

import UIKit

class ViewController: UIViewController {

    var requestManager: RequestManager?

}
// Initialize View Controller
let viewController = ViewController()

// Configure View Controller
viewController.requestManager = RequestManager()

Method Injection

Dependencies can also be injected whenever they’re needed. This is easy to do by declaring a method that accepts the dependency as an argument. In this example, the serializer isn’t a property on the DataManager class. Instead, the serializer is injected as an argument of the serializeRequest(_:withSerializer:) method.

import Foundation

class DataManager {

    func serializeRequest(request: Request, withSerializer serializer: Serializer) -> Data? {
        ...
    }

}

Even though the DataManager class loses some control over the dependency, the serializer, this type of dependency injection introduces flexibility. Depending on the use case, we can choose what type of serializer to pass into serializeRequest(_:withSerializer:).

There’s another minor benefit. The DataManager class doesn’t hold a reference to the serializer. This is a subtle benefit that reduces the complexity of the DataManager class.

Singletons

Dependency injection is a pattern that can be used to eliminate the need for singletons in a project. I’m not a fan of the singleton pattern and I avoid it whenever possible. Even though I don’t consider the singleton pattern an anti-pattern, I believe they should be used very sparingly. The singleton pattern increases coupling whereas dependency injection reduces coupling.

Too often, developers use the singleton pattern because it’s an easy solution to an often trivial problem. Dependency injection, however, adds clarity and transparency to a project. By injecting dependencies during the initialization of an object, it becomes clear which dependencies the target class or structure has, and it also reveals some of the object’s responsibilities.

Give It a Try

Dependency injection is one of my favorite patterns because it helps me stay on top of complex projects. This pattern has so many benefits. The only drawback I can think of is the need for a few more lines of code. That’s a drawback I’m happy to accept.

Nuts and Bolts of Dependency Injection in Swift

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!

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