UIKit Apprentice, Second Edition – Now Updated!

Learn iOS and Swift from scratch. Build four powerful apps—with support for iPad and Dark Mode. Publish apps to the App Store.

Home iOS & Swift Tutorials

Siri Shortcuts on Apple Watch

Learn how to take advantage of Siri and Shortcuts on the Apple Watch without any intervention required from the iOS companion app.

5/5 1 Rating

Version

  • Swift 5, iOS 14, Xcode 12

In iOS 14 and watchOS 7, Apple extended Shortcuts to the Apple Watch. Shortcuts can now run directly on the watch. While the phone is still necessary to create shortcuts, the watch can run them independently.

In this tutorial, you’ll:

  • Take advantage of shortcuts and Siri on the Apple Watch.
  • Create shortcuts for your app that run independently on the watch.
  • Enable Siri to function with your app through the watch, without any intervention required from the iOS companion app.
  • Add a shortcut for your app on the watch face.
Note: To follow along with this entire tutorial, you need:
  • A paid Apple Developer account.
    • This tutorial uses App Groups to share data between the apps and the intents you’ll build. Using App Groups requires some portal setup and a paid developer account.
  • A physical iPhone running iOS 14 and an Apple Watch running watchOS 7.
    • Although it’s possible to test the watch app from the simulator, it’s impossible to build and test intents on the simulator. To see your intents in action, you need a physical watch.

Please make sure you sign into Xcode with your developer account. This allows automatic provisioning to create all the necessary app identifiers and provisioning profiles as you work through this tutorial.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial. Open the app in the starter directory. This app assists in tracking your hydration intake throughout the day. It runs on the phone and the watch. Before you check it out, you need to make sure you have your simulator set up with a paired Apple Watch.

If you don’t already have an iPhone simulator paired with a watch, follow these steps:

  1. In Xcode, select Window ▸ Devices and Simulators.
  2. Select Simulators on the top left.
  3. Click + on the bottom left to add a new simulator.
  4. In the dialog, fill in the following information:
    • Simulator Name: Make this something that describes the combination of phone and watch.
    • Device Type: Select any device you like here.
    • OS Version: iOS 14.3 or the latest SDK.
    • Check Paired Apple Watch.

    Adding a new simulator dialog

  5. Click Next.
  6. In the second step, fill in the following information:
    • Paired Simulator Name: Anything that accurately describes the watch simulator you want to use.
    • Device Type: Select any device you like here.
    • OS Version: watchOS 7.2 or the latest SDK.

      Adding a new simulator dialog 2

  7. Click Create.
  8. Your newly created simulator pair will now appear in the Run Destination list:

    Run destination showing phone simulator with paired apple watch

Running the App

You can now run the app and access it from both the phone and watch simulators. First, check out the iOS app.

  1. Select Hydrator from the scheme menu and select your paired phone and watch simulator in the Run Destination menu.
  2. Build and run.
  3. Add a few hydration entries by tapping Create New Entry.
  4. Check out the list of entries on the main view.

iPhone Hydrator screen showing some entries

Next, check out the app on the watch.

  1. Switch the active scheme to HydratorWatch.
  2. Build and run again.
  3. Tap + and add some hydration entries to see them populate in the list.

Watch Simulator

Note: This article focuses on adding and running shortcuts on the Apple Watch. Data sync between the phone and watch is beyond the scope of this article. Data added to the iOS app is independent of the watch app.

The app uses frameworks and app groups to share some code between the iOS app, watch app and extensions. This work is complete already. You’re just pulling in the frameworks when necessary to read and save data.

Setting up Portal Requirements

The starter project uses .xcconfig files to propagate build settings across all targets.

Creating an App Group

Before you configure your project, you need to create an App Group.

  1. Log in to the developer portal using your account.
  2. Select Certificates, IDs & Profiles from the sidebar.
  3. Now, choose Identifiers and press the blue + button to create a new identifier.
  4. From the list of options available, select App Groups.
    App Group Config
  5. Fill in Description and Identifier. Note that the identifier will add group automatically at the beginning. Also, try to use reverse domain notation as recommended.
    App Group Configuration step 2

