If you’ve had an iPhone for any length of time, you’ve likely interacted with . When was first announced in 2011, its ability to understand context and meaning, regardless of the specific combination of words used, was groundbreaking.

Unfortunately, Siri integration was limited to Apple’s own apps until the release of SiriKit in 2016. Even then, the types of things you could do with Siri were limited to a particular set of domains.

With the release of Siri in , this is no longer the case. Now, you can create custom intents to represent any domain, and you can expose your app’s services directly to Siri.

In this , you’ll learn how to use these new shortcuts APIs to integrate Siri into a writing app.

Getting Started

To get started, use the Download Materials button at the top or bottom of this tutorial to download the starter project.

Once downloaded, double-click TheBurgeoningWriter.xcodeproj to open the project in Xcode.

Note: If possible, you should use a physical device to follow along with this tutorial. Although the Simulator works, it behaves differently with a few things.

Set the bundle ID to something unique to you (Apple recommends using a reverse DNS name such as com.razeware.TheBurgeoningWriter). Then, run the app, and you’ll see a home screen that shows all of the written articles. From here, you can add new articles and publish the drafts you’ve previously saved.

The big idea here is to write an article, sit on it for a little bit, and then publish it later — provided you’re still happy with it.

Ready to begin? Great!

Adding Shortcuts to an App

The first thing to consider is which features of your app are appropriate for turning into shortcuts.

Ideally, you should create shortcuts for actions your user can perform; preferably, something they’ll likely do repeatedly. Once you’ve decided to set up a shortcut, there are two ways to create it:

  1. NSUserActivity: User activities are part of an existing API that allows you to expose certain things a user can do for app hand-off and Spotlight searches. The thing to remember here is that this option is only useful when you want the user to go from Siri into your app to complete a task.
  2. Custom Intents: Creating a custom intent is the true power of shortcuts. With an intent, you can communicate with your user via Siri without ever having to open your app.

Making a Shortcut for Writing New Articles

Your first shortcut is one that lets a user go straight to the new article screen. This is the perfect candidate for creating a shortcut based on an NSUserActivity object, because it’ll take the user from Siri into your app.

Your goal is to donate one of these activities to the system every time your user performs that action. You’ll do so by adding a new method that allows you to generate these activity objects.

Open Article.swift and, at the top of the file under the imports, add the following constant string definition:


public let kNewArticleActivityType = "com.razeware.NewArticle"

This is the identifier you’ll use to determine if you’re dealing with a “new article” shortcut. A good rule of thumb is to use the reverse DNS convention when choosing an identifier for your shortcut.

Next, below the properties, add the following method definition:


public static func newArticleShortcut(thumbnail: UIImage?) -> NSUserActivity {
  let activity = NSUserActivity(activityType: kNewArticleActivityType)
  activity.persistentIdentifier = 
    NSUserActivityPersistentIdentifier(kNewArticleActivityType)

  return activity
}

Here, you’re creating an activity object with the correct identifier and returning it. The persistentIdentifier is what connects all of these shortcuts as one activity.

For your activity to be useful, you have to do some configuration.

Add the following two lines before the return:


activity.isEligibleForSearch = true
activity.isEligibleForPrediction = true

First, you set isEligibleForSearch to true. This allows users to search for this feature in Spotlight. You then set isEligibleForPrediction to true so prediction works. Setting this to true allows Siri to look at the activity and suggest it to your users in the future. It’s also what allows the activity to be turned into a Shortcut later.

Next, you’ll set the properties that affect how your Shortcut looks to users.

Define a local attributes property. Add it below the previously pasted lines:


let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)

Set your attributes by adding the following lines:


activity.title = "Write a new article"
attributes.contentDescription = "Get those creative juices flowing!"
attributes.thumbnailData = thumbnail?.jpegData(compressionQuality: 1.0)

This sets the title, subtitle and thumbnail image you’ll see on the suggestion notification.

Stepping back for a moment, it’s important to remember that Siri exposes this feature in two separate ways:

  • First, Siri learns to predict what your users might want to do in the form of suggestions that pop up in Notification Center and Spotlight Search.
  • Second, your users can turn these activities into voice-based shortcuts.

For the last bit of configuration, add the suggested phrase users should consider when making a shortcut for this activity:


activity.suggestedInvocationPhrase = "Time to write!"

The chosen phrase should be something that’s short and easy to remember. It should also not include the phrase “Hey, Siri” because the user might have already triggered Siri’s interface that way.

Finally, assign the attributes object to the activity object:


activity.contentAttributeSet = attributes

Now that you can grab user activity objects from an Article, it’s time to use them.

