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:
applicationWillTerminate is not called on swipe-up force-kill, only on OS-initiated termination or crash. So synchronous Activity.end(...) from the app itself is not a solution for the force-kill path.
Shortening staleDate does 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 sends event: end via APNs).
Current architecture
I have APNs push-to-end working end-to-end. Structure:
Client: Activity.request(pushType: .token), subscribe to Activity.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 whose last_heartbeat < now() - grace_ttl and sends APNs event: end to the stored token.
Parameters I am currently running with:
Parameter
Value
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 / BGProcessingTask have 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 staleDate as 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) with ActivityContent + custom stalenessInterval = 120s
APNs HTTP/2 via token auth (.p8), targeting api.push.apple.com in production
apns-push-type: liveactivity, apns-priority: 10, payload includes event: end
What I have tried (for the record, to avoid "did you try" responses)
applicationWillTerminate with DispatchSemaphore 3.5 s sync wait + dismissalPolicy: .immediate — works only for OS-terminate, not swipe-kill.
stalenessInterval = 30s + 15 s refresh cadence + override to 5 s on AppLifecycleState.paused — verified not to dismiss the LA after app death.
Cold-start reconciliation via Activity<...>.activities on 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.