Sirikit Workout always says 'Continue in the app'

I was working on a SiriKit intents extension for our app. We're using the workout extension type. The problem is, I just put the required methods in and always pass .ready but Siri always responds,



Sorry, you'll have to continue in the app.



I have tried other responses as well (.failure and .unspecified), but she still says to continue in the app. I've followed the documentation provided by Apple to setup my .plist files, but it still might be something related to that.



Here is my Intents code :


class IntentHandler: INExtension, INStartWorkoutIntentHandling, INPauseWorkoutIntentHandling, INResumeWorkoutIntentHandling, INCancelWorkoutIntentHandling, INEndWorkoutIntentHandling {
    override func handler(for intent: INIntent) -> Any {
        // This is the default implementation.  If you want different objects to handle different intents,
        // you can override this and return the handler you want for that particular intent.
  
        return self
    }
    func handle(startWorkout intent: INStartWorkoutIntent, completion: @escaping (INStartWorkoutIntentResponse) -> Void) {
        print("Starting Service")
        let activity = NSUserActivity(activityType: "start_service")
        let result = INStartWorkoutIntentResponse(code: .ready, userActivity: activity)
        completion(result)
    }
    func handle(endWorkout intent: INEndWorkoutIntent, completion: @escaping (INEndWorkoutIntentResponse) -> Void) {
        print("Ending Service")
        let result = INEndWorkoutIntentResponse(code: .ready, userActivity: nil)
        completion(result)
    }
    func handle(pauseWorkout intent: INPauseWorkoutIntent, completion: @escaping (INPauseWorkoutIntentResponse) -> Void) {
        print("Pausing Service")
        let result = INPauseWorkoutIntentResponse(code: .ready, userActivity: nil)
        completion(result)
    }
    func handle(cancelWorkout intent: INCancelWorkoutIntent, completion: @escaping (INCancelWorkoutIntentResponse) -> Void) {
        print("Cancel Service")
        let result = INCancelWorkoutIntentResponse(code: .ready, userActivity: nil)
        completion(result)
    }
    func handle(resumeWorkout intent: INResumeWorkoutIntent, completion: @escaping (INResumeWorkoutIntentResponse) -> Void) {
        print("Resume Service")
        let result = INResumeWorkoutIntentResponse(code: .ready, userActivity: nil)
        completion(result)
    }
}



I tried a breakpoint where the start workout intent is handled, and it does hit the function. I also tried passing .ready to an INMessageIntent and Siri responded without saying open in the app. I'm wondering if this is always the case for workout extensions. If it is, I would still like Siri to show my IntentsUI.



I appreciate any response.

Accepted Reply

I actually got the answer from StackOverflow user Georg:


Once you are ready to start the workout in your app, you need to call the completion handler with .continueInApp



So that would be something like:


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



See the documentation:


> Your extension is ready to transfer control to the app so that the workout can be started. Upon returning this code, SiriKit launches your app and passes it the NSUserActivity object you provided at initialization time. (If you did not provide a user activity object, SiriKit creates one for you.) SiriKit adds an INInteraction object with the intent and your response to the user activity object before delivering it. Your app should use the information in the user activity object to start the workout.

Replies

What do you have in your IntentsUI? Last time I got this, it's because I force unwrap an Optional in my IntentsUI code.

Here is my IntentsUI code:


class IntentViewController: UIViewController, INUIHostedViewControlling {
  
    @IBOutlet weak var timer: UILabel!
    @IBOutlet weak var titleTop: UILabel!
  
    override func viewDidLoad() {
        super.viewDidLoad()
    }
  
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
        present(self, animated: false, completion: nil)
      
        if let completion = completion {
            completion(self.desiredSize)
        }
    }
  
    var desiredSize: CGSize {
        return self.extensionContext!.hostedViewMaximumAllowedSize
    }
  
}

I actually got the answer from StackOverflow user Georg:


Once you are ready to start the workout in your app, you need to call the completion handler with .continueInApp



So that would be something like:


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



See the documentation:


> Your extension is ready to transfer control to the app so that the workout can be started. Upon returning this code, SiriKit launches your app and passes it the NSUserActivity object you provided at initialization time. (If you did not provide a user activity object, SiriKit creates one for you.) SiriKit adds an INInteraction object with the intent and your response to the user activity object before delivering it. Your app should use the information in the user activity object to start the workout.

Now that .continueInApp is depreciated (iOS 11) anyone have any ideas how to get the app to open without SIRI asking with the user dialog button that .handleInApp causes?

You should be able to do everything to start your workout from the Intent extension point or by passing control to the main app in the background with the `handleInApp` response code. Forcing the user to open the app to complete a task successfully is not the intended use of the SiriKit API except in exceptional circumstances where `failureRequiringAppLaunch` is an appropriate response code.


What is it that you need the main app to do in order to start a workout successfully?

I was simply hoping to say "start a workout" which would then open the app (in the forground that is) and start the workout without having to press any buttons. That way the user can do their exersize and see the app feedback without touching the phone (or any future target device).

I do not seem to be getting into my handler in the app "func application(_ application: UIApplication, handle intent: INIntent,completionHandler: @escaping (INIntentResponse) -> Void)" as I do not get to any breakpoints in that funciton.

I do now see the beginning of the UI extension but I am not hitting any breakpoints in the extension (that I would have expected).

Is this the correct approach to launch the app with SIRI?

Starting your app with Siri and having it transfer to the foreground isn't supported. The documentation for .handleInApp is clear about it triggering a background launch, and you can use that to set up any tracking for the workout you need.


As to application(_:handle:completionHandler:) not hitting breakpoints, you need to consider that the intent extension, the main application, and the UI extension are all in different processes, and that by default, Xcode is only going to hit breakpoints for the currently running process, so if you're starting the intents extension target, you won't trigger breakpoints in the UI extension or the main app.


The way I work with this is to set the scheme configurationfor the main app to be "Launch: Wait for executable to be launched." With this set, I "run" the main application by clicking the run button in Xcode, which puts Xcode in a state where it's waiting to attach to the process to trigger breakpoints until some external event starts the app, such as Siri lunching the app to the background. You can then invoke Siri to start a workout with your app to drop into the main app and hit your breakpoints.

Sweet, Now I can watch my program execute and explore SIRI's launch behavior.

Thanks