Configuring Your Account

Perform these setup steps to get going:

  1. In the Xcode Project navigator, find the Resources/config folder.
  2. Open base.xcconfig.
  3. Update the value of BASE_DOMAIN to include a unique string. Apple recommends reverse domain notation here, like com.yourcompanyname.
  4. Update the DEVELOPMENT_TEAM value, replacing YOUR-TEAM-ID-HERE with your Team ID from the developer portal. If you’re not sure what it is, log in to the Apple Developer portal. Click the Account tab and then select Membership from the left sidebar. Your Team ID is on this page.
    Apple Account Team ID
  5. Update GROUP_ID, replacing YOUR-GROUP-ID-HERE with the value you already created in your Apple account.
  6. Save the .xcconfig file by pressing Command-S.

Understanding Siri Shortcuts and Intents

When you build your app for Siri Shortcuts, you have two options: NSUserActivity-based shortcuts and intents-based shortcuts. By far, the more flexible and robust option is to use intents-based shortcuts. They give you more options in accepting parameters, conflict resolution and a robust object model for input.

Using NSUserActivity-Based Shortcuts

Shortcuts that use the NSUserActivity API require an app launch to function. They’re unable to execute requests in the background with Siri. In your watch app, NSUserActivity-based shortcuts will attempt to open the watch app to execute first. If there’s no watch app present, the shortcut fails.

Using Intents-Based Shortcuts

Intents-based shortcuts can run locally via an extension, through the watch app or remotely on the phone. An intents-based shortcut that starts on the watch follows this flow:

  1. First, if it’s possible to handle the shortcut on the watch — because an intents extension is present on the watch — then the watch handles the shortcut locally.
  2. If no intent extension is present on the watch, then the watch attempts remote execution on the paired iPhone.
  3. Next, if the shortcut requires an app launch, then the shortcut fails. Shortcuts started from the watch cannot launch their iOS app counterpart to execute.
  4. Lastly, if the extension can run in the background on the iOS device, it’s executed there.

Executing the App Remotely

Although remote execution offers a layer of flexibility when running shortcuts from the watch, it’s not the recommended approach. If possible, Apple recommends shipping a watch app with an intents extension to ensure shortcuts that start from the watch execute locally instead of running remotely. The main drawback to remote execution is time, as it takes significantly longer to run than a shortcut.

This chart illustrates an example of the decision flow between watchOS and iOS for executing shortcuts when they begin from the watch:

iOS and watchOS Shortcut Execution Flow

Adding an Intent-Based Shortcut

As discussed in the previous section, you’ll focus on intent-based shortcuts for this tutorial because of their flexibility and ability to run in a standalone fashion. You’ll ensure the shortcut can run without encountering an error state and can run locally on the device. For a watch app, that means the shortcut shouldn’t invoke remote execution to run partially on the phone. Skipping remote execution prevents costly wait times in end-to-end communication between the phone and watch apps.

The watchOS app can only run shortcuts created on iOS and marked for sync over to the watch. Because of this, you’ll create your intent and add it to iOS first. Once you have that up and running, you’ll sync it over to the watch and get it running there.

Everything starts with an intent definition file.

Adding the Intent Definition File

An intent definition file bridges available shortcut functionality from your app and makes it available to the Shortcuts app. Your users can configure shortcuts with a wide range of possibilities and plug into your app’s ability to accept input and perform actions. Add an intent definition file to the project:

  1. Open Project navigator by pressing Command-1.
  2. Select Shared ▸ Resources.
  3. Then, select File ▸ New ▸ File…
  4. Next, select the iOS filter at the top.
  5. Under the Resource section, select SiriKit Intent Definition File.
  6. Click Next.
  7. Name the file HydratorIntents.intentdefinition.
  8. Select all targets in the Targets section on the bottom.

    Targets to choose

Configuring Types

Intent types define objects that your intent expects as parameters. In the intents file, you have the option of defining custom types and enumerations to represent items in the shortcut flow. For this tutorial, you’ll define an enumeration to represent drink choices when tracking hydration intake.

