iosswiftuiwidgetkitappintentsios17

How to add AppIntent to app target and use it in an interactive widget


How do you create an AppIntent that will run in your app's process and use it in your widget's Button? I created a new file, added it to my app's target, created my AppIntent struct, then added a button to my widget: Button(intent: TestIntent()) { Text("Test") }. This results in a compile-time error Cannot find 'TestIntent' in scope which makes sense because this code doesn't exist in the widget extension where I'm trying to use it. But the documentation for adding interactivity to widgets states:

For a widget, create a new structure that adopts the AppIntent protocol and add it to your app target ... In the protocol’s required perform() function, add code for the action you want to make available to the widget.

and

If you adopt the AppIntent protocol, you can add your custom app intent to your widget extension target or your app target. Adding it to the app target is preferable because you can then reuse the button or toggle that you add to your widget in your app.

and

When a person interacts with a button or toggle in your widget, the system runs the perform() function in your app’s process.


Solution

  • If you add the AppIntent only to your widget extension target, the intent will always run in the widget process.

    If you add the AppIntent to both your app and widget extension targets, the intent will run in your app process if your app is running (even if suspended in the background) and in the widget process when your app is not running. If you want it to always run in your app's process, you can implement openAppWhenRun which will cause your app to come to foreground, or you implement a different intent protocol that runs in the app's process in the background: AudioPlaybackIntent, LiveActivityIntent, or ForegroundContinuableIntent.

    Note the documentation has been updated to address some previous inaccuracies. And thanks to Michael on Mastodon for clarifying the behavior!

    In my app, in order to get it to always run in the main app process, I created a regular AppIntent, added that file to both the app and widget targets, then conformed it to ForegroundContinuableIntent but only for the main app not the extension. ForegroundContinuableIntent is unavailable in application extensions for iOS, so you have to annotate it like so:

    struct MyWidgetButtonIntent: AppIntent {
        // ...
    }
    
    @available(iOSApplicationExtension, unavailable)
    extension MyWidgetButtonIntent: ForegroundContinuableIntent { }