Using SiriKit to start a workout

I think I might be missing something about how starting a workout is supposed to work with SiriKit. My initial thought was that I would be able to tell Siri "Start a running workout in MYAPP" which would cause MYAPP to launch (either in the background or foreground) and a NSUserActiviry object would be passed to the application:continueUserActivity:restorationHandler method. There I could start my workout as if the user had pressed a start workout button in my app.


To try to do this, I created a Siri intent handler that implements the INStartWorkoutIntentHandling protocol. I updated my .plist file to include the start workout type and I requested permisson to Siri using INPreferences.requestSiriAuthorization. Everything here works great - I can get Siri to resolve my workout request and I get to the handle:startWorkout:completion with everything I need to start the workout the way I want.


Here is where my trouble lies. I have not been able to get Siri to launch my main app and hand over the NSUserActivity object I create. The extension is a separate process, so there is no memory sharing between the two, and there doesn't seem to be any way to talk between the two processes. It looks like from all of the documentation, WWDC sessions, and examples that you are supposed to handle your workout from your intent extension code - not your main app code! You could in theory do this by moving all of your workout code into a framework (most of mine already is) but it doesn't seem like this is the right place to be handling this kind of data. I would expect the live span of an intent extension to be short lived, which many workouts are not (marathon runners may be going for several hours).


One thought I had with this was perhapts the workout is supposed to be passed along to an Apple Watch, since it has HKWorkoutSession support. There is a nifty new method in iOS 10 that lets you launch a workout on your watch from your iOS device (HKHealthStore. startWatchAppWithWorkoutConfiguration). However, calling this method on the intent extension returns a healthkit error stating you can't launch a workout on the Apple Watch when the main app is in the background. I'm kind of at a loss as to how this is supposed to work. When you use Siri to start a workout, is Siri supposed to become the only UI for your workout? This type of interaction makes sense for things like payment processing, but I'm not sure this makes sense for workout apps...


Any thoughts or suggestions would be appreciated!

Accepted Reply

I just found that the code generated for the workout intent in beta 2 seems to have this worked out:


    func handle(startWorkout startWorkoutIntent: INStartWorkoutIntent, completion: (INStartWorkoutIntentResponse) -> Void) {
        let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartWorkoutIntent.self))
        let response = INStartWorkoutIntentResponse(code: .continueInApp, userActivity: userActivity)
        completion(response)
    }


My understanding is that passing a response for any of the intents with type ".continueInApp" passes the respective user activity to your app in the continueUserActivity handler of your app delegate. All of the workout handler variants seem to include this ability, so I think that's the answer. Will post back here once I'm able to verify this.

Replies

I got the same confusion.


Is that Apple want to run the whole code on the Watch even without an iPhone connected. Then shares data when they are connected?😕

Still working to try to figure this out. So, since a custom SiriUI is displayed using a UIViewController in an extension, I thought that maybe I could tell the Siri UI to open my main application like so:


func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {       
    if let completion = completion {
        self.extensionContext?.open(URL(string: "app-my-schema://beginworkout?info1=asdf
            completion(self.desiredSize)
        })
    }
}


Where my main app is configured to handle the URL schema "app-my-schema://". However, the Siri UI seems to ignore this call. Does anyone have any other ideas about how to launch into your main iOS app from a Siri workout command?

I'm running into the same issue, has anyone managed to figure this out yet? I do recall that in one of the WWDC talks, they mentioned it was possible to launch the app in the case where the extension (UI or Intent), can't handle the entire request.

I just found that the code generated for the workout intent in beta 2 seems to have this worked out:


    func handle(startWorkout startWorkoutIntent: INStartWorkoutIntent, completion: (INStartWorkoutIntentResponse) -> Void) {
        let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartWorkoutIntent.self))
        let response = INStartWorkoutIntentResponse(code: .continueInApp, userActivity: userActivity)
        completion(response)
    }


My understanding is that passing a response for any of the intents with type ".continueInApp" passes the respective user activity to your app in the continueUserActivity handler of your app delegate. All of the workout handler variants seem to include this ability, so I think that's the answer. Will post back here once I'm able to verify this.

🙂 I think you are right. and I noticed that in beta 1 I was using code: .success, now the '.success' was gone in beta 2.

And the Intent UI doesn't work now.

Hi Airfly,


