<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged notifications</title>
<link>https://coderlegion.com/tag/notifications</link>
<description>Powered by Question2Answer</description>
<item>
<title>Medication Reminders That Actually Fire: Local Notifications and the 64-Notification Limit</title>
<link>https://coderlegion.com/32272/medication-reminders-that-actually-fire-local-notifications-and-the-notification-limit</link>
<description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/blog/medication-reminders-that-actually-fire/&quot;&gt;https://bestlatestapps.com/blog/medication-reminders-that-actually-fire/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Repeating calendar triggers, a notification budget, &quot;Taken&quot; and &quot;Snooze&quot; actions, run-out dates and on-device privacy, from building a pill reminder app&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/medication-reminders-that-actually-fire/daily-pills-icon-512.jpg&quot; alt=&quot;Daily Pills | Reminder app icon&quot;&gt;&lt;/p&gt;
&lt;p&gt;Most apps can afford a missed notification. A medication reminder can't. If the 9 p.m. reminder silently doesn't arrive, someone misses a dose, and they stop trusting the app the same evening.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Daily Pills | Reminder&lt;/strong&gt; is my take on the simplest version of this problem, live since May 2020: set the times you take your pills, get reminded, see how many you have left and know when you'll run out. The listing sums it up as &quot;Never miss your medication again!&quot; and promises that &quot;your medication data stays private on your device.&quot;&lt;/p&gt;
&lt;p&gt;That combination, reliable reminders with no server, rests entirely on &lt;strong&gt;local notifications&lt;/strong&gt;, and they have more sharp edges than most developers expect: a hard cap on pending notifications, time-zone behaviour, actions that run while the phone is locked, and data that must be readable at that moment. This article covers how I'd build the core: the data model, repeating triggers, a notification budget, actionable notifications, supply maths and on-device storage. API names and availability are checked against Apple's documentation, and this is the approach I'd use rather than the app's exact source.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-model-keep-it-boring&quot;&gt;1. The model: keep it boring&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

struct ReminderTime: Codable, Hashable, Identifiable {
    var hour: Int          // 0...23, local wall-clock time
    var minute: Int
    var id: String { String(format: &quot;%02d%02d&quot;, hour, minute) }
}

struct Medication: Codable, Identifiable {
    var id = UUID()
    var name: String
    var pillsPerDose: Int = 1
    var times: [ReminderTime]                 // multiple reminders per day
    var pillsRemaining: Int
    var lowSupplyWarningDays: Int = 5
    var log: [DoseEvent] = []
}

struct DoseEvent: Codable, Hashable {
    enum Kind: String, Codable { case taken, missed, skipped }
    var date: Date
    var kind: Kind
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two choices to notice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reminder times are an hour and a minute, not a &lt;code&gt;Date&lt;/code&gt;.&lt;/strong&gt; &quot;8:00 every morning&quot; is a wall-clock intention. Storing it as a &lt;code&gt;Date&lt;/code&gt; bakes in a time zone, and the reminder drifts when the user travels or when daylight saving changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The log is append-only.&lt;/strong&gt; Missed and taken doses are facts. Supply and adherence are computed from them, never stored separately where they could disagree.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-repeating-reminders-with-uncalendarnotificationtrigger&quot;&gt;2. Repeating reminders with UNCalendarNotificationTrigger&lt;/h3&gt;
&lt;p&gt;For &quot;every day at 08:00&quot;, a single repeating calendar trigger is exactly right:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import UserNotifications

enum ReminderScheduler {
    static let doseCategory = &quot;DOSE_REMINDER&quot;

    static func identifier(for med: Medication, time: ReminderTime) -&amp;gt; String {
        &quot;dose.\(med.id.uuidString).\(time.id)&quot;
    }

    static func schedule(_ med: Medication) async throws {
        let center = UNUserNotificationCenter.current()
        // Start clean for this medication so edits never leave stale reminders behind.
        await cancel(med)

        for time in med.times {
            let content = UNMutableNotificationContent()
            content.title = &quot;Time for \(med.name)&quot;
            content.body = med.pillsPerDose == 1 ? &quot;Take 1 pill.&quot; : &quot;Take \(med.pillsPerDose) pills.&quot;
            content.sound = .default
            content.categoryIdentifier = doseCategory
            content.userInfo = [&quot;medID&quot;: med.id.uuidString, &quot;time&quot;: time.id]
            if #available(iOS 15.0, *) {
                content.interruptionLevel = .timeSensitive   // needs the Time Sensitive capability
            }

            // Only hour and minute: fires every day at that local wall-clock time.
            var components = DateComponents()
            components.hour = time.hour
            components.minute = time.minute
            let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)

