Unable to update local scheduled notification content
Asked Answered
G

2

5

In one of the WWDC sessions I got code snippet for updating existing notifications. I don't think it works. Trying to update notification content.

First I request pending notifications from UNUserNotificationCenter which always works. Then I am creating new request to update notification with existing unique identifier.

There's 1 new variable content: String.

// Got at least one pending notification.
let triggerCopy = request!.trigger as! UNTimeIntervalNotificationTrigger
let interval = triggerCopy.timeInterval
let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: interval, repeats: true)

// Update notificaion conent.
let notificationContent = UNMutableNotificationContent()
notificationContent.title = NSString.localizedUserNotificationString(forKey: "Existing Title", arguments: nil)
notificationContent.body = content
let updateRequest = UNNotificationRequest(identifier: request!.identifier, content: notificationContent, trigger: newTrigger)
UNUserNotificationCenter.current().add(updateRequest, withCompletionHandler: { (error) in
    if error != nil {
        print("🚫 Couldn't update notification \(error!.localizedDescription)")
    }
})

I am unable to catch error. The problem is that notification content body doesn't change.

Update.

I also tried to change trigger with different repeat interval. It doesn't work, notification is repeated with the same original interval it was created with.

Update 2.

Read Chris' answer, trying to go with first option.

let center = UNUserNotificationCenter.current()
center.getPendingNotificationRequests(completionHandler: { (requests) in
    for request in requests {
        if request.identifier == notificationIdentifier {
            // Got at least one pending notification,
            // update its content.
            let notificationContent = UNMutableNotificationContent()
            notificationContent.title = NSString.localizedUserNotificationString(forKey: "new title", arguments: nil)
            notificationContent.body = "new body"
            request.content = notificationContent // ⛔️ request.content is read only.
        }
    }
})

As you can see I can't modify original request.

Update 3.

Had go with second "delete first" option. Noticed that calling removePendingNotificationRequests and schedule after, still gives me old notification version. I had to add 1 second delay between calling removePendingNotificationRequests and center.add(request).

Marked Chris' answer as accepted but feel free to share better option.

Goshawk answered 11/7, 2017 at 11:56 Comment(7)
what is the type of content? Can you print it and make sure it's what you expect?Crosspatch
Type of content is String.Goshawk
FWIW it's a confusing name. don't try to differentiate between notificationContent.body and content by giving the content a different name. Just write it as notificationContent.body = body because overtime you may have the exact confusion I just had. the left side is a property of another, the right Side is a local variable—enough for others to know the differences but also know the similarities. But again can you print and see if the body/title are what you expect? Let me know the result. Otherwise your code so far looks fine. I've also pasted in my own Xcode to give it a try.Crosspatch
you must be doing something wrong ELSEWHERE in your code or again your strings are coming incorrect. I just verified with a very similar code. I made changes to everything expect the identifier and it updated the previous notification. To do such, you don't need to remove the pending notification. Adding the new NotificationRequest to the NotificationsCenter is all you need to do. I highly suggest you see this answer and download the sample project. It's a very very good sample projectCrosspatch
With your current solution change the last line from request.content = notificationContent to let updateRequest = UNNotificationRequest(identifier: notificationIdentifier, content: notificationContent, trigger: newTrigger) and UNUserNotificationCenter.current().add(updateRequest, withCompletionHandler: { (error) in print("successfully updated") if error != nil { print("🚫 Couldn't update notification \(error!.localizedDescription)") }. Basically grabCrosspatch
@Honey thanks for trying to help, I did check the answer and project, but I didn't find it's relevant to my problem. The question there is about creating and canceling new notification. Mine problem is about updating existing notification.Goshawk
Basically you have to fire a new request again—with same identifier. You can't jus get a pending one and change it's content. It doesn't work that way. Follow my answer. It should be clear to follow a similar path. It's really more easier than you think.Crosspatch
B
4

The problem is that you're not modifying the existing notification, and instead adding a new notification with a duplicate identifier.

Let's tackle the duplicate issue first, the reason this duplicate notification doesn't show up is because the identifier isn't unique. From the docs:

(if identifier is not unique, notifications are not delivered).

You have two options. You can 1) modify the existing Notification, or 2) remove it and add the new one.

For 1, you already have the request, instead of pulling the trigger and identifier out of it, just replace request.content with your updated notificationContent.

For 2, you would just need to add a line before your Add:

UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [request!.identifier])
Brenner answered 11/7, 2017 at 14:15 Comment(4)
You don't need to remove it. See here. Not sure what you mean by you don't have to pull the trigger/identifier out. Perhaps if you add some code it would be clearer. Though the quote your provided made me confused since having multiple notifications with the same identifier, would rewrite the previous one. See here. Thinking...Crosspatch
I'm saying that he can just take his existing request and update the content. Instead, he is doing a new trigger with the old trigger's details and a new notification using the old notification's identifier.Brenner
Hi @ChrisAllwein, your answer does make sense, however I can't really update the request because its properties is read only. I've updated my question with code you suggested in option 1.Goshawk
There is no need to remove pending notifications. I just verified that. But you're right about how he's confusing himself.Crosspatch
C
2

After I've requested to Allow Notifications:

I trigger a notification right from my viewDidLoad but then also trigger another one with the same identifier. At the end the the updatedBody/updatedTitle show up.

import UIKit
import UserNotifications

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let content = UNMutableNotificationContent()
        content.title = "Scheduled Task"
        content.body = "dumbBody"
        content.badge = 1
        content.sound = UNNotificationSound.default()
        content.categoryIdentifier = "alertCategory"

        UNUserNotificationCenter.current().delegate = self

        //Setting time for notification trigger
        let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 3.0, repeats: false)
        let request = UNNotificationRequest(identifier:"myIdentifier", content: content, trigger: trigger)
        UNUserNotificationCenter.current().add(request, withCompletionHandler: {_ in print(" was registered")})

        updateNotification()
    }

My update function

    func updateNotification(){

        let center = UNUserNotificationCenter.current()
        var request : UNNotificationRequest?

        center.getPendingNotificationRequests{ notifications in
            for notificationRequest in notifications{
                if notificationRequest.identifier == "myIdentifier"{
                    request = notificationRequest
                    center.removeAllPendingNotificationRequests() // Removing this line or keeping it makes NO difference
                }

            }

            let newTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5.0, repeats: false)

            // Update notificaion conent.
            let notificationContent = UNMutableNotificationContent()
            notificationContent.title = "UpdatedTitle"

            notificationContent.body = "updatedBody"
            let updateRequest = UNNotificationRequest(identifier: request!.identifier, content: notificationContent, trigger: newTrigger)
            UNUserNotificationCenter.current().add(updateRequest, withCompletionHandler: { (error) in
                print("successfully updated")
                if error != nil {
                    print("🚫 Couldn't update notification \(error!.localizedDescription)")
                }
            })
        }

    }

}

In the above snippet: Removing center.removeAllPendingNotificationRequests() would make no difference. Still I would receive the updatedNotification.

For handling incoming notifications

extension ViewController:UNUserNotificationCenterDelegate{      

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    print("original identifier was : \(response.notification.request.identifier)")
    print("original body was : \(response.notification.request.content.body)")
    print("Tapped in notification")

    switch response.actionIdentifier {
    default:
        print("some action was clicked")
    }
}

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

        print("Notification being triggered")
        completionHandler( [.alert,.sound,.badge])

    }
}
Crosspatch answered 11/7, 2017 at 15:14 Comment(11)
just for your information, I am dealing with repeated notification and of course removing and creating new one will solve this but I would prefer not to remove it, so repeating cycle doesn't brake.Goshawk
@borisy then please update that in your question and make it clearCrosspatch
I wouldn't focus on this because I don't think repeated true or false makes difference here. To be fair, my question does specifies this already.Goshawk
@borisy I'm really confused as to what you want: "so repeating cycle doesn't brake" What does that mean?! do you mean if your interval was set to 60 seconds and you changed it to 90seconds, the interval time still stays at 60?Crosspatch
Let's say we scheduled notification to repeat every week starting from Monday. Then on Friday notification's content need to be updated. After update process notification must fire on next Monday. However if I decide to delete notification and create new one instead of updating existing one, it will fire on next Friday.Goshawk
@borisy my God! That's far from what the question is asking. It's not mentioned in your question that your major problem is not knowing how to set the new trigger right. In the question your asking 2 things: 1. how to update an existing notification (which seemingly you know it's answer) 2. how to do that using a time interval based (which is irrelevant. Since your issue is calendar based). Or perhaps it's that you don't know the difference between calendar trigger and timeIntervalTrigger?! Otherwise your problem would have been solved I guess.Crosspatch
Let us continue this discussion in chat.Goshawk
What if app is killed, will you be able to call in didReceive method on any action click called ?Brimmer
@Brimmer which didRecieve method are you talking about?Crosspatch
@Honey I am talking about method which handle selecting action of notification. public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {}Brimmer
@Brimmer yes. It will launch the app. Only that your usernotificationcenter delegate needs to be set from didFinishLaunching, otherwise you won’t get the callback...Crosspatch

© 2022 - 2024 — McMap. All rights reserved.