Local notification add/delete for a single item in Swift - swift

Hello I would like to create a local notification manager for my application, my entity is created with a title, a button if the item is finished or not, a date picker and also a boolean if a date is created otherwise nil, to create a notification all is well.
But if for example today it is June 1, 2022, I create an item for June 5, 2022, no worries so far but my come if in the meantime I finish my item, the notification is still active and I don’t understand how the identify UUID in string works…
Here is my code below, but when I call the cancelNotification function in another view I don't understand what to put exactly, item.id, uuid, identifier?....
I'm new to programming, thanks
func pushNotification(input:Date,text:String){
let calendar = Calendar.current
let hour = calendar.component(.hour, from: input)
let minute = calendar.component(.minute, from: input)
let second = calendar.component(.second, from: input)
let day = calendar.component(.day, from: input)
let month = calendar.component(.month, from: input)
let content = UNMutableNotificationContent()
content.title = "Organized"
content.subtitle = text
content.sound = .default
content.badge = 1
var dateC=DateComponents()
dateC.hour=hour
dateC.minute=minute
dateC.second=second
dateC.month=month
dateC.day=day
let trigger = UNCalendarNotificationTrigger(dateMatching: dateC, repeats: false)
let requst = UNNotificationRequest(
identifier: UUID().uuidString, content: content,trigger: trigger
)
UNUserNotificationCenter.current().add(requst)
}
func cancelNotification(ids: [String]) {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids)
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids)
}

You need to give a static string identifier for the cancel to work; change the lines:
let requst = UNNotificationRequest(
identifier: "NotificationIdentifier", content: content,trigger: trigger
)
And when you want to cancel all notifications, you can do this:
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
If you have multiple notifications and want to cancel a specific notification, you can use this:
UNUserNotificationCenter.current().getPendingNotificationRequests { (notificationRequests) in
var identifiers: [String] = []
for notification:UNNotificationRequest in notificationRequests {
if notification.identifier == "NotificationIdentifier" {
identifiers.append(notification.identifier)
}
}
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
}
or simply:
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: "NotificationIdentifier")

Related

(Swift) Question regarding Notification trigger

How can I change up my code to allow my notification to trigger every 12 hours since the last API call. In the code, you can see where I commented out the variable containing the last API call (lastRefreshDate)
// let lastRefreshDate = userDefaults.object(forKey: Constants.Defaults.LATEST_REQUEST_TIME) as? Date
var dateComponents = DateComponents(calendar: Calendar.current, hour: 12, minute: 0)
if let date = UserDefaults.getDate() {
dateComponents = Calendar.current.dateComponents([.hour, .minute], from: date)
}
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: "reminder", content: content, trigger: trigger)
center.add(request) { (error) in
if error != nil {
print(error!)
}
}

Is it possible to have two DateCompontents for one Local Notification?