            let request = UNNotificationRequest(identifier: identifier(for: med, time: time),
                                                content: content, trigger: trigger)
            try await center.add(request)
        }
    }

    static func cancel(_ med: Medication) async {
        let center = UNUserNotificationCenter.current()
        let ids = await center.pendingNotificationRequests()
            .map(\.identifier)
            .filter { $0.hasPrefix(&quot;dose.\(med.id.uuidString).&quot;) }
        center.removePendingNotificationRequests(withIdentifiers: ids)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What makes this reliable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deterministic identifiers.&lt;/strong&gt; &lt;code&gt;dose.&amp;lt;medID&amp;gt;.&amp;lt;HHmm&amp;gt;&lt;/code&gt; means rescheduling replaces rather than duplicates, and cancelling one medication can't touch another's reminders.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Date components without a time zone&lt;/strong&gt; fire at local wall-clock time. If the user flies from Delhi to London, &quot;08:00&quot; stays 08:00 local, which is usually what a daily pill means. (For medications that must be taken at strict &lt;em&gt;intervals&lt;/em&gt; regardless of time zone, that's a different design: talk to the user, and consider fixed-interval triggers.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.timeSensitive&lt;/code&gt; (iOS 15+)&lt;/strong&gt; lets the reminder break through Focus modes, if the user allows it. It requires adding the Time Sensitive Notifications capability. Use it only for genuinely time-sensitive content, which a dose reminder is.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And of course, ask permission first, at the moment the user adds their first reminder, with &lt;code&gt;requestAuthorization(options: [.alert, .sound, .badge])&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-the-64-notification-limit-and-a-budget&quot;&gt;3. The 64-notification limit, and a budget&lt;/h3&gt;
&lt;p&gt;Here's the trap. iOS keeps only a limited number of pending local notifications per app. Apple's documentation for the older &lt;code&gt;UILocalNotification&lt;/code&gt; API states it plainly: the system keeps the soonest-firing &lt;strong&gt;64&lt;/strong&gt; notifications and &lt;strong&gt;discards the rest&lt;/strong&gt;. Automatically repeating notifications count as a single notification. The same cap is widely observed with &lt;code&gt;UserNotifications&lt;/code&gt;, and it fails &lt;em&gt;silently&lt;/em&gt;: &lt;code&gt;add(_:)&lt;/code&gt; succeeds and the 65th reminder just never fires.&lt;/p&gt;
&lt;p&gt;With repeating triggers, one per medication per time, you're usually fine: 5 medications × 3 times a day is 15 requests. The trouble starts when you schedule &lt;strong&gt;one-off&lt;/strong&gt; notifications: a different message per day (&quot;Day 12 of 30&quot;), weekly schedules built from individual dates, snoozes or low-supply warnings. Each of those is its own slot.&lt;/p&gt;
&lt;p&gt;So treat the pending queue as a budget, and check it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum NotificationBudget {
    static let systemLimit = 64
    static let safetyMargin = 4          // room for snoozes and low-supply warnings

    /// How many requests we'd have after scheduling `meds`.
    static func projected(for meds: [Medication], extraOneOffs: Int = 0) -&amp;gt; Int {
        meds.reduce(0) { $0 + $1.times.count } + extraOneOffs
    }

    static func canSchedule(_ meds: [Medication], extraOneOffs: Int = 0) -&amp;gt; Bool {
        projected(for: meds, extraOneOffs: extraOneOffs) &amp;lt;= systemLimit - safetyMargin
    }

    /// Debug helper: compare what you *think* is scheduled with what the system has.
    static func audit() async {
        let pending = await UNUserNotificationCenter.current().pendingNotificationRequests()
        print(&quot;Pending: \(pending.count) / \(systemLimit)&quot;)
        pending.forEach { print(&quot; •&quot;, $0.identifier, $0.trigger.map(String.init(describing:)) ?? &quot;-&quot;) }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you genuinely need more than the budget allows, for example non-repeating schedules, schedule a &lt;strong&gt;rolling window&lt;/strong&gt; (the next N days) and top it up whenever the app becomes active. Never assume &quot;scheduled once&quot; means &quot;scheduled forever&quot;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-taken-and-snooze-right-from-the-notification&quot;&gt;4. &quot;Taken&quot; and &quot;Snooze&quot; right from the notification&lt;/h3&gt;
&lt;p&gt;A reminder you have to unlock the phone and open the app to acknowledge is a reminder people ignore. Register a category with actions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import UserNotifications

enum DoseActions {
    static let taken = &quot;TAKEN&quot;
    static let snooze = &quot;SNOOZE_10&quot;

    static func register() {
        let takenAction = UNNotificationAction(identifier: taken, title: &quot;Taken&quot;, options: [])
        let snoozeAction = UNNotificationAction(identifier: snooze, title: &quot;Snooze 10 min&quot;, options: [])
        let category = UNNotificationCategory(identifier: ReminderScheduler.doseCategory,
                                              actions: [takenAction, snoozeAction],
                                              intentIdentifiers: [], options: [])
        UNUserNotificationCenter.current().setNotificationCategories([category])
    }
}

final class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
    let store: MedicationStore
    init(store: MedicationStore) { self.store = store }

    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse) async {
        let info = response.notification.request.content.userInfo
        guard let idString = info[&quot;medID&quot;] as? String, let id = UUID(uuidString: idString) else { return }

        switch response.actionIdentifier {
        case DoseActions.taken, UNNotificationDefaultActionIdentifier:
            store.recordDose(for: id, kind: .taken, at: Date())

        case DoseActions.snooze:
            let content = response.notification.request.content.mutableCopy() as! UNMutableNotificationContent
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10 * 60, repeats: false)
            let request = UNNotificationRequest(identifier: &quot;snooze.\(idString)&quot;,   // one snooze per med
                                                content: content, trigger: trigger)
            try? await center.add(request)

        default:
            break
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Register the categories and set the delegate early, in &lt;code&gt;application(_:didFinishLaunchingWithOptions:)&lt;/code&gt; or your &lt;code&gt;App&lt;/code&gt; initialiser, because the system may launch your app in the background specifically to handle an action.&lt;/p&gt;
&lt;p&gt;Notice the action options are empty: no &lt;code&gt;.foreground&lt;/code&gt; (which would open the app) and no &lt;code&gt;.authenticationRequired&lt;/code&gt; (which forces an unlock). &quot;Taken&quot; works straight from the Lock Screen. That has a consequence for storage, which section 6 covers.&lt;/p&gt;
&lt;p&gt;Using one fixed identifier per medication for snoozes means repeated snoozes &lt;em&gt;replace&lt;/em&gt; each other instead of filling your 64-slot budget.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-supply-maths-pills-left-run-out-date-and-a-warning&quot;&gt;5. Supply maths: pills left, run-out date and a warning&lt;/h3&gt;
&lt;p&gt;The listing promises you'll &quot;know when your supply will run out&quot; and &quot;get notified before you run out&quot;. That's arithmetic on the model, plus one more scheduled notification:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

extension Medication {
    var pillsPerDay: Int { pillsPerDose * times.count }

    var daysOfSupplyLeft: Int {
        guard pillsPerDay &amp;gt; 0 else { return .max }
        return pillsRemaining / pillsPerDay
    }

    func runOutDate(from now: Date = Date(), calendar: Calendar = .current) -&amp;gt; Date? {
        guard pillsPerDay &amp;gt; 0 else { return nil }
        return calendar.date(byAdding: .day, value: daysOfSupplyLeft, to: calendar.startOfDay(for: now))
    }

    var missedCount: Int { log.filter { $0.kind == .missed }.count }
}

extension ReminderScheduler {
    /// One-off &quot;running low&quot; reminder at 09:00, `lowSupplyWarningDays` before run-out.
    static func scheduleLowSupplyWarning(for med: Medication, calendar: Calendar = .current) async throws {
        let id = &quot;supply.\(med.id.uuidString)&quot;
        let center = UNUserNotificationCenter.current()
        center.removePendingNotificationRequests(withIdentifiers: [id])

        guard let runOut = med.runOutDate(),
              let warnDay = calendar.date(byAdding: .day, value: -med.lowSupplyWarningDays, to: runOut),
              warnDay &amp;gt; Date() else { return }

        var components = calendar.dateComponents([.year, .month, .day], from: warnDay)
        components.hour = 9
        let content = UNMutableNotificationContent()
        content.title = &quot;Running low on \(med.name)&quot;
        content.body = &quot;About \(med.lowSupplyWarningDays) days left. Time to refill.&quot;
        content.sound = .default

        try await center.add(UNNotificationRequest(
            identifier: id, content: content,
            trigger: UNCalendarNotificationTrigger(dateMatching: components, repeats: false)))
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recalculate and reschedule the warning whenever supply changes: when a dose is taken, when pills are added, or when the schedule is edited. It's one slot per medication, kept accurate by a fixed identifier.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Missed doses.&lt;/strong&gt; The system can't tell you a reminder was &lt;em&gt;ignored&lt;/em&gt;. When the app becomes active, compare each scheduled time since the last check against the log, and record &lt;code&gt;.missed&lt;/code&gt; for any slot without a &lt;code&gt;.taken&lt;/code&gt; event within a grace window (say, 2 hours). Then show it honestly and let the user correct it (&quot;I took it, I just didn't tap&quot;).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-private-on-device-storage-that-works-on-a-locked-phone&quot;&gt;6. Private, on-device storage that works on a locked phone&lt;/h3&gt;
&lt;p&gt;The listing makes a clear promise: &quot;Your medication data stays private on your device. We don't collect or share your personal health information.&quot; The simplest way to keep that promise is to have no server at all: a small &lt;code&gt;Codable&lt;/code&gt; file in Application Support.&lt;/p&gt;
&lt;p&gt;There's one subtlety. The &quot;Taken&quot; action from section 4 can run &lt;strong&gt;while the device is locked&lt;/strong&gt;. If you save with the strongest file protection (&lt;code&gt;.completeFileProtection&lt;/code&gt;), the file is unreadable while locked, and the write fails exactly when the user taps &quot;Taken&quot; on the Lock Screen.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

final class MedicationStore {
    private(set) var medications: [Medication] = []
    private let url: URL

    init() {
        let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
        try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
        url = dir.appendingPathComponent(&quot;medications.json&quot;)
        load()
    }

    func load() {
        guard let data = try? Data(contentsOf: url) else { return }
        medications = (try? JSONDecoder().decode([Medication].self, from: data)) ?? []
    }

    func save() {
        do {
            let data = try JSONEncoder().encode(medications)
            // Encrypted at rest, but readable after the first unlock since boot,
            // so Lock Screen actions can update it.
            try data.write(to: url, options: [.atomic, .completeFileProtectionUntilFirstUserAuthentication])
        } catch {
            print(&quot;Save failed: \(error)&quot;)
        }
    }

    func recordDose(for id: UUID, kind: DoseEvent.Kind, at date: Date) {
        guard let i = medications.firstIndex(where: { $0.id == id }) else { return }
        medications[i].log.append(DoseEvent(date: date, kind: kind))
        if kind == .taken {
            medications[i].pillsRemaining = max(0, medications[i].pillsRemaining - medications[i].pillsPerDose)
        }
        save()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.completeFileProtectionUntilFirstUserAuthentication&lt;/code&gt; (iOS 5+) is the trade-off I'd choose: encrypted when the phone has just rebooted and never been unlocked, and accessible afterwards, so background actions work. Also keep the App Privacy section of your App Store listing consistent with &quot;data not collected&quot;, and don't add an analytics SDK that quietly breaks that promise.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-real-world-example-daily-pills-reminder&quot;&gt;7. Real-world example: Daily Pills | Reminder&lt;/h3&gt;
&lt;p&gt;These are the techniques I'd use, and recommend, for &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/app/daily-pills-reminder/id1510203960&quot;&gt;Daily Pills | Reminder&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/medication-reminders-that-actually-fire/daily-pills-screenshot-1.jpg&quot; alt=&quot;Daily Pills App Store screenshot 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;At the time of writing, its App Store listing shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free&lt;/strong&gt;, in &lt;strong&gt;Health &amp;amp; Fitness&lt;/strong&gt; and &lt;strong&gt;Medical&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;First released in &lt;strong&gt;May 2020&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;8 ratings&lt;/strong&gt; in the US store, averaging about &lt;strong&gt;4.4 stars&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;iOS 16.0 and later&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How the listing maps to this article:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;What the listing says &lt;/th&gt;&lt;th&gt;Where it's covered&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&quot;Set daily reminder times that work for you&quot; / &quot;Multiple reminders per day&quot; &lt;/td&gt;&lt;td&gt;One repeating calendar trigger per time, deterministic IDs (section 2)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Never miss a dose&quot; &lt;/td&gt;&lt;td&gt;Time-sensitive delivery, a notification budget, a rolling window if needed (sections 2–3)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Add pills with one tap&quot; / &quot;Mark missed pills instantly&quot; &lt;/td&gt;&lt;td&gt;An append-only dose log; Lock Screen actions (sections 1, 4)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;See how many pills you have left&quot; / &quot;Know when your supply will run out&quot; / &quot;Get notified before you run out&quot; &lt;/td&gt;&lt;td&gt;Supply maths and a one-off low-supply warning (section 5)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Your medication data stays private on your device&quot; &lt;/td&gt;&lt;td&gt;Local &lt;code&gt;Codable&lt;/code&gt; storage with the right file protection, no server (section 6)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/medication-reminders-that-actually-fire/daily-pills-screenshot-2.jpg&quot; alt=&quot;Daily Pills App Store screenshot 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;I also have a second, more feature-rich medication app, &lt;strong&gt;Track Your Pills&lt;/strong&gt;, with barcode scanning, PDF reports for doctors and widgets. I'm writing that one up separately, because its technical story is quite different.&lt;/p&gt;
&lt;p&gt;A necessary note for anyone building in this space: a reminder app is a convenience tool, not medical advice. Say so in the app, and never let a bug in your scheduling be the only thing between someone and their medication. Clear, honest UI about what's scheduled helps a lot.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-before-you-ship-a-reminder-app-a-checklist&quot;&gt;8. Before you ship a reminder app: a checklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Permission is requested at the moment of need&lt;/strong&gt;, and a denied state shows how to enable notifications in Settings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identifiers are deterministic&lt;/strong&gt;, and edits cancel and reschedule only what they own.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The pending count is audited.&lt;/strong&gt; You know how many of the 64 slots you use, with a safety margin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Times are wall-clock components&lt;/strong&gt;, tested across a time-zone change and a daylight-saving change.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actions work from the Lock Screen&lt;/strong&gt;, including right after a reboot, once the phone has been unlocked once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supply and run-out dates are recomputed&lt;/strong&gt; on every change, and the warning is rescheduled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Missed doses are detected on launch&lt;/strong&gt; and are easy to correct.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Privacy claims match reality.&lt;/strong&gt; No hidden analytics, and the App Privacy details are accurate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You've tested on a real device over several days.&lt;/strong&gt; The Simulator won't show you what happens after a week of real use.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;Reliable reminders on iOS come down to a few disciplined habits: repeating calendar triggers with deterministic identifiers, a budget for the 64-notification cap, actions that work from the Lock Screen, and storage that can be written while the phone is locked. Add simple, honest supply maths and you have an app people can rely on.&lt;/p&gt;
&lt;p&gt;Try it, or explore my other apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Daily Pills | Reminder (iOS):&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/app/daily-pills-reminder/id1510203960&quot;&gt;https://apps.apple.com/us/app/daily-pills-reminder/id1510203960&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All of my free iOS apps:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/developer/alok-singh/id1508795536&quot;&gt;https://apps.apple.com/us/developer/alok-singh/id1508795536&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My portfolio:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/alok/&quot;&gt;https://bestlatestapps.com/alok/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have you hit the 64-notification limit in production? I'd love to hear how you discovered it. Tell me in the comments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;— Alok Kumar Singh&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/blog/medication-reminders-that-actually-fire/&quot;&gt;https://bestlatestapps.com/blog/medication-reminders-that-actually-fire/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32272/medication-reminders-that-actually-fire-local-notifications-and-the-notification-limit</guid>
<pubDate>Sat, 24 Oct 2026 12:40:17 +0000</pubDate>
</item>
</channel>
</rss>