Swift 4 & >iOS 10.0
I want to schedule a local notification at a certain date and at a given time (let's say 3PM). I want the notifications to always be fired at 3PM, whatever the timezone I am in (automatic rescheduling of notifications according to timezones).
Previously, you could tweak UILocalNotifications' time zone to achieve exactly this, like perfectly explained in this SO post. However, in >iOS 10.0, UILocalNotifications is deprecated.
Here is my code:
func scheduleNotification(title: String, message: String, atDate: Date){
let center = UNUserNotificationCenter.current()
// Create content
let content = UNMutableNotificationContent()
content.title = title
content.body = message
content.sound = UNNotificationSound.default()
// Create trigger
let calendar = Calendar(identifier: .gregorian)
let triggerDate = calendar.dateComponents([.year,.month,.day,.hour,.minute,.second,], from: atDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: false)
// Create identifier
let identifier = "\(UUID())"
// Create request & add to center
let request = UNNotificationRequest(identifier: identifier,
content: content,
trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
})
}
Question:
How do you make the notification triggers properly with changing timezones ?
So, I managed to make it work. The triggerDate has a timeZone variable which is automatically nil, exactly like UILocalNotification.
triggerDate.timeZone behaves exactly like UILocalNotification.timeZone (behaviour described in this post, the same as mentioned in the question).
One of the reason it did not seem to work on the simulator was because I was not restarting the simulator when changing the timezone. Restarting will make everything work as expected.
Nota bene: Maybe a restart is not mandatory but since it's not obvious how much time a running simulator will take to detect the new timezone, I think restarting it is the most efficient solution.
Related
I have multiple Core Data entities, one for each day of the week, and each time I add a new Item inside one of these I want to trigger a Local Notification for that specific Item. Since I can add the same Item in more than one entity, how can I set a unique Identifier for each one of them?
I was using the title of that item as an Identifier because they're all different but if I add the same title in two entities then it will display a unique notification of the last item added because it will replace the old one and what I want is to have different notifications with the same item in different entities.
This is my actual code of the Local Notification
let content = UNMutableNotificationContent()
content.title = self.items.itemsData[item].title
content.body = "Notification"
content.sound = UNNotificationSound.default
var dateComponents = DateComponents()
dateComponents.weekday = 2
dateComponents.hour = 8
dateComponents.minute = 00
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: self.items.itemsData[item].title, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
Instead of using self.items.itemsData[item].title which is the same every time a new UNNotificationRequest is created for a particular item use UUID().uuidString which creates a new unique identifier each time it is called. You could probably use this unique identifier in combination with the title if that is more accurate for your scenario.
let request = UNNotificationRequest(identifier: self.items.itemsData[item].title + UUID().uuidString, content: content, trigger: trigger)
I looked all over stack overflow and was only able to find reoccurring local notifications for a specific time. However, I am trying to find information on how to make a reoccurring local notification based off a UTC time. The reason behind when you set up a reoccurring notification, it will stay to that specific time.
For example, at the moment 00:00:00 UTC is 5pm eastern time but when day light savings hits in a few months, the new time will be 4pm. However, the reoccurring notification is still set to 5pm. So this notification is now one hour off because of day light savings.
I am trying to figure out how to accomplish this, so the local notification will move properly with day light savings. I am not sure if this is possible since the reoccurring is set to a specific time, but I would love to find more information on this.
By default current Time Zone is considered for delivering local notifications. To use UTC time while registering for local notification, you need to set Time Zone to UTC.
import UIKit
import UserNotifications
//get the notification center
let center = UNUserNotificationCenter.current()
//create the content for the notification
let content = UNMutableNotificationContent()
content.title = " Title"
content.subtitle = "SubTitle"
content.body = "jvsvsvasvbasbvfasfv"
content.sound = UNNotificationSound.default
var dateComp = DateComponents()
dateComp.timeZone = TimeZone(identifier: "UTC") // set time zone to UTC
dateComp.day = 1;
dateComp.hour = 08;
dateComp.minute = 00;
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComp, repeats: true)
//create request to display
let request = UNNotificationRequest(identifier: "ContentIdentifier", content: content, trigger: trigger)
//add request to notification center
center.add(request) { (error) in
if error != nil {
print("error \(String(describing: error))")
}
}
Above code sets notification every morning 8 am UTC time.
I have a simple To do style list app, where an added item can have an intent donated so that user can find and mark the item as "completed" without opening the app.
In the Note class I have this function to donate the intent, which works as expected
public func donateMarkNoteAsCompleteIntent() {
let intent = MarkNoteAsCompleteIntent()
intent.content = self.content
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/YYYY"
intent.addedDate = dateFormatter.string(from: self.addedDate)
intent.id = self.id
let interaction = INInteraction(intent: intent, response: nil)
interaction.groupIdentifier = self.id
interaction.donate(completion: nil)
}
My only issue is, when the user uses the shortcut and triggers the app to update the Note item, I want to remove the shortcut so that the user can't trigger it again.
In my intent handle function, I end up calling this function
public func removeMarkNoteAsCompleteIntent() {
INInteraction.deleteAll(completion: nil)
let index = CSSearchableIndex()
index.deleteAllSearchableItems(completionHandler: nil)
}
No matter what combination of things I do here I can't seem to remove the donated shortcut. As soon as a user accepts marking the task as complete, I want the shortcut to no longer be visible in searches from Spotlight, etc. Everything else in the intent handling code is working, its updating the Note item in my database perfectly.
Help would be greatly appreciated.
When I call my notification function, it will trigger at the original time. However, I never get another notification after that. I noticed this post was dealing with a similar issue. I changed my code to mimic the solution. I still didn't receive another notification.
Here is the code:
static func scheduleNotification(hour:Int, minutes:Int, completion: #escaping (Bool) -> ()) {
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Hello "
notificationContent.subtitle = "Now might be a good time for a check in"
var dateInfo = DateComponents()
dateInfo.hour = hour
dateInfo.minute = minutes
let trigger = UNCalendarNotificationTrigger(dateMatching: dateInfo, repeats: true)
print(trigger.nextTriggerDate()!)
let request = UNNotificationRequest(identifier: Notifications.notificationIdentifier, content: notificationContent, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: { error in
if error != nil {
print("\(error)")
completion(false)
} else {
completion(true)
}
})
}
The intention is to have it trigger daily at 4:00pm.
scheduleNotification(hour: 16, minutes:00, completion: {success in
if success {
print("Successfully scheduled notification")
} else {
print("Could not schedule notification")
}
})
I printed the result of the nextTriggerDate function to see if it would return the original trigger date or the date of the following day. In this case, the date is 4-18-2018.
The trigger that gets returned is
2018-04-18 23:00:00 +0000
I'm also not sure why the date returned adds 7 hours to my intended trigger time. The notification still fires at 4:00. Thoughts?
short answer
add
dateInfo.timeZone = TimeZone(abbreviation: "UTC")
and it will work like expected.
explanation
I tried myself with hour 16 and minutes 0.
I got 14:00 as result. The reason is because I'm living in Germany and Germany (GMT+2, summer time) has at the moment +2 hours distance to UTC.
The defaults timezone of my device is GMT+2. I add 16 hours and DateComponent handle this date as GMT+2 but converts that value to UTC.
So 16 (input) - 2 (GMT) = 14 UTC.
I'm always try to work with UTC dates... everything else causes me a headache.
first I'd like to say that I've been searching for the answer to my question quite a bit but the only things I've found so far are answers for older versions of Swift or answers that don't specifically answer my question.
Background info:
I'm trying to develop an app that can remind you in a set interval. Now this works, given that you only set 1 reminder. However if I set the interval to be 20 seconds, launch the app, set 2 notifications and close the app only the second notification shows in 20 seconds. The first notification is being overwritten by the second one.
Question: How can I make sure that all of my notifications, requested by the user, actually get sent and that no notification overrides the previous one?
Code for the notification:
let tijd = 20 //20 is just for the test, normally there is more code to it
// Notification
let content = UNMutableNotificationContent()
content.title = "Notification title"//title
content.body = "Notification body" //body
content.badge = 1
content.sound = UNNotificationSound.default()
// Timer
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(tijd), repeats: false)
let request = UNNotificationRequest(identifier: "timerDone", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
This code is stored in a UITableView cell.
Okay I figured it out!
https://stackoverflow.com/a/41892828/7385440
This answer lead to the same problem I had. I had to make the identifier different for every notification! So my code now is:
let request = UNNotificationRequest(identifier: bezigheid, content: content, trigger: trigger)
and bezigheid is something that is different in every single cell. Tested it and now I get 2 different notifications!