It’s no exaggeration to say that alarms run my life.
Without setting alarms for literally everything, I would be reduced to a gibbering, suppurating, boneless puddle that barely qualifies as a human being. My wife would also leave me*.
*she told me to add this
But, wielding my alarms (and a little “confidence”), I’m a 10x superhuman capable of delivering at a Series A startup, raising 2 small children, and growing a nascent blogging business.
So imagine my excitement when Apple announced at WWDC 2025 that they are going to expose system functionality to us mere mortals, enabling us to create timers and alarms of our own accord!
Today, we’re going to look at the new AlarmKit API:
First, we’ll learn how AlarmKit works under the hood.
Next, we’ll understand how to use it:
Authorisation
Scheduling alarms
Customising alarm UI and sounds
Finally, we’re going to build the new CEO of my life: my new, open-source ADHDAlarms app, even more convenient and powerful than the system Clock app. I’m talking:
Daily alarms (standup, taking meds)
Weekly alarms (putting the bins out, cat flea medicine)
Spammy alarms (really annoying tasks you’re putting off, like shaving, or returning a bin to B&Q because it’s the wrong shape. Right, so not to get into it but ChatGPT explained to me that a curved edge is ideal for preventing the bag ripping when taking the bins out, which is by far my biggest pain point, but my wife had already bought the rectangle bin because it’s the same one we had before, and I got quite distracted before I could tell her not to, but now we can’t find the receipt and so now we just have a mildly expensive bin in the hallway that we don’t want and can’t return)
Sorry, what were we talking about again?
Right on. AlarmKit.
This post is pretty long, so it may be cut off in some email clients. Read the full article uninterrupted, on my website.
Live Activity stuff and Dynamic Island malarkey are huge rabbit holes, so I will cover them another time, where I can give Widgets and ActivityKit the love they deserve.
On with the show.
How AlarmKit works under the hood
If you’ve ever suffered from sleep paralysis, AlarmKit is largely powered by the same thing under the hood: Daemons.
Subscribe to Jacob’s Tech Tavern for free to get ludicrously in-depth articles on iOS, Swift, tech, & indie projects in your inbox every week.
Paid members get several benefits:
Read Elite Hacks, my exclusive advanced content 🌟
See my free articles 3 weeks before anyone else 🚀
Access my brand-new Swift Concurrency course 🧵
Daemons are system programs that run in the background. On iOS, they power core services such as the home screen (SpringBoard), touch & motion events from hardware (backboardd), audio & video playback (mediaserverd), and location services (locationd).
A daemon also powers iOS alarms.
*Since Apple hasn’t given that much detail, I’m making an educated assumptions that the AlarmKit API uses the same underlying framework and daemon as the regular Clock app. Because, uh, of course that’s what they’re doing. I’ll refer to the Clock app here, to help draw a line between guaranteed facts vs speculation.
The basic underlying functionality in the Clock app is provided by MobileTimer.framework. This framework contains the full functionality powering timers and alarms.
When using classdump-dyld or RuntimeBrowser, the full header interface for the alarms can be generated into a header file, Alarm.h.
The interface is very similar to the AlarmKit scheduling parameters we’re going to look at soon, so it’s a fairly clear indication that Clock and AlarmKit are both built on top of this framework. So I think we are okay to use them interchangeably going forward.
The Clock app has a private entitlement, com.apple.private.mobiletimerd, which grants it permissions to communicate with the mobile timer daemon (which itself links and imports MobileTimer.framework). This privileged program runs constantly in the background, monitoring alarms set up by the framework, scheduling them, and triggering them by calling _fireScheduledAlarm().
-(void)_fireScheduledAlarm:(id)
arg1 firedDate:(id)
arg2 completionBlock:(/*^block*/ id)arg3;
When fired, the alarm tells SpringBoard to interrupt whatever’s happening, and present the alarm UI.
Alarms have special system privileges:
They override silent mode.
They use a private AVKit audio session category to ignore muting.
They are whitelisted by focus modes like Do Not Disturb or Sleep Focus.
We can learn a lot about private APIs by lurking on Jailbreak forums. The BetterAlarm app demonstrates how it creates an alternative alarm screen interface by hooking into mediaserverd to intercept the alarm playback and presenting an alternative experience from SpringBoard.
Now we have an idea what it’s doing under the hood, let’s look at how to actually use AlarmKit.
Authorization
Like all system capabilities, we need to set up permissions. It works much the same way as permissions for hardware like push notifications, camera, and microphone.
First we set up the usage description in the Info.plist, the NSAlarmKitUsageDescription.
Then in your code, request authorisation from AlarmManager.
When called, we get a standard system dialog (plus my spicy flavour text).
Erm, well that’s the really uninteresting bit out of the way. Let’s make some alarms.
Scheduling Alarms
Setting up the first alarm is basically a bit of boilerplate, but then becomes very configurable once you power through.
Fixed Alarms
Alarm.Schedule defines when the alarm triggers. Fixed schedules trigger at a specific timestamp in the future, ignoring timezones.
This goes off 3 seconds after calling this function. It’s good for testing. My attention span isn’t that bad.
Relative Alarms
Relative alarm schedules are better for my use case, since we can set them for specific dates and times, with repetition. If your timezone changes, these will adjust accordingly.
I can use this schedule to ensure I don’t get distracted by my cats and miss my 10am standup. I told it to run weekly on the days and times given.
Scheduling the Alarm
To actually set up the alarm on this schedule, we need to create an AlarmConfiguration object and pass this into the AlarmManager.schedule() method.
Building and running the app, we see the configuration we created in a full-screen interrupt that displays after 3 seconds:
Frankly, the API is a little weird, in that everything kind of nests inside everything else, which is simple but very verbose.
AlarmManager.shared.schedule is the final AlarmKit call which actually schedules the AlarmConfiguration in the future.
AlarmConfiguration stores the schedule (when) and the attributes (visual elements). It can also contain custom sounds, custom snooze duration, and custom app intents for both the stop and snooze buttons.
AlarmAttributes handles the visual presentation, including colour, icon metadata, and Presentation.
AlarmPresentation defines the buttons and text. These can be defined for both an alarm (the original scheduled thing) and a countdown (during the snooze).
The ADHDMetadata is used to define stuff like icons on a Live Activity or configurable attributes. For now, you don’t really need to do anything other than define an empty structure conforming to AlarmMetadata to set up the AlarmAttributes.
struct ADHDMetadata: AlarmMetadata {
init() {}
}
Countdown Timers
As well as traditional alarms, we can set up one-off timers. You can do this with the AlarmConfiguration(countdownDuration:) form, or the AlarmConfiguration.timer() form. These work pretty similarly, with the countdownDuration allowing for a configurable snooze duration:
The countdownDuration form of Alarm configuration sets the alarm screen at a time interval of preAlert seconds from now. Ten seconds after running the app, we get this bad boy interrupting our screen:
Tapping “I forgot” causes the timer to snooze, and the screen presents again after the postAlert duration, again and again until you remember to take your meds. Configuring this postAlert will be fantastic for our spammy alarms.
But let’s not get sidetracked. We don’t massively care about Live Activity configurations and countdowns. I’m not a cook. But I have been cooking.
The CEO of my Life
Thanks to this initial experimentation, I was able to kickstart some back-and-forth with our Claude and saviour, and now I have the first inklings of my ultimate ADHD Alarm life-management app.
We pull together all the customisation options within AlarmKit, but focusing on the most important usability options missing in the vanilla Clock app:
Quickly selecting between repeat-daily or specific days of the week
Customisable snooze duration
Alarm noises ripped straight from a MLG meme soundboard
Colour selection (a mind hack to get my kid interested in my work)
Instead of relying on the AlarmKit API alarms property, we store everything in SwiftData and list them. This allows us to easily re-open and easily edit the individual alarms, as well as set names for them.
Last Orders
I was deeply happy when this API finally came out*.
*Maybe not so happy once I started experimenting, but that’s mainly because my MacOS 26 machine is a piece of garbage.
The AlarmKit API is almost certainly powered by the same engine which powers system Alarms from the Clock app: MobileTimer.framework and the mobiletimerd daemon. AlarmKit brings a fresh wealth of customisation to a system permission which, outside jailbreaks, has been locked down since the first iPhone.
But, AlarmKit also represents a trend in Apple APIs that started with Widgets, became even more restrictive with Dynamic Island, and now exists in an even more egregious form in the AlarmKit UI. A trend where we get less and less customisation options, with highly restrictive and somewhat clunky APIs.
The very simple UI offers very limited options, however we are still forced to wrap 5 layers of different structs from AlarmButton to AlarmManager.schedule() to make it work. But, I suppose, when we’re dealing with an interrupting system thing, strict restrictions are probably a necessary evil to prevent abuse.
Check out the open-source code for ADHDAlarms here! Once iOS 26 is live, I’ll post it to the app store for free.
Thanks for reading Jacob’s Tech Tavern! 🍺
Wait, custom sounds work for you?! On a real device?