I started to try Sirikit on beta 2. I created a new app with Intent extension and intent UI extension. When I said "Start my workout using MyApp", it launched my app directly and I can stop in the breakpoint inside IntentHandler, however, I cannot lauch the extension UI. I looked into the IntentViewController and I think it should lauch when it is the start "workout intent" because the plist has INStartWorkoutIntent. Do you know how to lauch extension UI in beta 2?

Well I have this sort of working now. I can get all of my handlers (start, pause, resume, and cancel) to launch the app and perform the propper action. The main issue now is that this all only works if the device i unlocked.


My app does have background modes enabled as it plays audio during workouts, but this doesn't seem to be enough to have the app open and handle the request while the device is locked.


Curiously I found this in the SiriKit programming guide:


"Listing 3-3 shows a handle method that reports the status of a newly started workout. In this example, the handler calls a custom method that notifies the app to start the specified workout before returning a response to Siri."


Does anyone know what this is getting at? The code itself in that example doesn't show the implementation of said method, so I have no clue how they expect this to actually be possible.

I'm noticing the same issue. I'm guessin they removed the UI for some intent types.

I have have the same issue. Used .ContinueInApp and it's even working on the lockscreen but only if the phone is unlocked (via touchId) before calling Siri. Found this in the documentation:


When performed on a locked device, the system automatically prompts the user to unlock the device so that it can launch the workout app.


So it seems like a bug, I will create a radar for that.


With beta 6 the debugger stops in the IntentHandler 🙂

Requiring the user to unlock their device before launching your app when starting a workout is correct behavior. The user must unlock their device before any app can be launched in the foreground.

Thanks for the response, bnew.


The promise of SiriKit for use with workout apps is so that runners and cyclists can stow their phones away in running belts, armbands, and cycling jerseys, and then use Siri to start, pause, and resume their workouts from their earphones. If you have to authenticate every action, you might as well not use Siri, and just use the apps directly.


I suspect the current behavior is a bug, which we are still seeing in both beta 6 and beta 7. Here are the steps to recreate using our app that implements workout intents and responds with INStartWorkoutIntentResponseCodeContinueInApp.


- Start with your device locked.

- You say: "Hey Siri, start my workout with MYAPP".

- Siri says and displays: "Starting your working in MYAPP".

- Nothing else happens on the display. You do not see any messages about TouchID or entering your passcode. (Note: the first time I tried to launch, I did get the authentication screen, but I do not get it any longer, even after rebooting the iPhone.)

- If you do not authenticate with TouchID, then the screen eventually dims.

- If you do authenticate with TouchID, then the app is launched and starts the workout.


The issue with this kind of behavior is that it happens for EVERY intent, not just an initial launch. You can't pause a workout without authenticating with TouchID.


This may be complicated by the fact that apps that use location services get to run while the device is locked, but only when they are using location services.


Steve Kusmer

Abvio

+1 to what kusmer is saying. I think the real-world use cases here show some problems in the UI.


The idea, as best I can suss out, is that SiriKit intends out Intention Handler to do the work of starting the workout in that callback, *before* our app is launched. If you look at their sample code that's how they do it. The callback to the app doesn't trigger managing the workout, it just triggers showing the correct nav stack. They consider the "resume in app" to be an optional step in the workflow.


The problem is though, for most fitness apps, things are a lot more complex than that. Lets say I'm trying to track GPS in a fitness app. Sure, I can in my Siri extension start some code from a shared framework and ask CoreLocation to start sending it data (where it gets saved to the app group), but then how can I ask that CoreLocation manager to stop getting updates later from inside my app. How can I keep that polling of GPS alive in the background? The SiriKit process is supposed to be short lived.


Lets say I need to update a watch complication with the fact I started a workout. Am I really supposed to do that from my intent? All that code for syncing data to my Watch app lives in my host app - am I supposed to share that now with the intent?


So now I'm left to relying on the user opening the app in response to the intent to make sure all this kind of background code executes. But the default implemtntation, as you pointed out, doesn't warn the user "if you don't unlock nothing happens." As such I've resorted to adding all my intents into the restricted-when-locked key in my Info.plist. That way when a user triggers something it is *very* clear nothing happens until they unlock.


Like you pointed out, terrible for the idea of a hands-free use of Siri. But I can't see anyway around it.

Thanks, parrots. I've submitted a bug report on the failure of SiriKit to prompt for the user to unlock the device. I've also submitted an enhancement request to not require unlocked devices to execute workout intents.


Steve

Has somebody managed to show UI for Pauseing/resuming/stoping a workout?

What UI are you looking to show? Like a checkmark and success?