Inside the intent, configure an enum to represent different drink options:

  1. With the new intent file selected, click + at the bottom and select New Enum.
  2. Name it HydrationType.
  3. Leave the Display Name as Hydration Type.
  4. Under Cases, add the following by clicking + for each new entry:
    • water, display name: Water.
    • coffee, display name: Coffee.
    • energyDrink, display name: Energy Drink.
    • tea, display name: Tea.
    • seltzer, display name: Seltzer.

    Enum Creation

Configuring a New Intent

Now, you’re ready to create a new intent. Per Apple, intent names should follow a VerbNoun convention.

Set up your new intent as follows:

  1. With the intent file selected, click + at the bottom and select New Intent.
  2. Name it TrackIntake.
  3. Under Category, select Create.
  4. Set the Title to Track Intake.
  5. Then, set Description to Track hydration intake.
  6. Leave the Confirmation and Widgets options unchecked.
  7. Leave the Configurable in Shortcuts and Suggestions options checked.

    Custom Intent Section

Setting up Intent Parameters

Intent parameters are a great addition to the SiriKit framework. Parameters make your shortcuts much more dynamic. The Shortcuts app can find parameters in your intent definition and make them available to users when setting up shortcuts into your app.

You’ll set up two parameters to handle hydration intake. Combining these two parameters will allow you to create a new log entry in the app from the shortcut.

Set up your first parameter as follows:

  1. Click + under the Parameters section.
  2. Name the parameter ounces.
  3. Leave Display Name as Ounces.
  4. Set the Type as Integer.
  5. Leave Configurable and Resolvable checked.
  6. Keep Default Value and Minimum Value at 0 and set Maximum Value to 200.
  7. Under Siri Dialog, set Prompt to Amount of intake.
  8. Leave Validation Errors as-is.

Ounces Parameter

Explaining Parameters

Here’s what those fields mean for the Siri and Shortcuts interface.

  • Display Name is the name that shows in the Shortcuts app as a prompt for input.
  • Type is the data type expected as the result of the parameter value.
  • The Configurable check allows the user to configure custom values for this parameter in the Shortcuts app.
  • The Input section defines default, minimum and maximum values for the integer input. For other input types, these fields will update appropriately.
  • The Siri Dialog prompt displays when Siri is prompting the user for this input parameter.
  • Validation Errors notify the user when an input parameter is invalid. They use tokens to represent error and data types. This allows you to create dynamic error messages for display to the user when a parameter is invalid.

Understanding More Parameters

Now, move on to the second parameter. This one is for the drink type. For this, use the DrinkType enum you defined earlier:

  1. Click + under Parameters.
  2. Name the parameter hydrationType.
  3. Leave Display Name as Hydration Type.
  4. Set type to Hydration Type.
  5. Under Input, set Default Value to Water.
  6. Under Siri Dialog, leave all values at defaults.

Defining Drink Type Parameter

There’s one difference between the ounces parameter and the hydrationType parameter. For ounces, validation errors are possible. The ounces value could be invalid due to a user input error. For hydrationType, the user selects from a generated list based on the enum. In that scenario, you’ll never have invalid data.

However, you could have a scenario where Siri is unable to distinguish what the user said for the selection. In this case, you can provide disambiguation prompts. When this happens, the user will see the text entered in the Disambiguation Prompt field. You’ll learn more about disambiguation when you create the intent handler for this intent.

Configuring the Shortcuts App

This section defines the user’s interaction experience with your shortcut actions in the Shortcuts app.

  1. Leave Input Parameter and Key Parameter set to None.
  2. Under Summary, enter Hydration type and amount.

Defining Suggestions

Finally, in the Suggestions section, set default image to rw-logo and set Summary to Hydration type and amount. These sections determine the prompt users will see when configuring your shortcut from a Siri suggestion.

Setting Intent Suggestions

Defining Siri’s Response

The response node defines how Siri will communicate the result of the shortcut back to the user.

  1. Select Response on the left sidebar.
  2. Under success, enter Item Added! for both fields: Voice-Only Dialog and Printed Dialog.
  3. Under failure, enter Sorry, but I couldn’t add your item. for both fields.

