Bit of background on the way interactive app intents get run for your widget.
1. If your app intent is compiled only into your Widget Extension, it will only run in your Widget Extension.
2. If your app intent is also compiled into your app, it will run in your app if your app is currently running and not suspended.
3. If you want your app intent to always run in your app, you can:
- implement openAppWhenRun, in which case it'll foreground your app OR
- implement AudioIntent, LiveActivityIntent or ForegroundContinuableIntent in which case it'll run in your app, but run in the background.
4. If you need to force an app intent to run in your app and it doesn't match well with any of the paths described in (3), I’d love to know about it and understand your use case.
@mgorbach Thank you for this very useful explanation. I've seen references to this scattered throughout the documentation on interactive widgets, but would love to see this concise and thorough explanation in the documentation in one place. I know several other iOS devs were struggling to understand this recently. Cc: @_Davidsmith @qzervaas
@mgorbach thank you I should try again but last I tried I saw
1. It failed when the app is closed/suspended in background: Could not find an intent with identifier TestIntent
2. It also ran in-app when it was suspended in background
3/4. My widget button needs to update a database, ideally sync that change, and schedule local notifications. It needs to execute in the app process never the widget, shouldn’t foreground the app. These other intent types don’t sound appropriate.
@mgorbach it seems the documentation is wrong in a few sentences? https://developer.apple.com/documentation/WidgetKit/Adding-interactivity-to-widgets-and-Live-Activities
@jordanhipwell if you are seeing it fail with “cannot find an intent …”, I’d love to see a test app that reproduces this.
@mgorbach Tried it on the latest and can confirm the issue I saw is resolved. 1) If only added to widget it won't try to run in-app. 2) If added to both targets, I'm still seeing it will run in the app's process if the app is suspended in background, but when the app is quit it’ll run in the widget process.
@mgorbach If I want to use the ForegroundContinuableIntent, it can't be added to the app and widget targets because 'ForegroundContinuableIntent' is unavailable in application extensions for iOS. But can't add it only to the app target as it won't compile: Cannot find ‘TestIntent' in scope. If I implement different intents in each target with the same name, ForegroundContinuableIntent in app and AppIntent in widget, it runs the widget intent when the app isn't running
@jordanhipwell Try disabling the "Require Only App-Extension-Safe API" build setting specifically for your Widget extension, and that should compile.
@mgorbach Hmm I get a build error: Application extensions and any libraries they link to must be built with the `APPLICATION_EXTENSION_API_ONLY` build setting set to YES.
@jordanhipwell @mgorbach Have you tried extending the AppIntent only in the application to implement ForegroundContinuableIntent? That worked for me.
@picnicbob @mgorbach I saw if the code in the app implemented ForegroundContinuableIntent while the code in the widget only implemented AppIntent, the widget code would run in the extension instead of the desirable behavior of always running app code in the app process. For now I’m using AudioPlaybackIntent instead to achieve this.
@jordanhipwell @mgorbach I realized the same shortly after that initial reply but I've found something that does seem to work:
In the same .swift file as the AppIntent, extend it to implement ForegroundContinuableIntent but mark the extension @available(iOSApplicationExtension, unavailable)
@picnicbob @mgorbach oo neat I’ll try that, thanks for sharing!
@picnicbob @mgorbach This works, just tested on beta 5 ty!
@mgorbach I found the AudioIntent workaround last week, and it’s doing the job, but I don’t actually play any audio.
Was pretty confused by the current documentation and videos, figured this was just a beta bug.
I need the main app awake long enough to sync with my backend (firebase), update my in-memory data structures, and update the json I store in my AppGroup, for other widgets.
I could add a little tick-off sound if I need to justify the use of AudioIntent.
@bennoland @mgorbach Does the ForegroundContinuableIntent work for you? That was a clean way for me to force my data management to happen in-app, rather than in the extension
@matt1corey @mgorbach I tried last week, but couldn't get beyond the "'ForegroundContinuableIntent' is unavailable in application extensions for iOS" compilation error.
See Jordan's post with Michael's followup seem to indicate it may be possible:
@bennoland @mgorbach Did you add the intents to multiple targets? I pulled my intents into a Framework, and used the AppIntentsPackage mechanism to 'import' them into both the App and the Widget Extension without issue - that's an iOS 17-only thing, though
@matt1corey @mgorbach no, I put them in my app code and checked the box for target membership for the widget as well.
@bennoland @matt1corey @mgorbach hey did you get it working? I got that setup to work with the following code someone suggested:
@available(iOS 17.0, *)
@available(iOSApplicationExtension, unavailable)
extension MyIntent: ForegroundContinuableIntent { }
@mgorbach You asked for examples of use cases for App Intents that need to open in the app that don't match the existing protocols. My use case is very specific, but here is the radar I filed for it in case you haven't seen it. FB12601164
@mgorbach I ran into a case that I didn't see anyone in this thread touch upon.
I have an app with widget. Both include an AudioPlaybackIntent and an AppEntity (the thing played).
I want a "Find" action for my AppEntity, so I'm implementing EntityPropertyQuery. The Find action runs in the widget exe, unless the app is launched.
The crux is that I do not want the widget exe to access/update my sqlite DB (dead10cc concerns) which is a dependency (left empty in widget).
Any recommendations?
@mgorbach (I haven't tested this yet, so I apologize if some of this is wrong.)
Presumably Widget extensions are short lived like share extensions, and thus don’t have time to sync an NSPersistentCloudKitcontainer to iCloud. I thus assume I’d want to use a `ForegroundContinuableIntent` to have my app handle the container save and, hopefully, be alive long enough to sync.
Is that a proper use case for that kind of intent?
@JoshHrach @mgorbach Good question, and something I'm working through right now
AppIntents themselves can run for up to 30 seconds, but I don't know if this would include time for NSPCKC to sync, as that would happen after my code finished executing.
When I force my Intents to run in-app, I am seeing my Widget fail to update sometimes - I assume this is due to the Widget loading its' data from the Widget Extension. Need to keep working through this one…
@matt1corey I sadly haven't been able to test this personally yet. Too busy trying to wrap up a 2.0 with Xcode 14. So my question is without even trying.
@matt1corey @JoshHrach I believe NSPersistentCloudKitContainer won’t sync when the app is the background, has to be in foreground unfortunately. I’d be v curious if you find otherwise!
@jordanhipwell @matt1corey @JoshHrach This is my experience as well. I’ve had discussions with apple engineers about this issue and they claimed it worked. I’ve submitted multiple sample projects and feedback support requests but I’m going on 2 years without a resolution.
@timbueno @jordanhipwell @JoshHrach in regard to what, syncing in the background? I suspect it does work, but it plays by a completely different set of rules than what anyone expects, and offers basically no timing guarantees
@matt1corey @timbueno @jordanhipwell @JoshHrach Now that I think of it I have seen sync work in the background, but only after multiple changes have been queued up. It def doesn’t sync when the app is quit though.
@jordanhipwell @matt1corey @JoshHrach This is exactly what I have seen. If the app is in the background and multiple changes have been pushed, my widget will see the changes come through and I’m able to update. If a single change comes through it will never update.
@timbueno @matt1corey @JoshHrach I reload my widget from the app upon db change so it’s up-to-date, my issue is when you modify the data via interactive widget it won’t upload until you open the app so other devices are outdated
@jordanhipwell @timbueno @JoshHrach is the intent running in-app, or in the Widget extension? Not sure it makes a difference…
@matt1corey @jordanhipwell @timbueno @JoshHrach it will need to always run in-app but right now it runs in both
@jordanhipwell @timbueno @JoshHrach this is pretty easy to do, btw, by just implementing ForegroundContinuableIntent - as far add I’ve seen, there are no side effects of adding this
@matt1corey @timbueno @JoshHrach Oo how do you get that to build? https://mastodon.world/@jordanhipwell/110815011648695610
@jordanhipwell @timbueno @JoshHrach My AppIntents are in a separate framework from either the App or the Widget Extension, and I'm using the new AppIntentsPackage mechanism (iOS 17 only) to import them into each - maybe that's the key? Did you do this, or did you add multiple ‘Target Memberships' for your Intents?
@matt1corey @jordanhipwell @timbueno @JoshHrach ah okay, I haven’t tried that just put em in the targets
@jordanhipwell @timbueno @matt1corey I saw similar when saving data from a Share/Safari extension when the app isn't open. Nothing synced until the app was in the foreground and there was enough time for the sync to complete.
@mgorbach Thanks for this. Very helpful.
@mgorbach Thank you very much for this! That’s super helpful to have all the cases laid out, I was running into a number of surprises when I was working in this because of how it can move between processes. This covers my needs well, I just needed to understand it.
@mgorbach thanks, good to know
@mgorbach this has been particularly helpful — thanks Michael
@mgorbach what’s the recommended way for compiling an app intent into both of the main app and an app intents extension? Should it be put in a framework first, or just add both of them as targets in the file? Or something else?
@eclair4151 You could do either, and it should work. The preferred way to avoid double-compiling is to keep the intent in a framework and use AppIntentsPackage to get that content into your app and extension.