Demystifying Siri, Part 3: Restoring User Activity
In Part 2, we created a custom intent in SiriKit to allow us to open our NumberRace app when our Solve Game shortcut is invoked. Next, we’re going to update NumberRace so that our solver is opened and populated with the data we provided.
Restoration
Firstly our application needs to respond to the invocation of an intent. In order to do this we need to implement the application (_ application: UIApplication, continueUserActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void)
method in our application delegate. In this method we identify which intent was invoked and pass any data to one or more view controllers to process. We’re finally going to do some coding! Open up AppDelegate.swift
and add the following:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == "SolveGameIntent" else {
return false
}
guard let window = window,
let rootViewController = window.rootViewController as? SwitchViewController else {
return false
}
restorationHandler([rootViewController])
return true
}
My root view controller is the main menu screen and I’ve decided that it should be the lucky recipient of the intent data. Why? Well it’s the only view controller that I’m sure will be instantiated in our app at all times. From there we’ll open my solver’s view controller, predictably named SolverViewController
, and pass in our data at the same time.
A segue about segues
An aside: the NumberRace app has a number of view controller scenes - scenes that appear while playing the game, scenes for changing settings, and so on. The scenes are connected with a number of UIStoryboardSegue
s. I’ve written a helper class called ViewCoordinator
that handles the task of opening our solver view controller (SolverViewController
) no matter where you left the app. You may be in the middle of a game, or in a settings screen - ViewCoordinator
attempts to perform and unwind the segues necessary to open the solver.
The ViewCoordinator
code is outside the scope of this blog post series. (By which I mean that I am too embarrassed to share it as it’s not the most elegant thing I’ve written. If I knock it into shape I’ll update this blog post with the code.)
Restoring user activity state
When the line
restorationHandler([viewController1, viewController2, ...])
is executed in a continueUserActivity
function, all the restoreUserActivityState(_ activity: NSUserActivity)
functions in the view controller list are run.
Therefore, for us, we now need to add such a function to our root view controller.
override func restoreUserActivityState(_ activity: NSUserActivity) {
if #available(iOS 12.0, *) {
guard let intent = activity.interaction?.intent as? SolveGameIntent else {
return
}
ViewCoordinator.helper.solverData = (
number1: intent.number1.rawValue,
number2: intent.number2.rawValue,
number3: intent.number3.rawValue,
number4: intent.number4.rawValue,
number5: intent.number5.rawValue,
number6: intent.number6.rawValue,
target: intent.target
)
}
ViewCoordinator.helper.openSolver()
}
The code here is pretty straightforward. Firstly, we’ll check that our custom intent is of type SolveGameIntent
before proceeding. We’ll then add our intent data to ViewCoordinator
. Finally, by calling my openSolver()
function our ViewCoordinator
will handle all the segues that need to be performed to bring our solver into view.
I’ve also created a SolverData
data type. I could’ve passed around the SolveGameIntent
intent as is, but custom intents are only supported in iOS 12 and above. It seemed more straightforward to convert the data into a tuple instead of adding iOS version checks everywhere in the code.
Incidentally, when you create an intent definition file, Xcode translates this into custom generated code behind the scenes. You can see this generated code by right-clicking on SolveGameIntent
appearances in your code and selecting Jump to Definition. Thought it’s worth pointing out here in case you were wondering how Xcode is aware of intent classes.
Trying it out
And that’s all we need! Let’s try it out and create a shortcut…
If we run the shortcut by pressing Play, the application continueUserActivity
function is activated, our root view controller’s restoreUserActivityState()
function is called and then openSolver()
opens our solver and populates the values.
So far so good! Our restoration handler is working as expected. But how can we publicise the fact that this functionality is available to a user? The answer lies in suggestions, which we’ll cover next, in Part 4.