Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 306
Jordan H Avatar asked Jan 25 '26 14:01

Jordan H


2 Answers

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.

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 { }

In iOS 26, ForegroundContinuableIntent is deprecated. Instead, specify supportedModes and include foreground(.dynamic). That results in the same behavior as conforming to ForegroundContinuableIntent.

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

like image 150
Jordan H Avatar answered Jan 27 '26 04:01

Jordan H


I found a better solution for interaction Live Activity.

  1. You can create a struct impletion LiveActivityIntent then add it to widget target and app target.
  2. Create two files, one add to widget target, other one add to app target, each extension your Intent and write one same function. Widget target function can empty, app target function is real code. In perform call your function in extensions file.

Above steps make sure compile success and when interaction intent, System will execute your real code in App Process.

See below samples. because LiveActivityIntent always called in iOS Main target, so your widget's `doSomething` will never be called, it's just let Xcode compile succesfull.

// include this in both target
MyLiveActivityIndent.swift

struct MyLiveActivityIndent: LiveActivityIntent {
    func perform() async throws -> some IntentResult {
        await doSomething()
        return .result()
    }
}

//include this only in widget target
MyLiveActivityIndent+Widget.swift
extension MyLiveActivityIndent {
    func doSomething() async {
        //empty code, this will never be called
    }
}

//include this only in iOS target
MyLiveActivityIndent+iOS.swift
extension MyLiveActivityIndent {
    func doSomething() async {
        //your real code
    }
}
like image 26
iblacksun Avatar answered Jan 27 '26 04:01

iblacksun



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!