iosswiftswiftuiapple-push-notifications

SwiftUI: Scheduling Daily, Dynamic Notifications Fetched From API


I've previously tried many times to figure out a way to send out local push notifications of quotes from my own API (connected to my own database)—might be useful info in case there's some change I could make to the API.

My goal is to send out a daily quote at the user's specified time during the day, with different quote info, fetched by the getRandomQuoteByClassification() call. This function gets a quote based on the user's selected quote category, which could change at any time. As such, I'd want to only schedule/send one notification at a time with the specified quote category—unlike other apps with a constant notification to send out at a specified time, I want this notification to be dynamic.

Currently, the quote sends out fine the first time, but simply sends out the exact same notification at the same time the next day.

I've read up on answers such as this one, which mention how I would need to send out a remote notification call, as opposed to a local one. In practice, though, I could really use some guidance on how to incorporate this into my current implementation below:

    private func scheduleNotifications() {
        // Cancel existing notifications to reschedule them with the new time
        UNUserNotificationCenter.current().removeAllPendingNotificationRequests()

        // Get the selected time from notificationTime
        let selectedTime = Calendar.current.dateComponents([.hour, .minute], from: notificationTime)

        // Create a trigger date for the selected time
        guard let triggerDate = Calendar.current.date(from: selectedTime) else {
            print("Error: Couldn't create trigger date.")
            return
        }

        // Create a date components for the trigger time
        let triggerComponents = Calendar.current.dateComponents([.hour, .minute], from: triggerDate)

        // Create a trigger for the notification to repeat daily at the selected time
        let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: true)

        // Retrieve a new quote
        getRandomQuoteByClassification(classification: getSelectedQuoteCategory().lowercased()) { quote, error in
            if let quote = quote {
                // Create notification content
                let content = UNMutableNotificationContent()
                if getSelectedQuoteCategory() == QuoteCategory.all.rawValue {
                    content.title = "Quote Droplet"
                } else {
                    content.title = "Quote Droplet - \(getSelectedQuoteCategory())"
                }
                if let author = quote.author, !author.isEmpty {
                    if author == "Unknown Author" {
                        content.body = quote.text
                    } else {
                        content.body = "\(quote.text)\n- \(author)"
                    }
                } else {
                    content.body = quote.text
                }
                content.sound = UNNotificationSound.default

                // Generate a unique identifier for this notification
                let notificationID = UUID().uuidString

                // Create notification request
                let request = UNNotificationRequest(identifier: notificationID, content: content, trigger: trigger)

                // Schedule the notification
                UNUserNotificationCenter.current().add(request) { error in
                    if let error = error {
                        print("Error scheduling notification: \(error.localizedDescription)")
                    } else {
                        print("Notification scheduled successfully.")
                        print("Body of notification scheduled: \(content.body)")
                        print("Scheduled for this time: \(selectedTime)")
                    }
                }
            } else if let error = error {
                print("Error retrieving quote: \(error.localizedDescription)")
            } else {
                print("Unknown error retrieving quote.")
            }
        }
    }

I'd greatly appreciate any code suggestions or feedback on my thinking through this problem. Thanks.


Solution

  • I simply ended up scheduling local notifications, using local data (a JSON file I have within the project). Note that you can only schedule 64 local notifications (see this Apple article for more info). Then, I believe you'll need the user to have re-launched the app for more to be scheduled.

    Here's the code if it's useful to anyone:

        func scheduleNotifications(notificationTime: Date, quoteCategory: QuoteCategory) {
            UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
            
            let classification = quoteCategory.displayName
            
            let calendar = Calendar.current
            
            let currentDate = calendar.startOfDay(for: Date())
            
            // 60, meaning 60 days
            for i in 0..<60 {
                var triggerDate = calendar.dateComponents([.hour, .minute], from: notificationTime)
                triggerDate.day = calendar.component(.day, from: currentDate) + i
                
                let content = UNMutableNotificationContent()
                
                let shortQuotes = quotes.filter{ $0.text.count <= 100 }
                
                var randomQuote: QuoteJSON
                if classification.lowercased() == "all" {
                    guard let randomElement = shortQuotes.randomElement() else {
                        print("Error: Unable to retrieve a random quote.")
                        continue
                    }
                    randomQuote = randomElement
                    content.title = "Quote Droplet"
                } else if classification.lowercased() == "favorites" {
                    let bookmarkedQuotes = getBookmarkedQuotes().map { $0.toQuoteJSON() }
                        
                    if !bookmarkedQuotes.isEmpty {
                        let randomIndex = Int.random(in: 0..<bookmarkedQuotes.count)
                        
                        let randomElement = bookmarkedQuotes[randomIndex]
                        randomQuote = randomElement
                        
                        content.title = "Quote Droplet: Favorites"
                    } else {
                        randomQuote = QuoteJSON(id: 9999999, text: "Please add a quote to favorites by clicking the favorites button under a quote in the app's \"Droplets\" tab", author: "", classification: "Favorites")
                        content.title = "Quote Droplet: No Favorites Added"
    
                    }
    
                } else {
                    let filteredQuotes = shortQuotes.filter { $0.classification.lowercased() == classification.lowercased() }
                    guard let randomElement = filteredQuotes.randomElement() else {
                        print("Error: Unable to retrieve a random quote.")
                        continue
                    }
                    randomQuote = randomElement
                    content.title = "Quote Droplet: \(classification)"
                }
                
                if (randomQuote.author != "Unknown Author" && randomQuote.author != "" && randomQuote.author != "NULL" && ((randomQuote.author.isEmpty))) {
                    content.body = "\"\(randomQuote.text)\"\n— \(randomQuote.author)"
                } else {
                    content.body = "\"\(randomQuote.text)\""
                }
                
                content.sound = UNNotificationSound.default
                
                let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: false)
                
                let notificationID = UUID().uuidString
                
                let request = UNNotificationRequest(identifier: notificationID, content: content, trigger: trigger)
                
                UNUserNotificationCenter.current().add(request) { error in
                    if let error = error {
                        print("Error scheduling notification: \(error.localizedDescription)")
                    } else {
                        print("Notification scheduled successfully.")
                        print("Body of notification scheduled: \(content.body)")
                        print("Scheduled for this time: \(triggerDate)")
                    }
                }
            }
        }