I am trying to set two different times for Local Notifications - the default one being 10 am every day (if the user does not touch the date picker to pick their own notification time AKA it is nil), and one based on the user's input. The user input one does work and sends notifications every day, but if no time is chosen the default time does not work.
This is my code, can someone tell me what I am doing wrong? I basically want to check that if the user input is NIL it should revert to the default time set.
In settings:
IBOutlet var userDatePicker: UIDatePicker!
IBAction func pickerValueChanged(_ sender: UIDatePicker) {
var selectedTime = Date()
selectedTime = sender.date
// convert to data type DateComponents
let convertedSelectedTime = calendar.dateComponents([.hour, .minute,], from: selectedTime)
let delegate = UIApplication.shared.delegate as? AppDelegate
delegate?.sendNotification(with: convertedSelectedTime)
UserDefaults.standard.set(selectedTime, forKey: "SavedTime")
}
In AppDelegate:
func sendNotification(with userSelectedTime: DateComponents?) {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "Testing"
content.body = "testing"
var defaultTime = DateComponents()
defaultTime.hour = 10
defaultTime.minute = 00`// trying to set default time to 10 am if user never picks a time`
let components = userSelectedTime ?? defaultTime//option between the two
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
Try this. All of its fields are optional. So you can use those you need.
DateComponents(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:weekday:weekdayOrdinal:quarter:weekOfMonth:weekOfYear:yearForWeekOfYear:)
let triggerDaily = DateComponents(calendar: Calendar.current,
timeZone: Calendar.current.timeZone,
hour: 10, // Use 22 if PM
minute: 00,
second: 00,
nanosecond: 00)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDaily, repeats: false)
More info
DateComponents - Apple
Update
In pickerValueChanged, you are schedulling notification every time DatePicker value is changed. That is bad. There you should only store date.
func pickerValueChanged(_ sender: UIDatePicker) {
UserDefaults.standard.set(sender.date, forKey: "SavedTime")
}
sendNotification should only send notification.
func sendNotification(with dateComponents: DateComponents) {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "Testing"
content.body = "testing"
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
UIApplication has alife method that is called when an App goes to background. You can schedule notifications there.
func applicationDidEnterBackground(_ application: UIApplication) {
let date = UserDefaults.standard.value(forKey: "SavedTime") as? Date
if let date = date {
let convertedSelectedTime = Calendar.current.dateComponents([.hour, .minute,], from: date)
sendNotification(with: convertedSelectedTime)
} else {
let dateComponent = DateComponents(calendar: Calendar.current,
timeZone: Calendar.current.timeZone,
hour: 10, // Use 22 if PM
minute: 00,
second: 00,
nanosecond: 00)
sendNotification(with: dateComponent)
}
}
Use SavedTime if it exists in UserDefaaults. Else use 10:00.
If you use SceneDelegate, use sceneDidEnterBackground instead of applicationDidEnterBackground
func sceneDidEnterBackground(_ scene: UIScene) {
}
They both are life-cycle methods.
More info.
Managing Your App's Life Cycle
UIApplication

How to make local notification repeat every day on the first of the month - swift

I'm wondering how I can make a local notification in swift repeat on the first of every month. So on the first of January, reminder. On the first of February, same reminder, and so on. Preferably using date components.
First we need to get default "UNUserNotificationCenter".
Create "UNNotificationContent".
Create the "UNCalendarNotificationTrigger".
Create "UNNotificationRequest".
Add "UNNotificationRequest" to "UNUserNotificationCenter".
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.month = 1;
dateComp.day = 1;
dateComp.hour = 00;
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))")
}
}
Use can refer this link more info.

Local Notification every X day - Swift

