I set up a dummy project to reproduce the issue I'm seeing. In my ContentView, I schedule some repeating notifications.
struct ContentView: View {
var body: some View {
VStack {
Button("Schedule notifications") {
let content = UNMutableNotificationContent()
content.title = "Title"
content.body = "body"
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
Button("Request Permission") {
let current = UNUserNotificationCenter.current()
current.requestAuthorization(
options: [.sound, .alert],
completionHandler: { completed, wrappedError in
guard let error = wrappedError else {
return
}
})
}
}
}
}
Then in my AppDelegate, I attempt to cancel those repeating notifications before the app terminates.
func applicationWillTerminate(_ application: UIApplication) {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
print("============== removing all notifications")
}
What I'm finding is that my scheduled notifications are still delivered, even though I can see my print statement in the Xcode console. But if I run the same test on an iPhone, my notification is not delivered, as expected.
Am I doing something wrong, or is this a bug? I'm using 13.4.1 on iPad, and 13.3.1 on iOS
I am also facing the same problem. Found out that applicationWillTerminate method has approximately five seconds to perform any tasks and return. And removeAllPendingNotificationRequests returns immediatly but removes scheduled notification asynchronously in secondary thread.
I think 5 seconds are more than necessary to clear notification.
Related
I need to clear all notifications from my notification centre.I tried to cancellAllLocalNotification and UIApplication.shared.applicationIconBadgeNumber = 0 but its not working.i am using swift 4 and xcode 10.1 and ios 12.Anyone can help me..
cancellAllLocalNotification is deprecated from iOS 10. from appledoc
Deprecated
Use the UNUserNotificationCenter class to schedule local notifications instead.
Use
UNUserNotificationCenter.current().removeAllDeliveredNotifications() // For removing all delivered notification
UNUserNotificationCenter.current().removeAllPendingNotificationRequests() // For removing all pending notifications which are not delivered yet but scheduled.
From Delivered Removal appledoc
Use this method to remove all of your app’s delivered notifications from Notification Center. The method executes asynchronously, returning immediately and removing the identifiers on a background thread. This method does not affect any notification requests that are scheduled, but have not yet been delivered.
From Pending Removal appledoc
This method executes asynchronously, removing all pending notification requests on a secondary thread.
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: #escaping () -> Void) {
print("payload",payload.dictionaryPayload)
{
let state = UIApplication.shared.applicationState
if state == .background || state == .inactive{
startTimer()
}
}
func startTimer() {
timer2 = Timer.scheduledTimer(timeInterval: 7, target: self, selector: (#selector(updateTimer2)), userInfo: nil, repeats: true)
}
#objc func updateTimer2() {
seconds2 += 7
isPushNotificationCallReceived = true
let content = UNMutableNotificationContent()
content.title = self.message
content.body = self.callerName
content.badge = 0
content.sound = UNNotificationSound(named: "phone_loud.wav")
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
let request = UNNotificationRequest(identifier: "SimplifiedIOSNotification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)]
print(seconds2)
if seconds2 == 28 {
isPushNotificationCallReceived = false
seconds2 = 0
timer2.invalidate()
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers:
["SimplifiedIOSNotification"])
UIApplication.shared.applicationIconBadgeNumber = 0
}
}
I am working on a timer application that should terminate if the user leaves the app. I want a notification to be sent to the user when they leave the application. This notification WARNS the user that the timer will terminate if they do not return to the app immediately
I have look at UNNotficationTrigger's but not of what i have tried repeats the notification if the user is to be warned more than once.
How to i detect when the user is outside the application, then send a notification warning them to return to the app??
Thank you in advance
Code:
#IBAction func start(_ sender: Any) {
startTimer()
let content = UNMutableNotificationContent()
content.title = "WARNING: Return to 4ocus"
content.subtitle = ""
content.body = "Go back to 4ocus or risk losing 4ocus points"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: "timerDone", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
#IBAction func start(_ sender: Any) {
startTimer()
let content = UNMutableNotificationContent()
content.title = "WARNING: Return to 4ocus"
content.subtitle = ""
content.body = "Go back to 4ocus or risk losing 4ocus points"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: "timerDone", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
func applicationWillTerminate(_ application: UIApplication) {
let content = UNMutableNotificationContent()
//adding title, subtitle, body and badge
content.title = "Hey if you will not come back"
content.subtitle = "your timer will be killed"
content.body = ""
content.badge = 1
//getting the notification trigger
//it will be called after 5 seconds
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
//getting the notification request
let request = UNNotificationRequest(identifier: "SimplifiedIOSNotification", content: content, trigger: trigger)
//adding the notification to notification center
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
and also put the same code in willforeground method of appdelegate to get know if the user is about to leave application
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
put this method in your appdelegate and if there is alredy func applicationWillTerminate(_ application: UIApplication) is defined then put only code :)
I have a Firebase listener that launches a notification when it fires like so:
chat.ref.child("lastMessage").observe(.value, with: { snapshot in
let lastMessage = snapshot.value as! String
chat.lastMessage = lastMessage
self.tableView.reloadData()
chat.lastMessageText = "New Message!"
let content = UNMutableNotificationContent()
content.title = "You have a new Message!"
content.subtitle = chat.title
content.body = chat.lastMessage
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger (timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: "timerDone", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
Also have:
override func viewDidLoad() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge], completionHandler: {didAllow, error in })
}
The problem I am having is, although the HomeScreen notifications are working in the simulator, they are not showing on the device. All permissions are granted on the device. Reading some questions here I found some answers that suggested I add the following in the app delegate:
application.beginBackgroundTask(withName: "showNotification", expirationHandler: nil)
but this actually disabled notifications on the simulator too. The notifications did work on the device before but stopped very suddenly. Any thoughts?
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.
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])
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])
}
}
I'm making an app that used circular regions for geofences. When the phone is active or the app is open, the geofence notifications are working fine in both simulator and device (iPhone 6 running 10.3.1).
In the simulator it works fine; When the user enters a region, it wakes up, makes a sound and shows an alert on the lock screen.
On the phone, the "didEnterRegion" delegate calls are made when entering the region (I log some messages) but the phone is not making an alert and waking up. When I push the home button once, I can see the alert on the lock screen, but I want it to wake up and show the alert instantly - like when I get a message. It works in the simulator, so I wonder what could be wrong? It has worked for me a few times, where the alert was shown on both the phone and my watch, but 95% of the time it's not working - the notifications are generated but only visible if I manually wake up the phone.
How to fix this?
Here's the code I use for creating the local notification:
// https://blog.codecentric.de/en/2016/11/setup-ios-10-local-notification/
let location = CLLocation(latitude: item.coordinate.latitude, longitude: item.coordinate.longitude)
GeoTools.decodePosition(location: location) {
(address, city) in
let content = UNMutableNotificationContent()
content.title = "Camera nearby!"
content.subtitle = item.id
content.body = "\(address), \(city)"
content.categoryIdentifier = Constants.notificationCategoryId
content.sound = UNNotificationSound.default()
content.threadIdentifier = item.id
// FIXME make action for clicking notification
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.001, repeats: false) // FIXME HACK
let request = UNNotificationRequest(identifier: "camNotification", content: content, trigger: trigger)
let unc = UNUserNotificationCenter.current()
unc.removeAllPendingNotificationRequests()
unc.add(request, withCompletionHandler: { (error) in
if let error = error {
print(error)
}
else {
print("completed")
}
})
}
Here is some code that I just verified wakes the device when notification is presented:
let message = "CLRegion event"
// Show an alert if application is active:
if UIApplication.shared.applicationState == .active {
if let viewController = UIApplication.shared.keyWindow?.rootViewController {
showSimpleAlertWithTitle(nil, message: message, viewController: viewController)
}
}
else {
// Otherwise app is in background, present a local notification:
let content = UNMutableNotificationContent()
content.body = message
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "message"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1.0, repeats: false)
let request = UNNotificationRequest(identifier: "com.foobar", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
Really the only diff is that I don't call removeAllPendingNotifications() so if you must remove notifications I wonder if removePendingNotificationRequests(withIdentifiers identifiers: [String]) might be more precise?