Schedule local notification every n days (timezone safe)
You can set them up upfront using UNCalendarNotificationTrigger
for an n
number of times and using an adjusted calendar for the current timeZone
import SwiftUI
class NotificationManager: NSObject, UNUserNotificationCenterDelegate{
static let shared: NotificationManager = NotificationManager()
let notificationCenter = UNUserNotificationCenter.current()
private override init(){
super.init()
requestNotification()
notificationCenter.delegate = self
getnotifications()
}
func requestNotification() {
print(#function)
notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if let error = error {
// Handle the error here.
print(error)
}
// Enable or disable features based on the authorization.
}
}
/// Uses [.day, .hour, .minute, .second] in current timeZone
func scheduleCalendarNotification(title: String, body: String, date: Date, repeats: Bool = false, identifier: String) {
print(#function)
let content = UNMutableNotificationContent()
content.title = title
content.body = body
let calendar = NSCalendar.current
let components = calendar.dateComponents([.day, .hour, .minute, .second], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: repeats)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
notificationCenter.add(request) { (error) in
if error != nil {
print(error!)
}
}
}
///Sets up multiple calendar notification based on a date
func recurringNotification(title: String, body: String, date: Date, identifier: String, everyXDays: Int, count: Int){
print(#function)
for n in 0..<count{
print(n)
let newDate = date.addingTimeInterval(TimeInterval(60*60*24*everyXDays*n))
//Idenfier must be unique so I added the n
scheduleCalendarNotification(title: title, body: body, date: newDate, identifier: identifier + n.description)
print(newDate)
}
}
///Prints to console schduled notifications
func getnotifications(){
notificationCenter.getPendingNotificationRequests { request in
for req in request{
if req.trigger is UNCalendarNotificationTrigger{
print((req.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
}
}
}
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.banner)
}
}
class ZuluNotTriggerViewModel:NSObject, ObservableObject, UNUserNotificationCenterDelegate{
@Published var currentTime: Date = Date()
let notificationMgr = NotificationManager.shared
///Sets up multiple calendar notification based on a date
func recurringNotification(title: String, body: String, date: Date, identifier: String, everyXDays: Int, count: Int){
print(#function)
notificationMgr.recurringNotification(title: title, body: body, date: date, identifier: identifier, everyXDays: everyXDays, count: count)
//just for show now so you can see the current date in ui
self.currentTime = Date()
}
///Prints to console schduled notifications
func getnotifications(){
notificationMgr.getnotifications()
}
}
struct ZuluNotTriggerView: View {
@StateObject var vm: ZuluNotTriggerViewModel = ZuluNotTriggerViewModel()
var body: some View {
VStack{
Button(vm.currentTime.description, action: {
vm.currentTime = Date()
})
Button("schedule-notification", action: {
let twoMinOffset = 120
//first one will be in 120 seconds
//gives time to change settings in simulator
//initial day, hour, minute, second
let initialDate = Date().addingTimeInterval(TimeInterval(twoMinOffset))
//relevant components will be day, hour minutes, seconds
vm.recurringNotification(title: "test", body: "repeat body", date: initialDate, identifier: "test", everyXDays: 2, count: 10)
})
Button("see notification", action: {
vm.getnotifications()
})
}
}
}
struct ZuluNotTriggerView_Previews: PreviewProvider {
static var previews: some View {
ZuluNotTriggerView()
}
}
Local notification trigger only last ten days based an expiry dates
You should add next day with current day and changed time zone as system zone identifier.
Try let me know :
func deviceExpiryReminder(){
let dateString = "2019-08-30 00:00:00"
let formatter : DateFormatter = DateFormatter()
formatter.timeZone = NSTimeZone.local
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: dateString)
if date != nil && date! > Date() {
let message = "Device Expire Reminder"
let title1 = "Your device is expire in {2} days"
let title2 = "Your device is due to expire"
let identifier = "DeviceExpire"
//trigger for every day from configured days before
let numberofDays = self.daysBetweenDates(startDate: Date(), endDate: date ?? Date())
for i in (0...numberofDays) {
let preTitleMsg = title1.replacingOccurrences(of: "{2}", with: String(numberofDays - i))
var title = preTitleMsg
if numberofDays == i {
title = title2 + " today"
} else if numberofDays - 1 == i{
title = title2 + " tomorrow"
}
let userInfo = ["reminderType": "Device Expire",
"title": title,
"message": message,
"identifier": identifier]
var calendar = Calendar.current
calendar.timeZone = TimeZone(identifier: self.getCurrentTimeZone())!
let currentDay = Date()
let nextDate = Calendar.current.date(byAdding: .day, value: i, to: currentDay)
if nextDate! >= Date() {
createReminder(title, message, nextDate, false, NSCalendar.Unit.day, nil, userInfo, identifier + String(i))
}
}
}
}
func getCurrentTimeZone() -> String{
return String (TimeZone.current.identifier)
}
func createReminder(_ title: String, _ message: String, _ triggerDate: Date?, _ canRepeat: Bool, _ repeatInterval: NSCalendar.Unit, _ triggerDateComponents: DateComponents?, _ userInfo: [AnyHashable : Any]?, _ identifier: String) {
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
content.title = title
content.body = message
content.sound = UNNotificationSound.default
let center = UNUserNotificationCenter.current()
// *** create calendar object ***
var calendar = Calendar.current
calendar.timeZone = TimeZone(identifier: self.getCurrentTimeZone())!
let components = calendar.dateComponents([.month, .hour, .year, .day], from: triggerDate!)
print("trigger dates one by one : \(triggerDate!)")
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: canRepeat)
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
center.add(request) { (error) in
if error != nil {
print("Error")
}else {
print("Notification added")
}
}
}
}
func daysBetweenDates(startDate: Date, endDate: Date) -> Int {
let daysBetween = Calendar.current.dateComponents([.day], from: startDate, to: endDate)
print(daysBetween.day!)
return daysBetween.day!
}
Daily Local Notifications Are Not Working
Look at the comments within the code
import SwiftUI
//struct and class should start with an uppercase
struct NotificationView: View {
//Central location for Notification code including the delegate
// A call to the notificationManager just like the line of code below has to be included in
// application(_:willFinishLaunchingWithOptions:) or
// application(_:didFinishLaunchingWithOptions:)
//https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate
//https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-an-appdelegate-to-a-swiftui-app
let notificationManager: NotificationManager = NotificationManager.shared
var body: some View {
VStack {
VStack {
Button("Request Permission") {
//Call a func here don't define it
notificationManager.requestAuthorization()
}
.frame(width: 200, height: 60, alignment: .center)
.foregroundColor(.black)
.background(Color.blue)
.cornerRadius(10.0)
.padding()
Button("Add Notifications For Morning") {
//Unique date components
var dateComponents = DateComponents()
dateComponents.hour = 6
dateComponents.minute = 30
//Reusable method
self.notificationManager.scheduleTriggerNotification(title: "Morning Time", body: "Wake Up And Be Productive!", categoryIdentifier: "reminder", dateComponents: dateComponents, repeats: true)
}
.padding()
Button("Add Notifications For Middle Of The Day") {
var dateComponents = DateComponents()
dateComponents.hour = 12
dateComponents.minute = 30
//Reusable method
self.notificationManager.scheduleTriggerNotification(title: "Middle Of The Day", body: "Did you have your daily run?", categoryIdentifier: "reminder", dateComponents: dateComponents, repeats: true)
}
.padding()
Button("Add Notifications For Night") {
var dateComponents = DateComponents()
dateComponents.hour = 20
dateComponents.minute = 51
//Reusable method
self.notificationManager.scheduleTriggerNotification(title: "Night Time", body: "Time to sleep", categoryIdentifier: "reminder", dateComponents: dateComponents, repeats: true)
}
.foregroundColor(.blue)
.padding()
Button("Print Notifications") {
//Reusable method
self.notificationManager.printNotifications()
}
.foregroundColor(.blue)
.padding()
Button("Delete Notifications") {
//Reusable method
self.notificationManager.deleteNotifications()
}
.foregroundColor(.blue)
.padding()
}
}
}
}
//You need a central location for the notification code because
// it is needed in more than 1 spot. At launch in the AppDelegate
// and wherever you schedule your notifications
class NotificationManager: NSObject, UNUserNotificationCenterDelegate{
//Singleton is requierd because of delegate
static let shared: NotificationManager = NotificationManager()
let notificationCenter = UNUserNotificationCenter.current()
private override init(){
super.init()
//This assigns the delegate
notificationCenter.delegate = self
}
func requestAuthorization() {
print(#function)
notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Access Granted!")
} else {
print("Access Not Granted")
}
}
}
func deleteNotifications(){
print(#function)
notificationCenter.removeAllPendingNotificationRequests()
}
///This is just a reusable form of all the copy and paste you did in your buttons. If you have to copy and paste make it reusable.
func scheduleTriggerNotification(title: String, body: String, categoryIdentifier: String, dateComponents : DateComponents, repeats: Bool) {
print(#function)
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.categoryIdentifier = categoryIdentifier
content.sound = UNNotificationSound.default
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: repeats)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
notificationCenter.add(request)
}
///Prints to console schduled notifications
func printNotifications(){
print(#function)
notificationCenter.getPendingNotificationRequests { request in
for req in request{
if req.trigger is UNCalendarNotificationTrigger{
print((req.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
}
}
}
}
//MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.banner)
}
}
struct NotificationView_Previews: PreviewProvider {
static var previews: some View {
NotificationView()
}
}
Repeating a Local Notification after every 14 days(two weeks)?
You can only set the repeat interval to a calendar unit. In order to get the right time interval you may have to set several notifications though.
If you for instance wanted a notification every 20 minutes you would have to create 3 notifications 20 minutes apart with a repeat interval of NSHourCalendarUnit.
The problem in your case is that the next thing up from the week unit is a month but a month is not exactly 4 weeks.
To actually set a notification for every 14 days you will have to create 26 notifications with a repeat interval of NSYearCalendarUnit.
Set Local Notification for Specific Time
Here is how you do it.
Fully Tested on Swift 2.0
//Note I have taken date time from date picker
func scheduleLocalNotification() {
let localNotification = UILocalNotification()
localNotification.fireDate = fixNotificationDate(datePicker.date)
localNotification.alertBody = "Hey, you must go shopping, remember?"
localNotification.alertAction = "View List" // Change depending on your action
localNotification.category = "reminderCategory"
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
}
//This function will help you in generating fire date.
func fixNotificationDate(dateToFix: NSDate) -> NSDate {
let dateComponets: NSDateComponents = NSCalendar.currentCalendar().components([NSCalendarUnit.Day, NSCalendarUnit.Month, NSCalendarUnit.Year, NSCalendarUnit.Hour, NSCalendarUnit.Minute], fromDate: dateToFix)
dateComponets.second = 0
let fixedDate: NSDate! = NSCalendar.currentCalendar().dateFromComponents(dateComponets)
return fixedDate
}
Don't forget to request permission for push notification.
Link to my project i worked on
https://drive.google.com/open?id=0B2csGr9uKp1DR0ViZFZMMEZSdms
Related Topics
Swift: Triggering Tableviewcell to Lead to a Link in a Uiwebview in Another Viewcontroller
Audio Information of Current Track iOS Swift
The Array Value Should Be Sort Like (Alphabetic, Numbers and Special Characters)
How to Make Uiscrollview Zoom in Only One Direction When Using Auto Layout
Nsclassfromstring Returning Nil for Nested Class
Reversing a Range Results in Mismatching Types
Indexing into Array of Functions: Expression Resolves to an Unused L-Value
Swift/iOS Refreshing App Data When in Background
Creating a Rtsp Client for Live Audio and Video Broadcasting in Objective C
Allow Users to Send Messages to Multiple Users Simultaneously in a Messaging App
Why Does It Take Such a Long Time for UI to Be Updated from Background Thread
Uitest Color of a Label (Not UI Label)
Updating the Status Bar Style Between View Controllers
Swiftui - Scrollviewreader's Scrollto Does Not Scroll
Why Do I Get an Mkerrordomain Error When Doing a Local Search