Persist and deliver scheduled local notifications after app update

I have an app available for download in the Apple App Store. The app sends local notifications, which are scheduled at the user's request once the app launches. I've recently learned that when new versions of my app are deployed and automatically update on the user's device, previously scheduled local notifications are deleted. Given my app design, the user can re-launch the app in order to re-schedule the local notifications. This is a bit of a problem, though, because part of my app's value is in reminding the user - so after requesting a local notification, the user expects to receive a local notification and then launch the app, not the other way around.

Given this, I've been exploring solutions so my app continues to function as expected (including delivering local notifications, even if the app hasn't yet been launched) after an app update. I've explored .backgroundTasks(), but they too are apparently deleted with an app update and require the app to be re-launched first to work as expected. Another solution might be to use push notifications instead of local notifications, but that seems like a very involved solution if I'm just looking to make sure that local notifications persist after an app update. I can't be the only person to have this dilemma - am I overlooking a simple solution?

This is not a correct observation. Scheduled local notifications will persist after an app update as long as you have not changed the app’s bundle id. Is your app doing things like removing scheduled notifications in between sessions or something that might be getting triggered somehow?

Is this all native code or are you using some sort of a third party library to manage your notifications that might be doing things with them without you knowing what?

What you have observed is not an expected behavior. Scheduled local notifications will stay scheduled across normal app updates. There must definitely be something else going on with your app.

Hi Apple Engineer - I hate to post this message because I really wanted to be mistaken in my original post, but I just deployed an updated version of my app and previously scheduled local notifications were NOT delivered after the update. I carefully tested this with the user who originally brought this issue to my attention, by asking him to: 1) schedule a notification right before the version update and then 2) allow the app to update silently in the background. After the update, the local notification was NOT delivered. I did NOT change the app's bundle ID, nor am I using any sort of third party tool. The user who reported this issue has an iPhone 14 Pro Max, iOS 18.2.1, and allowed the app to update silently in the background. After re-launching the app, local notifications continued as before. So, it does appear to be the case that the update breaks previously scheduled local notifications until the app is relaunched.

I've included my code to schedule local notifications below. I'm running this code when my app changes phase and moves to the .background. If the issue I'm describing with scheduled local notifications is NOT the expected behavior, do you see anything in my code that would cause this?

func createLocalNotifications() {
        
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
        
 // Clear all the old notifications to replace them with newly scheduled notifications in this function
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()

        let context = sharedModelContainer.mainContext
        var query = FetchDescriptor<Item>(predicate: #Predicate { item in item.reminder == true},
                                                 sortBy: [SortDescriptor(\Item.reminderDate,
                                                                          order: .forward)])
        query.fetchLimit = 60
        
        do {
            let items = try context.fetch(query)
            
            for notificationItem in items {
                
                if notificationItem.reminder == true {
                    let content = UNMutableNotificationContent()
                    content.title = "Reminder"
                    content.body = notificationItem.name 
                    content.sound = .default
                    
                    // Local notification trigger
                    let calendar = Calendar.current
                    var triggerDate = calendar.dateComponents([Calendar.Component.day, Calendar.Component.month, Calendar.Component.year], from: notificationItem.reminderDate!)
                 
                    triggerDate.hour = Calendar.current.component(.hour, from: userSettings.reminderTimeOfDay)
                    triggerDate.minute = Calendar.current.component(.minute, from: userSettings.reminderTimeOfDay)

                    let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: false)
                    
                    // Local notification request with content and trigger
                    let notificationUUID = "\(notificationItem.id)" + "-localNotificationID"
                    let request = UNNotificationRequest(identifier: notificationUUID, content: content, trigger: trigger)
                    
                    // Schedule the request
                    UNUserNotificationCenter.current().add(request) { error in
                        guard error == nil else { return }
                    }
                    
                } // End of notificationItem.reminder == true
            } // End of notificationItem in items
            
        } catch {
            print("Local notification error")
        } // End of catch
        
    } // End of createLocalNotifications()

In case this is relevant, I also have an AppDelegate file with this code:

class AppDelegate: NSObject, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        UNUserNotificationCenter.current().delegate = self
        return true
        
    } // End of func didFinishLaunchingWithOptions
    
} // End of AppDelegate

// Show local notification in foreground
extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.list, .banner, .badge])
    }
}
Persist and deliver scheduled local notifications after app update
 
 
Q