Using the Activity Object

Open ArticleFeedViewController.swift and locate newArticleWasTapped().

Below the comment, add the following lines:


//1
let activity = Article.newArticleShortcut(thumbnail: UIImage(named: "notePad"))
vc.userActivity = activity

//2
activity.becomeCurrent()
  1. First, you create an activity object. Then, you attach it to the view controller that’ll be on screen.
  2. Next, you call becomeCurrent() to officially become the “current” activity. This is the method that registers your activity with the system.

Congratulations, you’re now successfully donating this activity to Siri.

Build and run. Then, go to the new article screen and back to the home screen a few times.

You won’t see anything too interesting in the app, but each time you perform that action, you’re donating an activity to the system.

To verify, pull down on the home screen to go to search. Then, type “write”, and you should see the “Write a new article” action come up.

Continuing a User Activity

Tap on the “Write a new article” result in your search. You’ll be taken to your app’s home screen.

Your feature may be exposed to the system, but your app isn’t doing anything when the system tells it the user would like to use the feature.

To react to this request, open AppDelegate.swift, and at the bottom of the class, add the following method definition:


func application(
  _ application: UIApplication,
  continue userActivity: NSUserActivity,
  restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
  return true
}

Inside the method and before the return statement, create the New Article view controller and push it onto the nav stack:


let vc = NewArticleViewController()
nav?.pushViewController(vc, animated: false)

Build and run again. When you search for this feature and tap on it, your app takes you directly to the New Article screen.

Developer Settings for Working With Siri

All you’ve done so far is accessed your feature from Spotlight. This isn’t anything new, and it would work even if Siri weren’t involved since you made your activity eligible for search.

To prove that Siri can start suggesting this action, you’ll need to go to the Settings app and enable a few options.

Open Settings and find the Developer option. Scroll towards the bottom and you’ll see a section named SHORTCUTS TESTING.

Turning on the Display Recent Shortcuts option means that recently donated shortcuts will show up in Spotlight Search instead of Siri’s current predictions.

Similarly, Display Donations on Lock Screen always shows your recent donations as a notification on your lock screen.

Turn on both of the options so you can always see what shortcuts your app has donated most recently.

Now that you know how to see what Siri’s suggestions look like, it’s time to turn these activities into full-blown shortcuts!

Turning User Activities Into Shortcuts

When a user wants to turn one of these activities into a shortcut, they can do so from the Settings app. As the developer, there’s nothing more you need to do.

To test it out: On your device, open Settings > Siri & Search.

The first section shows a list of shortcuts donated by the different apps on your phone. You’ll see the “Write a new article” shortcut in this list; if you don’t, tap More Shortcuts to see more.

To add a shortcut for this action, tap the action, and you’ll be taken to the shortcut creation screen.

Here, you can see that suggested invocation phrase you added earlier.

Tap the red circle at the bottom. When prompted, say to Siri, “Time to write”. After Siri figures out what you said, tap Done to finalize your shortcut.

Now, your shortcut is listed along with any other shortcuts you’ve created.

Adding Shortcuts To Siri From Your App

This is all well and good, but can you expect users to muck around in the Settings app to add a shortcut for your app? Nope! Not at all.

Fortunately, you can prompt your users to do this straight from your app!

Open NewArticleViewController.swift and you’ll see an empty definition for addNewArticleShortcutWasTapped().

This is the method that gets called when the blue “Add Shortcut to Siri” button is tapped.

The IntentsUI framework provides a special view controller that you can initialize with a shortcut. You can then present this view controller to show the same UI you just saw in the Settings app.

Add the following two lines to initialize the shortcut:


let newArticleActivity = Article
  .newArticleShortcut(thumbnail: UIImage(named: "notePad.jpg"))
let shortcut = INShortcut(userActivity: newArticleActivity)

Next, create the view controller, set the delegate and present the view:


let vc = INUIAddVoiceShortcutViewController(shortcut: shortcut)
vc.delegate = self

present(vc, animated: true, completion: nil)

At this point, Xcode complains that this class isn’t fit to be the delegate of that view controller. You’ll need to fix this.

Add the following extension at the bottom of the file:


extension NewArticleViewController: INUIAddVoiceShortcutViewControllerDelegate {
}

Then, conform to the INUIAddVoiceShortcutViewControllerDelegate protocol by adding the method for when the user successfully creates a shortcut:


func addVoiceShortcutViewController(
  _ controller: INUIAddVoiceShortcutViewController,
  didFinishWith voiceShortcut: INVoiceShortcut?,
  error: Error?
) {
}

