Context
I'm building a study-timer feature for an iOS app (Flutter + native ActivityKit) that displays a Live Activity on the Lock Screen / Dynamic Island while a session is running. When the user force-quits the app by swiping it up from the App Switcher, I want the Live Activity to disappear as quickly as possible.
I have already confirmed (from on-device testing and Apple Developer Forums thread 732418) that:
applicationWillTerminateis not called on swipe-up force-kill, only on OS-initiated termination or crash. So synchronousActivity.end(...)from the app itself is not a solution for the force-kill path.- Shortening
staleDatedoes not visually dismiss the Live Activity once the app process is gone — the Widget Extension keeps rendering the last fresh snapshot and there is no body-reevaluation trigger on the stale transition post-app-death. (I implemented and verified this, then rolled it back.) - The only Apple-official reliable mechanism is APNs push-to-end (
Activity.request(pushType: .token)+ server sendsevent: endvia APNs).
Current architecture
I have APNs push-to-end working end-to-end. Structure:
- Client:
Activity.request(pushType: .token), subscribe toActivity.pushTokenUpdates, forward each new token to the backend. - Backend: On every client heartbeat, upsert
(user_id, la_apns_token, la_activity_id, last_heartbeat)into Postgres. A separate scheduler polls for rows whoselast_heartbeat < now() - grace_ttland sends APNsevent: endto the stored token.
Parameters I am currently running with:
| Client heartbeat interval | 60 s |
| Orphan grace TTL (server) | 135 s (heartbeat × 2.25, to absorb network jitter) |
| Scheduler poll interval | 30 s |
The observation
End-to-end latency from "user force-kills the app" to "Live Activity disappears from Lock Screen" is:
- Worst case: 60 + 135 + 30 = ~225 s (~3.75 min)
- Typical: ~3 min (as consistently measured on iOS 26.4.1, iPhone 17 Pro Max)
- Theoretical minimum (if the kill happens exactly at a heartbeat boundary): ~135 s
Users perceive 3 minutes as broken — the timer clearly stopped (no ticking), but the Live Activity "ghost" is still visible on the Lock Screen.
My question
Is there any Apple-supported mechanism to reliably tear down a Live Activity faster than ~2 minutes after the owning app's process is gone, given that applicationWillTerminate does not fire on swipe-kill?
Specifically:
-
Is there any practical lower bound below ~60 s for this scenario using the current ActivityKit + APNs model, assuming we are not willing to spam heartbeats every few seconds? I can push heartbeat to 20–30 s, but the server cost grows linearly with active sessions.
-
Does
BGAppRefreshTask/BGProcessingTaskhave any documented lifecycle hook that fires on user-initiated swipe-kill specifically, so that I could do a "last-heartbeat flush" just before the process dies? My understanding is that background tasks are scheduled for later and do not fire synchronously at termination. -
Is there any signal from APNs/ActivityKit to my server (e.g. a feedback-service-like mechanism) that indicates "this Live Activity's owning app was force-killed", which would let the server short-circuit the heartbeat-based orphan detection?
-
Are there any new APIs in iOS 18.x or the upcoming release that address this specific force-kill → LA-dismissal latency? I could not find anything in the 18.x release notes, but I may have missed it.
What I am NOT asking
- I am not asking how to implement APNs push-to-end (that works).
- I am not asking about
applicationWillTerminate(I already confirmed it does not fire on swipe-kill). - I am not asking about shortening
staleDateas a visual workaround (I already verified it does not trigger body reevaluation post-kill).
Environment
- iOS 26.4.1 (also reproducible on 18.x devices I have on hand)
- iPhone 17 Pro Max, iPhone 15 Pro, iPad Air 11-inch (M3)
- Xcode 26.x
Activity.request(pushType: .token)withActivityContent+ customstalenessInterval = 120s- APNs HTTP/2 via token auth (
.p8), targetingapi.push.apple.comin production apns-push-type: liveactivity,apns-priority: 10, payload includesevent: end
What I have tried (for the record, to avoid "did you try" responses)
applicationWillTerminatewithDispatchSemaphore3.5 s sync wait +dismissalPolicy: .immediate— works only for OS-terminate, not swipe-kill.stalenessInterval = 30s+ 15 s refresh cadence + override to 5 s onAppLifecycleState.paused— verified not to dismiss the LA after app death.- Cold-start reconciliation via
Activity<...>.activitieson next app launch — works, but that only helps if the user relaunches. - Current APNs push-to-end with 60 s / 135 s / 30 s configuration — works, but latency is the complaint.
Any guidance, even "no, ~2 minutes is the floor by design" with a pointer to the relevant doc, would be very helpful. Thank you.