Adding the Intents Extensions

Next, you need to add an extension for the iOS and watchOS platforms. The extensions allow shortcuts to run outside of the app context. The app doesn’t have to be in the foreground for the shortcut to run when the extension is present. Intent extensions enable a lot of different types of interactions. Your users can add actions to shortcuts as part of a large, complex shortcut or as one-offs that just perform a single task. Now, your shortcut actions are available from the Shortcuts app or by voice via Siri.

  1. In Xcode, select File ▸ New ▸ Target…
  2. Select iOS at the top for the platform.
  3. Under the Application Extension category, select Intents Extension.

    Selecting Intent Extension

  4. Click Next.
  5. On the next screen, set Product Name to HydratorIntents.
  6. Select your team from the Team drop-down.
  7. Set Starting Point to None. Include UI Extension isn’t checked, Project is Hydrator and Embed in Application is set to Hydrator.
  8. Click Finish.

    Intent Dialog

  9. Now, repeat that process, but select watchOS for the platform and name the extension HydratorIntentsWatch.

    WatchOS Intent Extension

  10. Ensure the Embed in drop-down is set to HydratorWatch Extension.

New target options

Adding Framework Dependencies

Each extension will depend on an underlying framework for data management. You’ll add those now.

  1. Select each new target in Xcode.
  2. In Frameworks and Libraries, click + to add the relevant HydratorKit framework.
    • For HydratorIntents, add HydratorKit.

      HydratorKit

    • For HydratorIntentsWatch, add HydratorKitWatchOS.

HydratorKit Watch

Configuring for Builds

You need to perform some maintenance work to make the new extensions work with the .xcconfig files you learned about earlier.

  1. In Project navigator, select the Hydrator project node.
  2. In Projects, select Hydrator.
  3. Select the Info tab.
  4. Under Configurations, in the middle pane, expand Debug and Release.
  5. For HydratorIntents and HydratorIntentsWatch, select the appropriate .xcconfig file from the drop-down menu.

    XCConfig Setup

Configuring Identifiers

The data storage for the extensions uses app groups to write to a shared location. You need to set up app groups for the new targets for this to work.

  1. In Project navigator, select the Hydrator project node.
  2. Select the HydratorIntents target.
  3. Select the Signing & Capabilities tab on top.
  4. Click + Capability.
    Add Capability
  5. In the menu, select App Groups and click Enter.
  6. Repeat this process for the HydratorIntentsWatch target.
  7. This process generates an entitlements file inside each respective extension group.

Enabling Entitlements Files

For each entitlements file, perform the following steps:

  1. Select the entitlements file in Project navigator.
  2. Expand the App Groups node.
  3. Click + to add a new entry to the array.
  4. In the Value column, add the variable $(GROUP_ID).

    Adding group ID to the entitlements files

Now your Group ID maps to the variable in the .xcconfig files.

Updating Info.plist Files

Next, you need to update the Info.plist files in the new targets to declare support for the intent.

In each plist for the new targets, do the following:

  1. Open NSExtension.
  2. Next, open NSExtensionAttributes.
  3. Then, open IntentsSupported.
  4. Click + to add a new array item.
  5. Add TrackIntakeIntent as the value for the new item.

Plist Intent Support

This step allows the system to invoke the right class when Siri uses the intent. Next, you’ll move on to code generation for your intent and creating a shared handler.

Generating Intent Code

Now, you need to set up code generation options for the intent. Xcode can create the correct classes for you based on your intent setup. You’ll interact with these generated classes when you create the shared intent handler.

  1. Select the intent definition file in Project navigator following the path: Hydrator ▸ Shared ▸ Resources.
  2. Open File inspector by pressing Command-Option-1.
  3. Under Target Membership, select all targets.
  4. The property to the right of each target is the code generation scheme.

    Set up the following:

    • For the Hydrator, HydratorWatchExtension, HydratorIntents and HydratorIntentsWatch targets, ensure No Generated Classes is selected.
    • HydratorWatch won’t allow any changes and is set to No Role.
    • For HydratorKit and HydratorKitWatchOS, select Public Intent Classes.

The code generation for each target