Before I begin please don't burn me as I know this has been asked hundreds of times on here with no reliable answer but I believe there's a solution using background refresh. https://medisafe.com/ app seems to have solved it!
The goal :
To trigger a local notification at a specified time every x days
My solution
step 1: get timer interval from start date and odd occurrence (this case 2) days from (edited)
step 2: set interval timer on this difference with a repeat
step 3: activate background refresh ( if the app is even terminated it will load the app in the background and give me a small window to perform some tasks)
step 4. set background refresh to trigger once a day
step 5: perform get items api which will refresh all timers and notifications
step 6 sit back and smile with amazement at my solution
but this fails.
so a timer interval
let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 172800,repeats: true)
but this will just reset the timer every day when the background fetch is performed and it will trigger 2 days from NOW and not from the start date.
So there must be a way of comparing dates hours and minutes (start date, x date, and current date to work out the timer interval value.
currently im using calendar components. to trigger everyday im doing the following
var triggerType : DateComponents? {
var triggerT : DateComponents?
var cal = Calendar(identifier: .gregorian)
cal.firstWeekday = 2
if let notificationModel = self.notificationModel {
switch notificationModel.reminderType {
case .daily?, .weekly?:
if let date = notificationModel.date {
triggerT = cal.dateComponents([.weekday, .hour, .minute], from:date)
if let weekday = notificationModel.weekday {
triggerT?.weekday = weekday
}
}
case .alternateDays?:
if let date = notificationModel.date {
triggerT = cal.dateComponents([ .hour, .minute], from:date)
// THIS IS WHERE I NEED HELP
}
case .monthly?:
if let date = notificationModel.date {
triggerT = cal.dateComponents([.day,.hour,.minute], from: date)
}
case .yearly?:
triggerT = Calendar.current.dateComponents([.month,.day,.hour,.minute], from: (notificationModel.date)!)
case .oneOff?:
triggerT = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute], from: (notificationModel.date)!)
case .none:
DispatchQueue.main.async {
if let category = self.notificationModel?.category, let title = self.notificationModel?.title {
Toast.down("An error was discovered in \(category). Please change the occurance value for the following \(title)")
}
}
}
} else {
print("NOTIFICATION MODEL IS CORRUPT")
}
return triggerT
}
func add(notification: NotificationModel){
let content = UNMutableNotificationContent()
if let title = notification.title,
let body = notification.body,
let identifier = notification.identifier {
content.title = title
content.body = body
content.sound = UNNotificationSound.default()
content.categoryIdentifier = (notification.category?.rawValue)!
content.setValue("YES", forKeyPath: "shouldAlwaysAlertWhileAppIsForeground")
var trigger : UNCalendarNotificationTrigger?
if let triggerType = self.triggerType {
if let occurance = notification.occurance {
if occurance > 0 {
}
}
trigger = UNCalendarNotificationTrigger(dateMatching: triggerType, repeats: true)
} else {
return
}
let interval = Date().timeIntervalSince1970
let identifierString = "2\(interval)"
var request : UNNotificationRequest!
if notification.reminderType == .alternateDays {
print("ADDING TIMER NOTIFICATION")
print("REMINDER TIME = \(notification.date)")
// 172800 = two days
let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 172800,
repeats: true)
request = UNNotificationRequest(identifier: identifierString,
content: content, trigger: newTrigger)
} else {
request = UNNotificationRequest(identifier: identifierString,
content: content, trigger: trigger)
}
center.add(request, withCompletionHandler: { (error) in
if let error = error {
// Something went wrong
print(error.localizedDescription)
} else
{
print("ADDING NOTIDCIATION \(content.title)")
}
})
//SNOOZE OR DELETE NOTIFICATIONS
let snoozeAction = UNNotificationAction(identifier: "Snooze", title: "Snooze", options: [])
let deleteAction = UNNotificationAction(identifier: "UYLDeleteAction",title: "Delete", options: [.destructive])
//Create a category with the actions: This requires another unique identifier (you probably want to define these magic strings in an enum):
let category = UNNotificationCategory(identifier: notification.category!.rawValue,
actions: [snoozeAction,deleteAction],
intentIdentifiers: [], options: [])
//Register the category with the notification center. It is recommended to do this early in the app lifecycle.
center.setNotificationCategories([category])
//To include this action in our notifications we need to set the category in the notification content:
} else {
print("Failed to add notification")
}
}
however, I want every other day and dont want to use the 64 notification limit.
thanks for your time
Thomas
Lets say you want to trigger notification 2, 4 and 6 days from now, here is how you can do it:
For my example I added extension to Date
extension Date {
func adding(days: Int) -> Date? {
var dateComponents = DateComponents()
dateComponents.day = days
return NSCalendar.current.date(byAdding: dateComponents, to: self)
}
}
Then you could just create new notifications for dates specified, in this example 2, 4, 6 days from now
let date = Date()
for i in [2, 4, 6] {
if let date = date.adding(days: i) {
scheduleNotification(withDate: date)
}
}
func scheduleNotification(withDate date: Date) {
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Title"
notificationContent.subtitle = "Subtitle"
notificationContent.body = "Body"
let identifier = "Make up identifiers here"
let dateComponents = Calendar.autoupdatingCurrent.dateComponents([.day, .month, .year, .hour, .minute, .second], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let notificationReques = UNNotificationRequest(identifier: identifier, content: notificationContent, trigger: trigger)
UNUserNotificationCenter.current().add(notificationReques) { error in
if let e = error {
print("Error \(e.localizedDescription)")
}
}
}
This should schedule 3 notifications - 2, 4, 6 days from now...
So thanks for the directions on here this is the final solution i came up with. Ensure you turn on background modes in app capabilities so the current week is updated. i did mine to every day.
Then the code with comments.
//: Playground - noun: a place where people can play
import UIKit
import UserNotifications
Lets create some helper clases to make it easier to work with dates
// HELPERS
extension Date {
public var weekday: Int {
return Calendar.current.component(.weekday, from: self)
}
public var hour: Int {
get {
return Calendar.current.component(.hour, from: self)
}
set {
let allowedRange = Calendar.current.range(of: .hour, in: .day, for: self)!
guard allowedRange.contains(newValue) else { return }
let currentHour = Calendar.current.component(.hour, from: self)
let hoursToAdd = newValue - currentHour
if let date = Calendar.current.date(byAdding: .hour, value: hoursToAdd, to: self) {
self = date
}
}
}
public var minute: Int {
get {
return Calendar.current.component(.minute, from: self)
}
set {
let allowedRange = Calendar.current.range(of: .minute, in: .hour, for: self)!
guard allowedRange.contains(newValue) else { return }
let currentMinutes = Calendar.current.component(.minute, from: self)
let minutesToAdd = newValue - currentMinutes
if let date = Calendar.current.date(byAdding: .minute, value: minutesToAdd, to: self) {
self = date
}
}
}
}
Then we create our custom notification struct
struct CustomNotification {
static func everyOtherDay(wtihStartDate startDate: Date) -> [Int]? {
//
let currentDate = Date()
// get initial week day from start date to compare dates
let weekDay = startDate.weekday
// Then we need to get week of years for both dates
let cal = Calendar.current
guard let weekA = cal.dateComponents([.weekOfYear], from: startDate).weekOfYear else { return nil}
guard let weekB = cal.dateComponents([.weekOfYear], from: currentDate).weekOfYear else {return nil}
// create two arrays for week days
let weekOne = [1,3,5,7]
let weekTwo = [2,4,6]
// then we create a module to check if we are in week one or week two
let currentWeek = (weekA - weekB) % 2
if currentWeek == 0 {
//week 1
return weekOne.contains(weekDay) ? weekOne : weekTwo
} else {
// week 2
return weekOne.contains(weekDay) ? weekTwo : weekOne
}
}
}
finally in our class where we create the notification. I personally use a notification manager. but to shwo you quickly
class AClass : NSObject {
func setupNotifications() {
let startDate = Date()
let weekDays = CustomNotification.everyOtherDay(wtihStartDate: startDate)
let cal = Calendar.current
let center = UNUserNotificationCenter.current()
if let weekDays = weekDays {
for day in weekDays {
let identifier = "Some Random ID"
let content = UNMutableNotificationContent()
content.title = "title"
content.body = "body"
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "SOME CATEGORY"
content.setValue("YES", forKeyPath: "shouldAlwaysAlertWhileAppIsForeground")
var components = cal.dateComponents([.hour, .minute], from:startDate)
components.weekday = day
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
if let error = error {
// Something went wrong
print("ERROR ADDING NOTIFICATION TO CENTER \(error.localizedDescription)")
} else
{
print("ADDING NOTIFCIATION \(content.categoryIdentifier)")
}
})
}
}
}
}
Then we need to setup background fetch in our app and app delegate
// OVER IN APP DELEGATE
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// setup background refresh ensuring you turn it on in app capabilities
// trigger back ground refrsh once a day
UIApplication.shared.setMinimumBackgroundFetchInterval(86400)
return true
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// FETCH DATA and REFRESH NOTIFICATIONS
// We need to do this to ensure the current week value is updated to either 1 or 0
// You will need to delete all notifications with same same category first else your going to be getting both weeks notifications
let aClass = AClass()
aClass.setupNotifications()
}
Hope this helps somebody :D Thomas

