Notifications scheduled but never delivered at scheduled time

Device: iPhone (real device) iOS: 17.x Permission: Granted

Notifications are scheduled using UNCalendarNotificationTrigger. The function runs and prints "SCHEDULING STARTED". However, notifications never appear at 8:00 AM, even the next day.

Here is my DailyNotifications file code:

import Foundation import UserNotifications

enum DailyNotifications {

// CHANGE THESE TWO FOR TESTING / PRODUCTION

// For testing set to a few minutes ahead
static let hour: Int = 8
static let minute: Int = 0

// For production use:
// static let hour: Int = 9
// static let minute: Int = 0

static let daysToSchedule: Int = 30
private static let idPrefix = "daily-thought-"
private static let categoryId = "DAILY_THOUGHT"

// MARK: - Permission

static func requestPermission(completion: @escaping (Bool) -> Void) {
    let center = UNUserNotificationCenter.current()

    center.requestAuthorization(options: [.alert, .sound]) { granted, _ in
        DispatchQueue.main.async {
            completion(granted)
        }
    }
}

// MARK: - Schedule

static func scheduleNext30Days(isPro: Bool) {

    print("SCHEDULING STARTED")

    let center = UNUserNotificationCenter.current()

    center.getNotificationSettings { settings in

        guard settings.authorizationStatus == .authorized else {
            requestPermission { granted in
                if granted {
                    scheduleNext30Days(isPro: isPro)
                }
            }
            return
        }

        // Remove old scheduled notifications
        center.getPendingNotificationRequests { pending in
            let idsToRemove = pending
                .map { $0.identifier }
                .filter { $0.hasPrefix(idPrefix) }

            center.removePendingNotificationRequests(withIdentifiers: idsToRemove)

            let calendar = Calendar.current
            let now = Date()

            for offset in 0..<daysToSchedule {

                guard let date = calendar.date(byAdding: .day, value: offset, to: now) else { continue }

                var comps = calendar.dateComponents([.year, .month, .day], from: date)
                comps.hour = hour
                comps.minute = minute

                guard let scheduleDate = calendar.date(from: comps) else { continue }

                if scheduleDate <= now { continue }

                let content = UNMutableNotificationContent()
                content.title = "Just One Thought"
                content.sound = .default
                content.categoryIdentifier = categoryId

                if isPro {
                    content.body = thoughtForDate(scheduleDate)
                } else {
                    content.body = "Your new thought is ready. Go Pro to reveal it."
                }

                let triggerComps = calendar.dateComponents(
                    [.year, .month, .day, .hour, .minute],
                    from: scheduleDate
                )

                let trigger = UNCalendarNotificationTrigger(
                    dateMatching: triggerComps,
                    repeats: false
                )

                let identifier = idPrefix + isoDay(scheduleDate)

                let request = UNNotificationRequest(
                    identifier: identifier,
                    content: content,
                    trigger: trigger
                )

                center.add(request)
            }
        }
    }
}

// MARK: - Cancel

static func cancelAllScheduledDailyThoughts() {
    let center = UNUserNotificationCenter.current()

    center.getPendingNotificationRequests { pending in
        let idsToRemove = pending
            .map { $0.identifier }
            .filter { $0.hasPrefix(idPrefix) }

        center.removePendingNotificationRequests(withIdentifiers: idsToRemove)
    }
}

// MARK: - Helpers

private static func isoDay(_ date: Date) -> String {
    let formatter = DateFormatter()
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.dateFormat = "yyyy-MM-dd"
    return formatter.string(from: date)
}

private static func thoughtForDate(_ date: Date) -> String {

    guard let url = Bundle.main.url(forResource: "thoughts", withExtension: "json"),
          let data = try? Data(contentsOf: url),
          let quotes = try? JSONDecoder().decode([String].self, from: data),
          !quotes.isEmpty
    else {
        return "Stay steady. Your growth is happening."
    }

    let calendar = Calendar.current
    let comps = calendar.dateComponents([.year, .month, .day], from: date)

    let seed =
        (comps.year ?? 0) * 10000 +
        (comps.month ?? 0) * 100 +
        (comps.day ?? 0)

    let index = abs(seed) % quotes.count

    return quotes[index]
}

}

Then here is my Justonethoughtapp code:

import SwiftUI import UserNotifications

@main struct JustOneThoughtApp: App {

@StateObject private var thoughtStore = ThoughtStore()

// MUST match App Store Connect EXACTLY
@StateObject private var subManager =
    SubscriptionManager(productIDs: ["Justonethought.monthly"])

var body: some Scene {
    WindowGroup {
        ContentView()
            .environmentObject(thoughtStore)
            .environmentObject(subManager)
            .onAppear {

                // Ask for notification permission
                NotificationManager.shared.requestPermission()

                // Schedule notifications using PRO status
                DailyNotifications.scheduleNext30Days(
                    isPro: subManager.isPro
                )
            }
    }
}

}

final class NotificationManager {

static let shared = NotificationManager()
private init() {}

func requestPermission() {
    UNUserNotificationCenter.current().requestAuthorization(
        options: [.alert, .sound, .badge]
    ) { _, _ in }
}

}

You are not setting the year, month, day of the month.

var comps = calendar.dateComponents([.year, .month, .day], from: date)
comps.hour = hour
comps.minute = minute
Notifications scheduled but never delivered at scheduled time
 
 
Q