Sharing Intent Handling

You’ll set up both intent extensions to use the same code to centralize intent handling in one place. You can now use the types generated from your intent definition file.

  1. Navigate to the Shared group in Project navigator.
  2. Open Source inside it.
  3. Create a new group named Intents.
  4. Create a new Swift file named TrackIntakeIntentHandler.swift.
  5. In Target Membership for this file, select HydratorIntents and HydratorIntentsWatch. Deselect all other targets.

Now you’re ready to set up your shared intent handling code. At the top of the file, add the following code:

#if os(iOS)
import HydratorKit
#else
import HydratorKitWatchOS
#endif

The compiler directive above selectively imports the correct framework depending on which platform is active.

Next, create the class declaration:

class TrackIntakeIntentHandler: NSObject, TrackIntakeIntentHandling {
}

You’ll get an error that the class does not conform to TrackIntakeIntentHandling. This is your first use of one of the generated classes resulting from your intent definition file. Command-click into the definition of this protocol to see the generated header, and you’ll see something like this:

@available(iOS 12.0, macOS 11.0, watchOS 5.0, iOS 12.0, *)
@available(tvOS, unavailable)
@objc(TrackIntakeIntentHandling) public protocol TrackIntakeIntentHandling : NSObjectProtocol {

    @objc(handleTrackIntake:completion:) func handle(intent: HydratorKit.TrackIntakeIntent, completion: @escaping (HydratorKit.TrackIntakeIntentResponse) -> Void)

    @available(iOS 13.0, macOS 11.0, watchOS 6.0, *)
    @objc(resolveOuncesForTrackIntake:withCompletion:) func resolveOunces(for intent: HydratorKit.TrackIntakeIntent, with completion: @escaping (HydratorKit.TrackIntakeOuncesResolutionResult) -> Void)

    @available(iOS 13.0, macOS 11.0, watchOS 6.0, *)
    @objc(resolveHydrationTypeForTrackIntake:withCompletion:) func resolveHydrationType(for intent: HydratorKit.TrackIntakeIntent, with completion: @escaping (HydratorKit.HydrationTypeResolutionResult) -> Void)

    @objc(confirmTrackIntake:completion:) optional func confirm(intent: HydratorKit.TrackIntakeIntent, completion: @escaping (HydratorKit.TrackIntakeIntentResponse) -> Void)
}

Back in TrackIntakeIntentHandler.swift, click Fix in the error message. Xcode will generate the appropriate method stubs for you.

Resolving Parameters

Inside resolveOunces(for:with:), add the following code:

// 1
guard
  let ounces = intent.ounces?.intValue,
  ounces != 0
else {
  let result = TrackIntakeOuncesResolutionResult.needsValue()
  completion(result)
  return
}

// 2
let result = TrackIntakeOuncesResolutionResult.success(with: ounces)
completion(result)

Here’s what you’re doing:

  1. Check for the ounces variable, cast to Int and make sure it isn’t zero. If any of these checks fail, return a needsValue() result, which will invoke the disambiguation response you set up in your intent handler.
  2. If all conditions are satisfied, return a success result.

Next, inside resolveHydrationType(for:with:), add the following code:

// 1
let hydrationValue = intent.hydrationType.rawValue
guard 
  let hydrationType = HydrationType(rawValue: hydrationValue), 
  hydrationType != .unknown 
else {
  let result = HydrationTypeResolutionResult.needsValue()
  completion(result)
  return
}

// 2
let result = HydrationTypeResolutionResult.success(with: hydrationType)
completion(result)

Here’s what you’re doing:

  1. Get rawValue from the enum passed in the arguments. Attempt to create HydrationType from Int and ensure it doesn’t come back .unknown. If any of these checks fail, return a needsValue() result, which will start the disambiguation flow.
  2. If all conditions are satisfied, return a success result.

Once you satisfy all those conditions, handle(intent:completion:) gets called.

Fill that method out now with the following code:

// 1
let hydrationValue = intent.hydrationType.rawValue
// 2
let drink = DrinkType(rawValue: hydrationValue)
// 3
guard 
  let drinkType = drink,
  let ounces = intent.ounces?.intValue 
