Post

Replies

Boosts

Views

Created

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 } } }
1
0
149
Feb ’26