Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I open my SwiftUI app and perform a function using NSUseractivity from within Siri Intent Extension?

My Problem

My app has its own Shortcuts actions created using Intents Extensions. They perform background actions perfectly.

For some actions, I'm trying to make the intent extension open the main (container) app when run in Shortcuts and perform a function.

I'm having trouble with NSUserActivity and I'm not sure if it's the fact it's a SwiftUI project or the way I'm implementing it (or both).

What I've tried

I have registered my NSUserActivity name as an NSUserActivityType in my info.plist ("com.me.project.activityName").

I've added the code below to my AppDelegate.

I initialise a new NSUserActivity inside my intent extension with the same type as the one declared in info.plist.

I've also tried declaring the activity within the app (I don't think I need to do this?)

I'm running: iOS (iPhone XS, Beta 6) macOS 10.15 Catalina (Beta 5) Xcode 11.0 (Beta 5)

My Code Thus Far

I have this in my AppDelegate:

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

          if userActivity.activityType == "com.me.project.activityName" {
              if let url = URL(string: "https://www.google.com") {
                  UIApplication.shared.open(url)
              }
              return true
          }
          return false
      }

This is within my intent extension:

let openApp = intent.launchApp?.boolValue ?? false
        if openApp {

            let UA = NSUserActivity(activityType: "com.me.project.activityName")
            UA.title = "Dummy title"

            completion(UserActivityTestIntentResponse(code: .continueInApp, userActivity: UA))

        } else {
            completion(UserActivityTestIntentResponse.success(result: "You chose not to open the app with a user activity."))
        }

In info.plist

<key>NSUserActivityTypes</key>
    <array>
        <string>com.me.project.activityName</string>
    </array>

I have this declared in a swift file in my project (though I don't think I need it):

let openURLActivityType = "com.me.project.activityName"

let viewPageActivity: NSUserActivity = {
    let userActivity = NSUserActivity(activityType: openURLActivityType)
    userActivity.title = "Dummy Title"
    userActivity.suggestedInvocationPhrase = "Dummy phrase"
    userActivity.isEligibleForSearch = false
    userActivity.isEligibleForPrediction = false
    return userActivity
}()

Unexpected Results

I'm expecting that when the action is run in Shortcuts, my app opens and the website "https://www.google.com".

Currently, after running the action in Shortcuts, my app launches to the home screen and nothing else happens. No breakpoints appear to be hit in my appDelegate.

I can't work out if it's because I'm using NSUserActivity wrong, whether it's because it's SwiftUI or whether it's something that just won't work from inside an intent.

Thank you so much in advance for any help!

like image 818
mralexhay Avatar asked Aug 15 '19 20:08

mralexhay


2 Answers

Like you already commented you can use func scene(_ scene: UIScene, continue userActivity: NSUserActivity) to continue the NSUserActivity when the app is still in the background.

However when the app is closed you can implement let userActvity = connectionOptions.userActivities.first inside the scene(_:willConnectTo:options:) method inside the SceneDelegate to access the activity the user wants to continue.

Just adding this piece of code to the standard implementation of this method would look like this:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Use a UIHostingController as window root view controller
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: ContentView())
        self.window = window
        window.makeKeyAndVisible()

        //Standard implementation until here
        if let userActvity = connectionOptions.userActivities.first {
            if let intent = userActivity.interaction?.intent as? YourIntent {
                print(“do something”)
            }
        }
    }
}

This works for Siri Intents with Swift 5 Xcode 11.

For Handoff though the .userActivities should not be used as the documentation says, instead the associated delegate methods will be called (scene(_:continue:)):

This property does not contain user activity objects related to Handoff. At connection time, UIKit delivers only the type of a Handoff interaction in the handoffUserActivityType property. Later, it calls additional methods of the delegate to deliver the NSUserActivity object itself.

like image 106
L. Stephan Avatar answered Sep 20 '22 10:09

L. Stephan


If your application is a SwiftUI app, maybe you should consider to add something like:

ContentView
   .onContinueUserActivity("com.company.app.activityIdentifier") { userActivity in 
      handleShortcut(with: userActivity)
   }

where handleShortcut is getting userInfos from the given NSUserActivity and launching things to be done from this opening of your app

This replace any implementations made in the func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool delegate method that is not called anymore in your SwiftUI application unless you make a few more (and not trivial) lines of code to make it called

You can find more details on :

  • Apple documentaction
  • An example from Hacking With Swift
like image 30
jgodon Avatar answered Sep 22 '22 10:09

jgodon