else {
  let response = TrackIntakeIntentResponse(
    code: .failure,
    userActivity: nil)
  completion(response)
  return
}

// 4
let item = LogEntry(drinkType: drinkType, ounces: ounces)
let dataManager = DataManager()
dataManager.save(entry: item)

// 5
let response = TrackIntakeIntentResponse(
  code: .success,
  userActivity: nil)
completion(response)

Here’s what you’re doing above:

  1. Get the hydration value from the shortcut’s input.
  2. Create a DrinkType from the value.
  3. Check to ensure you have valid DrinkType and ounces input, otherwise call completion with a failure result.
  4. Create LogEntry and saving it to the data store.
  5. Call completion with a successful response.

Note that the completion handler argument is using the generated types from your intent definition file.

Connecting the Handlers

Now, you have to invoke the shared handler from your intents extensions. In each extension, Xcode generated a file for you named IntentHandler.swift. Open each one and update the body of handler(for:) to the following:

return TrackIntakeIntentHandler()

The code above creates a new intent handler for any shortcut invoked from your app on iOS or watchOS.

Testing Your Intent

That was a lot of setup, but now you can test your intent in a shortcut!

Follow these steps to see your new intent in action:

  1. Select the Hydrator scheme. Build and run to your physical device.
  2. Open the Watch app on your phone and install the Hydrator app on the watch.
  3. Open the Shortcuts app on the phone.
  4. Tap + at the top of the screen to create a new shortcut.
  5. Next, tap Add Action.
  6. Then, tap Apps.
  7. Scroll down to find Hydrator and select it.
  8. Tap Track Intake.
  9. Tap Show More to expand options. You should see Ounces and Hydration Type appear.
  10. For each option, tap into the value and select Ask Each Time from the keyboard accessory view.
    Hydrate Me app Hydration type and amount entry
  11. Tap the ellipsis (⋯) button on the top right to open the shortcut’s settings.
  12. Name the shortcut Hydrate Me and ensure Show on Apple Watch is enabled.

Hydrate Me app add shortcut to Apple watch

  1. Tap Done to return to the shortcut, then tap Done again to save your shortcut.
  2. On your watch, open the Shortcuts app. You should see the Hydrate Me shortcut you just created near the top.

Hydrate Me app on Apple Watch Shortcut

  1. Tap to run it and answer the prompts.

Hydrate Me app Ounces Input

Hydrate Me app Drink List

Hydrate Me app Shortcut Success

Testing With Voice

Next, try running your shortcut solely by voice.

  1. Push the digital crown to go back to the watch face.
  2. Hold in the crown or say “Hey, Siri” to invoke Siri.
  3. Say “Hydrate me”.
  4. Now, you should get voice prompts for both input items.

    Hydrate Me app Amount of Intake Prompt

    Hydrate Me app Drink List

    Hydrate Me app Confirmation Message

Validating Input

Now, open the Hydrator app on the watch. Tap refresh on the bottom right. You should see all the items you added via shortcuts now show up in the app!

Hydrate Me app Watch List

Adding Your App to the Watch Face

Finally, watchOS 7 adds the ability to put shortcuts directly on the watch face as a complication. Adding the Shortcuts complication to your watch face gives you the ability to select a specific shortcut to run when you tap that complication. Now your users have one-tap access to your shortcut!

Hydrate Me app on Watch Face

Where to Go From Here?

You can download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

Shortcuts are an exciting and dynamic addition to Apple’s platforms. In this tutorial, you learned how to build shortcuts to run locally on the watch. You also learned to avoid remote execution by ensuring you build your intent extension for watchOS.

You can also investigate many related areas, including customization options for your flows, donating to the system, confirmation flows and more complicated disambiguation flows. Apple has released extensive documentation for SiriKit. We also have an excellent tutorial on SiriKit to help you take your shortcuts to the next level.

Take all these tools and see what kind of incredible experiences you can build for your users.

We hope you enjoyed this tutorial, and if you have any questions or comments, please join the forum discussion below!

Average Rating

5/5

Add a rating for this content

1 rating

More like this

Contributors

Comments