The AlarmKit alarm keeps ringing after you open the app
We concluded iOS was impossible. Then the requirement changed, and the same platform turned out to be fine. The analysis was not wrong — it answered a different question.
Miracle Alarm is an alarm you switch off by answering a question out loud. The alarm has to keep sounding while the question is being answered, or the whole mechanism collapses into an ordinary dismiss button with extra steps.
Our first analysis said iOS could not do this. That conclusion was wrong, and the way it was wrong is more interesting than the fix.
The original conclusion: impossible
The reasoning was straightforward. AlarmKit does not let an alarm wake your app and hand it the screen. An alarm presents its own system UI; the app does not get to take over. If the entire interaction — hear the alarm, get asked a question, speak an answer, be judged correct — has to happen on the lock screen, then iOS simply does not offer the surface for it. Android does. iOS does not.
That is a correct analysis of the question it was given.
The premise moved
The requirement changed underneath it. The flow is no longer “handle everything on the lock screen”. It is:
- The alarm fires and presents its system UI.
- The lock screen offers exactly one button: Start.
- Tapping Start opens the app.
- The questions happen inside the app, where we control the whole screen.
Once that is the requirement, “AlarmKit cannot wake the app and own the lock screen” stops being a blocker, because we no longer need it to.
The finding that makes it work
The part that had to be validated, and was:
An AlarmKit alarm keeps ringing even after the user opens the app through a custom Start button. Opening the app does not stop it.
The alarm stops when — and only when — you explicitly tell it to:
import AlarmKit
// The alarm is still sounding while the user answers.
// Nothing about opening the app silences it.
func onAnswerAccepted(alarmID: UUID) throws {
try AlarmManager.shared.stop(id: alarmID)
}
That single call is the entire off switch, and we own when it fires. So the design holds on iOS exactly as specified: one button on the lock screen, the questions in-app, and the sound continuing the whole time until the user actually answers.
Findings
- AlarmKit alarms continue sounding after the app is foregrounded via a custom button. Verified behaviour, not an inference from the docs.
AlarmManager.shared.stop(id:)is the explicit and only stop we rely on.- A lock-screen button that opens the app is sufficient; we never needed the alarm to hand us the lock screen.
- The in-app question flow therefore runs against a live, still-ringing alarm, which is precisely what the product requires.
Two caveats that are still open
Neither of these invalidates the design, and both are unresolved:
- AlarmKit’s automatic silence timeout is undocumented by Apple. An alarm does not ring forever; at some point the system stops it on its own. How long that is, and whether it varies, has to be measured on a real device. We have not measured it yet.
stopIntentis reported not to fire on swipe-dismiss. So alarm-end detection cannot be built on top of it. Anything that needs to know the alarm ended has to determine that some other way, and we have designed on that assumption rather than hoping the intent shows up.
The part actually worth writing down
The first analysis was not a bad analysis. It was an analysis of a different question.
It asked: can the full interaction happen on the lock screen? Answer: no. The new question was: can the alarm keep ringing while the interaction happens in the app? Answer: yes. Same platform, same API, same engineer — and opposite conclusions, because the premise moved and nobody re-ran the analysis against the new one.
The failure mode is not “we were wrong about iOS”. It is that a conclusion outlived its premise, and kept getting quoted as a fact about the platform rather than as an answer to a question that had since been withdrawn. That is a much easier mistake to make than being bad at reading documentation, and it is worth stating plainly: when the requirement changes, the old verdict is not evidence. It is a stale answer to a question nobody is asking any more.