2

I made an app that sends a local notification showing the user a random prime number every day at 9 AM.

The issue is that the shown number is always the same.
The code that creates the Notification Request only gets called once (which I kind of expected, being the Notification a repeating one), how then can I update its content?

I can provide the code that generates the random prime number, but I've tested it and it works, so I don't think that's necessary (please let me know if otherwise).

Here's how I create the Notification Request (this is from AppDelegate):

//  AppDelegate.swift

import UIKit
import MessageUI
import UserNotifications


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {


    // MARK: Properties

    var window: UIWindow?


    // Life Cycle

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [
        UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {

        // MARK: Notification

        // Create notification object
        let center = UNUserNotificationCenter.current()

        // Set the delegate of the Notification to be AppDelegate file
        center.delegate = self

        // Create action allowing user to copy number from notification
        let copyAction = UNNotificationAction(identifier: "COPY_ACTION",
                                              title: "Copy",
                                              options: UNNotificationActionOptions(rawValue: 0))

        // Create category with a copy action
        let myCategory = UNNotificationCategory(identifier: "RANDOM",
                                                actions: [copyAction],
                                                intentIdentifiers: [],
                                                hiddenPreviewsBodyPlaceholder: "",
                                                options: .customDismissAction)

        center.setNotificationCategories([myCategory])

        let options: UNAuthorizationOptions = [.alert, .sound]

        center.requestAuthorization(options: options) { (granted, error) in
            if !granted {
                print("Something went wrong: \(String(describing: error))")
            }
        }

        center.getNotificationSettings { (settings) in
            if settings.authorizationStatus != .authorized {
                // Notifications not allowed
            }
        }

        // Access view controller containing function that generates random prime numbers
        let tab = window?.rootViewController as? UITabBarController
        let randomVC = tab?.viewControllers?[3] as? RandomViewController

        let content = UNMutableNotificationContent()
        content.title = "Your daily prime is:"

        // Set body to random prime, or 1 if returned value is nil
        content.body = "\(randomVC?.makeRandomNotification() ?? 1)"

        content.categoryIdentifier = "RANDOM"
        content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "choo.caf"))

        var date = DateComponents()
        date.hour = 9
        date.minute = 00
        let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)

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

        center.add(request)

        return true
    }


    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        switch response.actionIdentifier {
        case "COPY_ACTION":
            UIPasteboard.general.string = response.notification.request.content.body
        default:
            break
        }
        completionHandler()
    }


    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

        completionHandler([.alert, .sound])
    }

}

Note: I tested this by changing the trigger from a specific time to simply repeating every 60 seconds. Like so:

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)

(Un)related:

jscs
  • 63,694
  • 13
  • 151
  • 195

1 Answers1

2

The content is fixed once you've created the request. If it wasn't, there would be some method your UNUserNotificationCenterDelegate could use to do it. The code you've written won't be re-called just because the notification repeats.

You basically have three options:

  1. Use push notifications instead of local notifications, and put the responsibility for generating new daily content in a server somewhere.
  2. Schedule many individual notifications with their own prime number content (you can have up to 64 at one time) and rely on the user opening your app at some point over the course of two months so that you can schedule more. Note that this means you'll need code to figure out when you already have them scheduled, how many you should add, etc.
  3. Create a notification content extension* that will allow you to choose a number when the user sees and interacts with your notification.

I think I'd probably lean towards 3, but I haven't personally played with that API yet. It seems closest to what you're wanting to happen: it's essentially a callback -- albeit an elaborate one -- to allow you to "update" the content.


*Also see: 10-minute intro to them at Little Bites of Cocoa

jscs
  • 63,694
  • 13
  • 151
  • 195
  • 1
    Before I look into your suggestions, this behavior is expected? Repeating local notifications cannot update their content? –  Jan 13 '19 at 00:38
  • 1
    Yep; at least, I've never seen any API that provides for it. – jscs Jan 13 '19 at 00:39
  • 1
    Uhm Ok (_sits down, legs shaking_), so: 1. do I need my own server, or does Apple provide such a thing? If I need my own, I have no access to that. 2. That sounds good. So I would schedule them all at once and essentially would *not* be using repeating notifications? But how would a user opening the app affect that? It would reset the amount to 64? After all, if a user doesn't open the app for 2 months, they probably don't want notifications... :) 3. I played with that but the content then is only seen on 3D Touch, which I'd prefer to avoid (but it might the best solution). –  Jan 13 '19 at 00:45
  • 1
    1. You'd need your own server. 2. The user opening the app is required so that you can schedule _more_ notifications. If you schedule 64 and then they all get delivered without the app opening, you don't get a chance to schedule more. 3. Not just 3D touch, it's also triggered by the user dragging down on the banner -- see the Little Bites of Cocoa link I added at the bottom. – jscs Jan 13 '19 at 00:47
  • 1
    1. Ok so that's not an option unfortunately. 3. Ah, so that's an option too. 2. I found an answer posted somewhere before that basically showed a way like the #2 you provided, but I thought that the amount never resets, and because 64 isn't enough, I dismissed it. Looking for it now... Perhaps you can provide an example implementation? –  Jan 13 '19 at 00:56
  • 1
    I'm not sure what you mean by "never resets"...to be clear, you can have 64 scheduled _at any given time_. As notifications are delivered, they drop off the schedule and you will no longer be at the limit of 64, so you can schedule more. – jscs Jan 13 '19 at 00:59
  • 1
    Let's say I schedule notifications to show **fixed** numbers instead, starting from 1, up to 64. The user gets the first 20, then opens the app. Will the next notification show 21, or 1? –  Jan 13 '19 at 01:05
  • 1
    That depends entirely on how your code handles scheduling more notifications... You'll need to write the logic to produce your desired result. – jscs Jan 13 '19 at 01:06
  • 1
    #2 worked! Being that random numbers don't have or need an order in which they are presented, on launch, I: remove all pending notifications, get current date, get "next 9 AM", and use a for loop: for number in 0...64, add a day to "next 9 AM", and create a notification with it (of course I also add the "number" to the identifier so each is unique). Thanks again :) –  Jan 13 '19 at 03:11