Siri Shortcuts Tutorial in iOS 12

In this iOS 12 tutorial, you’ll learn how to build Siri Shortcuts for your app to surface in Spotlight as well as command Siri with your voice. By Zaphod Beeblebrox.

3.9 (20) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Setting Up Siri’s Responses

Click on Response so you can define how Siri will respond to the user.

Once again, configure your responses like this:

PostArticleResponse definition

Under Properties you can once again define the dynamic parts of what Siri will say. Add properties for title, publishDate and failureReason; make them all strings.

Then, under Response Templates, add this template for failure:

Sorry, but I couldn't post your article. ${failureReason}

And this template for success:

Your article "${title}" was successfully posted on ${publishDate}. Nice work!

Adding a Siri Extension

To pull off the trick of being able to stay in Siri’s UI without launching your app, you need to create an Intents Extension with code that can manage the interaction.

Click on the project file in the upper left-hand corner, then find the plus symbol that allows you to add a new target.

Add Intents target

Now, find the Intents Extension target; you can search for it in the Filter search bar.

select Intents extension

Then, name your intents extension WritingIntents, set Starting Point to None, and uncheck the Include UI Extension option.

Finally, click the Finish button to create your extension. Build and run to make sure things still work.

Note: When Xcode offers to activate the WritingIntents scheme, click Cancel.

Nothing new to see, but now you’re ready to use your custom intents!

There are two quick things you’ll need to do before moving on.

First, make sure ArticleIntents.intentdefinition is visible to the extension.

Open the file, then look in the File inspector. Make sure its Target Membership includes the app, framework and extension. Also, make sure to change the code generation option to No Generated Classes for the app and extension targets since this code should live in the framework.

Next, your extension and main app need to share an app group. Since articles are saved to and loaded from disk, this is the only way both targets can share the same area on the file system.

Click on the project file in the Project navigator, make sure you have TheBurgeoningWriter selected and go to the Capabilities tab.

Switch the App Groups capability to ON, and name the group group.<your-bundle-id>.

enable app group on main project

Next, select the WritingIntents extension and do the same thing. This time, the group should exist so you can check simply the box.

enable app group on intents extension

Finally, open ArticleManager.swift and locate the declaration for groupIdentifier. Change its value to match your newly defined app group name.

Note: The previous articles you’ve entered will no longer appear in the app after this change. This is expected since they’re stored in a different spot in the file system.

Donating Post Article Intents

Now that you’ve defined an intent for posting articles, it’s time to create and donate one at the appropriate time.

Once again, head to Article.swift so you can add a method for generating “post” intents for new articles.

Below your definition of newArticleShortcut(thumbnail:), add the following method definition:

public func donatePublishIntent() {

}

This method creates and donates the intent all at once since you don’t need to deal with adding intents to a view controller.

Now, create the intent object and assign an article and publish date to it:

let intent = PostArticleIntent()
intent.article = INObject(identifier: self.title, display: self.title)
intent.publishDate = formattedDate()

If you’re wondering where this class came from, Xcode generated it for you when you created the ArticleIntents.intentdefinition file.

An INObject is a generic object you can use to add custom types to your intent. In this case, you’re just giving it an identifier and the display value of the article’s title.

Next, create an interaction from this intent:

let interaction = INInteraction(intent: intent, response: nil)

When using a custom intent, the thing that you’ll end up donating to the system is an interaction like this one.

Finally, do the donation by calling donate(_:) on the interaction:

interaction.donate(completion: nil)

Here, you’re donating your interaction without worrying about the completion block. You can, of course, add error handling or whatever else you want to this completion block.

You must complete one last “secret handshake” step: You must tell iOS exactly what intents your app supports. To do this, click on the project file in the Project navigator. Select the WritingIntents target and click the Info tab. Option-click the disclosure triangle next to the NSExtension key to open the entire key. Hover over IntentsSupported to reveal the plus button and click it once. Set the value of the newly added item to PostArticleIntent.

list supported intents

That’s it! That’s all there is to donating an intent-based shortcut to the system.

Now that you’ve got the method defined, go to NewArticleViewController.swift and find saveWasTapped(). Since you want the system to prompt users to post articles that they’ve saved for later, this is where you’ll make it happen.

Add this line to donate the intent below the comment in that method:

article.donatePublishIntent()

Now that you’re donating, build and run the app. Then, create and save a new article. After you’ve done so, go to Spotlight search, and you should see a new donation that looks something like this.

Handling Intents-Based Shortcuts

Like before, you now have to think about handling this shortcut when the user has used it.

This time, the extension you created will be responsible for handling things.

First, add a new Swift file in the WritingIntents folder named PostArticleIntentHandler.swift.

Replace import Foundation with this:

import UIKit
import ArticleKit

class PostArticleIntentHandler: NSObject, PostArticleIntentHandling {
  func confirm(intent: PostArticleIntent, 
               completion: @escaping (PostArticleIntentResponse) -> Void) {

  }

  func handle(intent: PostArticleIntent, 
              completion: @escaping (PostArticleIntentResponse) -> Void) {

  }
}

Here, you’re creating the class that handles responding to interactions involving your post article intent.

Conforming to the PostArticleIntentHandling protocol means that you need to implement one method involving the confirmation step and one method for handling the intent after the user has confirmed.

Next, add the following code to confirm(intent:completion:):

completion(PostArticleIntentResponse(code: PostArticleIntentResponseCode.ready,
                                     userActivity: nil))

This indicates that if the user taps confirm, then the extension is ready to take on the intent.

Next, you’ll implement handle(intent:completion:).

This is where the real choices come into play. Since the user is trying to post an article, you should only respond with a success message if it works.

First, add this guard statement for when the article they chose isn’t found:

guard let title = intent.article?.identifier,
    let article = ArticleManager.findArticle(with: title) else {
        completion(PostArticleIntentResponse
          .failure(failureReason: "Your article was not found."))
        return
}

This calls the completion block with a failure intent response. Its only argument is called failureReason because the failure response template you created earlier has the failureReason variable in the template.

Next, add a guard for when this article has already been published:

guard !article.published else {
    completion(PostArticleIntentResponse
      .failure(failureReason: "This article has already been published."))
    return
}

Finally, for the success case, you’ll publish the article and call completion with the success response. This includes the article’s title and the date on which it was published:

ArticleManager.publish(article)
completion(PostArticleIntentResponse
  .success(title: article.title, publishDate: article.formattedDate()))

Now that you have your intent handler set up, you have to make sure it gets used.

Open IntentHandler.swift and replace the existing handler(for:) with the following to tell the system to use the handler you just wrote:

override func handler(for intent: INIntent) -> Any {
  return PostArticleIntentHandler()
}

Next, open AppDelegate.swift and find application(_:continue:restorationHandler:).

Something you might not expect is that even though you have your own handler for dealing with this shortcut, the continue user activity callback in the app delegate is still called.

To block the method from taking users out of Siri and to the new article view, add the following guard:

guard userActivity.interaction == nil else  {
    ArticleManager.loadArticles()
    rootVC.viewWillAppear(false)

    return false
}

If the activity has an interaction attached, that means its the “publish” shortcut, and you need to load the articles and make sure the feed view controller reloads.

Build and run, and then write a new article to donate one of these shortcuts.

Note: You can safely ignore the warning about linking against a dylib which is not safe for use in application extensions.

Next, go into Settings and make a shortcut for it with a title; something like “Post my last article” works perfectly.

After that, start Siri and use your shortcut; Siri will post the article for you and respond with a custom response informing you of the title and publication date.