Also, add the method for when the user taps the Cancel button:


func addVoiceShortcutViewControllerDidCancel(
  _ controller: INUIAddVoiceShortcutViewController) {
}

Next, you need to dismiss the Siri view controller when these methods are called.

Add the following line to both methods:


dismiss(animated: true, completion: nil)

Build and run to try it out! Go to the New Article view and tap Add Shortcut to Siri.

You’ll see a view controller that looks a lot like the view you see when you go to set up shortcuts in the Settings app. With this in place, your users won’t have excuses for not utilizing your app’s full potential! :]

Publishing Articles with Siri

Next, you want to add the ability to publish articles you’ve written directly from Siri. The big difference here is that instead of Siri opening your app, everything will happen in-line, directly from the Siri UI.

For this shortcut, you’ll create a custom Intent to define the back and forth between Siri, your users and your app.

Defining a Custom Writing Intent

In the Project navigator, locate the ArticleKit folder and click on it to highlight it.

Then, press Command + N to create a new file.

In the filter search box, type Intent and you’ll see SiriKit Intent Definition File.

add intent definition file

Click Next, then name your file ArticleIntents.intentdefinition.

Now it’s time to create an intent for posting articles you’ve written.

Go to the bottom of the section that reads No Intents and click the plus button to add a new intent definition.

add new intent

Name it PostArticle, and then configure your intent based on these settings (described after the picture):

PostArticleIntent definition

This screen is for configuring the information that the publish intent needs to work.

The options in the Custom Intent section define what type of intent it is and can affect how Siri talks about the action. Telling Siri that this is a Post type action lets the system know that you’re sharing some bit of content somewhere:

  1. Category: Post
  2. Title: Post Article
  3. Description: Post the last article
  4. Default Image: Select one of the existing images in the project.
  5. Confirmation: Check this box since you want to ask the user to verify that they’re really ready to publish this article.

The Parameters section is where you define any dynamic properties used in the Title and Subtitle, which you’ll do now.

Define a parameter named article that’s a Custom data type and a publishDate that’s of type String.

Then, in the Shortcut Types section, click the plus button to add one type that includes both the article and publishDate arguments as its parameters.

Next, set up the Title and Subtitle for the shortcut.

Make the title Post “${article}” and the subtitle on ${publishDate}. If you don’t copy and paste, make sure to let Xcode autocomplete article and publishDate.

Finally, make sure Supports background execution is checked so you aren’t forced to leave the Siri UI.

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.

Wrapping Up

The final thing you need to worry about is deleting intents from the system. Let’s say a user has deleted the only article they wrote. If Siri prompts them to publish this article, that means the system remembers information that they wanted to delete.

Since this goes against Apple’s strict respect for user privacy, it’s your job to remove activities and intents that were deleted.

Go to ArticleFeedViewController.swift and scroll to the bottom of the file.

Then, add the following method call to the bottom of remove(article:indexPath:):


INInteraction.delete(with: article.title) { _ in
}

The completion block allows you to react to deletion errors however you see fit.

Since the new article shortcut doesn’t contain any user data, it isn’t strictly necessary to remove it.

Finally, open Layouts.swift and find the UITableViewDataSource extension for ArticleFeedViewController. Add the following at the end of the extension to enable deleting articles:


func tableView(_ tableView: UITableView,
                 commit editingStyle: UITableViewCell.EditingStyle,
                 forRowAt indexPath: IndexPath) {
  if editingStyle == .delete {
    let article = articles[indexPath.row]
    remove(article: article, at: indexPath)
    if articles.count == 0 {
      NSUserActivity.deleteSavedUserActivities(withPersistentIdentifiers: 
        [NSUserActivityPersistentIdentifier(kNewArticleActivityType)]) {
          print("Successfully deleted 'New Article' activity.")
      }
    }
  }
}

If you want, you can use NSUserActivity‘s class method deleteSavedUserActivities(withPersistentIdentifiers:completionHandler:) to remove all of the activities that were donated with a single identifier.

Where to Go From Here?

You’ve worked through a lot, but there’s still a lot to learn. You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial. Be sure you update your bundle id and app group id before you try to run it.

If you want to learn more about Shortcuts, check out the two WWDC videos from 2018. The first presentation covers a lot of the same material as this tutorial and is a good refresher to solidify the big ideas you learned here. The second goes more into best practices.

As always, we hope you enjoyed the tutorial. Let us know if you have any questions down in the comments!



Source link https://www.raywenderlich.com/6462-siri-shortcuts-tutorial-in-ios-12

LEAVE A REPLY

Please enter your comment!
Please enter your name here