DateComponents and Notifications Not Showing

I have made a to do list app. In the app, the user can select 1 of 4 buttons to set a notification. Immediate, morning, afternoon and evening. Currently, the evening and immediate work but the morning and afternoon are not working and I am unsure why.
Here is my code for the evening:
#IBAction func eveningTapped(_ sender: Any) {
eveningEnabled = true
morningEnabled = false
lockscreenEnabled = false
afternoonEnabled = false
}
if eveningEnabled == true {
var dateComponents = DateComponents()
dateComponents.hour = 18
dateComponents.minute = 00
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let content = UNMutableNotificationContent()
content.title = taskTextField.text!
content.body = DescTextField.text!
content.sound = UNNotificationSound.default()
content.badge = 1
let identifier = "UYLLocalNotification"
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
if error != nil {
// Something went wrong - another alert
}
})
}
This works completely fine but the morning doesn't work, here is the code:
#IBAction func morningTapped(_ sender: Any) {
morningEnabled = true
lockscreenEnabled = false
afternoonEnabled = false
eveningEnabled = false
}
if morningEnabled == true {
var dateComponents = DateComponents()
dateComponents.hour = 07
dateComponents.minute = 00
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let content = UNMutableNotificationContent()
content.title = taskTextField.text!
content.body = DescTextField.text!
content.sound = UNNotificationSound.default()
content.badge = 1
let identifier = "UYLLocalNotification"
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
if error != nil {
// Something went wrong - another alert
}
})
}
Since your code is absolutely identical (except the DateComponents) in both actions, there should be no difference when running.
But as you have a difference ('morning' is not working) the reason MUST be somewhere else (and not in the code, that you posted here). Maybe your tapAction for morning is not correctly connected in the InterfaceBuilder?
If you would post more code, we could probably better help finding the bug.
First you should check, wether your tapAction is being performed, when you tap. You can do this by adding a log command, like this:
print("Morning was tapped")
and put this code into your tapAction. Then you should get this log on your log console, after having tapped the corresponding button.