How to display dynamic body text for local notification in Swift - ios

I am adding a local notification feature to my iOS app which will fire up a notification / alert on a specified time e.g. 7am daily. So I setup the alert using the code below:
let content = UNMutableNotificationContent()
content.title = "Planned transactions due today"
content.body = "You have [NUMBER] scheduled income and [NUMBER] planned expenses due for today."
content.categoryIdentifier = "alarm"
content.userInfo = ["customData": "fizzbuzz"]
content.sound = UNNotificationSound.default
let date = Date(hour: 7, minute: 0, second: 0) //custom initializer from Date extension
let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let request = UNNotificationRequest(identifier: "Reminder", content: content, trigger: trigger)
center.add(request) { (error) in
if let error = error {
print("Error scheduling notification: \(error.localizedDescription)")
}
}
It works perfectly. But what I want to achieve is to add more useful information within the local notification's body, like replacing the [NUMBER] to an actual number which is derrived from a query from core data.
I need help on how to pull this off. I've looked at implementations using UNNotificationServiceExtension and UNNotificationContentExtension. But I can't seem to make it work. I was unable to find any implementations of UNNotificationServiceExtension that applies to local notification – I could be wrong but I think it's only for push notifications.
I've also tried implementing UNUserNotificationCenterDelegate on the AppDelegate to intercept the notification as it is being triggered. func userNotificationCenter(willPresent:) method allows me to get the notification but there seems to be no way for me to change the .content property as it is get-only.
So basically, I just want to be able to change the body content of the notification before it is being displayed.

My solution isn't the best, but to achive this, i do the following at each app launch:
first cancel the next notifications,
then update it with a new content.
It works fine if the app is used at a regular basis.
there is some something called BGTaskScheduler. Apple said that it is a
A class for scheduling task requests that launch your app in the
background.
https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler
Maybe you should check for it

Related

iOS - Scheduling a repeating notification with different text

I have an app where the user needs to complete a task every day. If the user does not complete a task, he/she gets a reminder at 8am the next day with a phrase prompting to complete the task.
We would like to send a phrase every morning but we don't want it to be the same phrase every day.
This is what we have right now:
static func scheduleDailyUnwatchedNotification() {
let notificationMessages = ["Phrase one", "Phrase two", "Phrase 3", "Phrase 4", "Phrase 5"]
let totalMessages = notificationMessages.count
let randomIndex = Int.random(in: 0..<totalMessages)
let center = UNUserNotificationCenter.current()
center.removePendingNotificationRequests(withIdentifiers: ["dailyReminder"])
let content = UNMutableNotificationContent()
content.title = "Reminder"
content.body = notificationMessages[randomIndex]
content.sound = .default
var dateComponents = DateComponents()
dateComponents.hour = 8
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: "dailyReminder", content: content, trigger: trigger)
center.add(request)
}
The problem is that even though a random phrase is selected, the notification will always repeat with that same random phrase.
How can we have it repeat with a different phrase?
You’ll need to schedule each different phrased notification manually. However if you’re sending a phrase every day and you have say 50 of them you could schedule each to repeat every 50 days. Then whenever the user opens the app you can always swap around the days the notifications are sent - so the phrases ordering isn’t always the same. It’s not the most ideal but does allow recurring notifications with different titles.
Alternatively if you want to be able to change the notification titles without new app releases/have more control you can use push notifications. This way you can set up a backend to send messages but it does have more overhead from a server perspective.

Swift: Mute notifications for current day

in my app I send a daily notification to remind the user to visit the app.
This notification is locally delivered every day at 1pm.
func scheduleNotifications() -> Void {
for notification in notifications {
let content = UNMutableNotificationContent()
content.title = notification.title
let todaysDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd"
let currentDay = dateFormatter.string(from: todaysDate)
let currentDayInt = Int(currentDay) ?? 0
var datComp = DateComponents()
datComp.hour = 13
datComp.minute = 00
datComp.day = currentDayInt + 1
let trigger = UNCalendarNotificationTrigger(dateMatching: datComp, repeats: true)
let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
guard error == nil else { return }
print("Scheduling notification with id: \(notification.id) on Day \(datComp.day ?? 00) at \(datComp.hour ?? 00) - \(datComp.minute ?? 00)")
}
}
}
As you can see, I added the "current day + 1" lines because if the user opens the app before 1pm, there is no need to deliver the notification on this day.
So every time the user opens the app, I use UNUserNotificationCenter.current().removeAllPendingNotificationRequests() to remove and reschedule the notification for the next day (by recalling the function above).
My issue:
The notification should repeat every day, which it does, as long as the user opens the app.
But if the user does not open the app on one day, there will be no notification on the following days.
Is there a way to mute notifications for the current day so that I don't have to use this "current day + 1"-thing? Or does anyone have a better idea?
Thank you guys.
I think you misunderstood how repeating notifications and your datComp works here.
For example (let's use today's date: May 27)
datComp.hour = 13
datComp.minute = 00
datComp.day = currentDayInt + 1 // in our example it's 28
let trigger = UNCalendarNotificationTrigger(dateMatching: datComp, repeats: true)
means that your notification will get triggered every month on 28th, all parameters your trigger knows is hour, minute, day and by repeating it, will be triggered on every 28th at 13:00.
So the way your app works now is that you set up monthly notification starting from tomorrow, when you open the app you remove that monthly notification and reschedule it for day later. By opening every day it gives you impression that its daily notification but it's not, it's monthly. That's why if you don't open the app nothing shows up next the day, it will show up next month.
You can check my similar explanation here: (top answer there, maybe it is better worded and easier to understand)
Removing scheduled local notification
and my solution for similar problem i had here:
How to set up daily local notification for tasks but don't show it when user completes the task before

Swift 3 - user notification custom repeat like iOS Reminders app

I want to make an app just like iOS Reminders app. My problem is the custom repeat part. We can set custom repeats like "Every 2 Months on the third Monday"(the screenshot below) but I don't know how to implement this sort of repeats with User Notification.
What should I have do?
Although the question might be abroad to be fully answered, I would post an answer that should scratch the surface of how you could achieve it by using user notifications.
If you are aiming to let the displaying of the notification is based on a specific date/interval -as you mentioned "Every 2 Months on the third Monday"-, then you should work with UNCalendarNotificationTrigger:
The date and time at which to deliver a local notification.
Example:
import UserNotifications
// first, you declare the content of the notification:
let content = UNMutableNotificationContent()
content.title = "Notification Title"
content.subtitle = "Notification Subtitle"
content.body = "Notification Body"
// now, you should declare the UNCalendarNotificationTrigger instance,
// but before that, you'd need to describe what's the date matching for firing it:
// for instance, this means it should get fired every Monday, at 10:30:
var date = DateComponents()
date.weekday = 2
date.hour = 10
date.minute = 30
// declaring the trigger
let calendarTrigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)
// creating a request and add it to the notification center
let request = UNNotificationRequest(identifier: "notification-identifier", content: content, trigger: calendarTrigger)
UNUserNotificationCenter.current().add(request)

iOS 10 UserNotifications custom sound in background mode

Since UILocalNotification is deprecated in iOS 10 so I have updated my local notification flow using the UNUserNotification framework.
The app plays custom sound using AVPlayer when the app is in the foreground and it works fine. But in background mode when local notification is triggered, instead of custom sound, a default notification sound is being played.
However, things were working fine in iOS9, using "didReceiveLocalNotification" method app can play custom sound even in background mode.
Update 1:
I'm setting local notification as follows:
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationStringForKey("reminder!", arguments: nil)
content.body = NSString.localizedUserNotificationStringForKey("Reminder body.", arguments: nil)
if let audioUrl == nil {
content.sound = UNNotificationSound.defaultSound()
} else {
let alertSound = NSURL(fileURLWithPath: self.selectedAudioFilePath)
content.sound = UNNotificationSound(named: alertSound.lastPathComponent!)
}
content.userInfo = infoDic as [NSObject : AnyObject]
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)
let identifier = "Reminder-\(date)"
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
let center = UNUserNotificationCenter.currentNotificationCenter()
center.addNotificationRequest(request, withCompletionHandler: { (error) in
if error != nil {
print("local notification created successfully.")
}
})
I have already gone through many SO Questions but didn't get the solution.
Any help will be greatly appreciated!
In Swift 4.2 and Xcode 10.1
For local notifications to play sound
let content = UNMutableNotificationContent()
content.title = "Notification Tutorial"
content.subtitle = "from ioscreator.com"
content.body = " Notification triggered"
//Default sound
content.sound = UNNotificationSound.default
//Play custom sound
content.sound = UNNotificationSound(named:UNNotificationSoundName(rawValue: "123.mp3"))
"123.mp3" file needs to be in your Bundle.
Found out that alertSound doesn't have the correct file path hence the content.sound sets nothing. I was saving the recorded file in documents directory and the file path retrieve for real device is different as compared to the simulator.
Setting a sound file which is placed in project bundle did the trick
content.sound = UNNotificationSound(named: "out.caf")
In your push notification package you need to include the custom sound you want to play in the background. This sound needs to be included in your application too.
For example, here is how I create a custom notification package in PHP for iOS. Notice in my notification object I have a sound object.
array("to" => $sDeviceID,
"data" => array($sMessageType => $sMessage,
"customtitle" => $sMessageTitle,
"custombody" => $sMessage,
"customimage" => "$mIcon") ,
"notification" => array("title" => "sMessageTitle",
"text" => $sMessage,
"icon" => "$mIcon",
"sound" => "mySound.mp3"));
Here is how to create a notification using the UserNotifications framework with a custom sound.
If notification is your UNNotificationContent and the name of your custom media file that is in your bundle is a.mp4, just use the following code:
notification.sound = UNNotificationSound(named: "a.mp4")
So, you don't need to use didRecieveLocalNotification.
I understand that
let notificationContent = UNMutableNotificationContent()
...
notificationContent.sound = UNNotificationSound(named: "out.mp3")
is working fine, but:
1) the sound file should not be longer than 30 sec (which is ok, I think)
2) the sound file has to be part of the Bundle.mail files. If it is stored somewhere else the notification will not have access to it, when in background. It works fine in foreground. Apple does not provide permission to access other folders as far as I know.
3) there are restrictions on the sound format. Please refer to Apple guides
4) I'm not sure if sound files in Bundle.main can be changed
programmatically. For example: I wrote a routine which extracts 30 sec of a song stored in apple music and chosen by the user... everything works fine, if the info is stored in a different directory, but only in foreground... once locked or in background there is no access to it. I'm not able to change/overwrite a file in Bundle.main. If that would be possible the problem would be solved ... but I think that would be a security issue. I conclude that alert sounds and music are working fine as long as the above is met.
let notificationContent1 = UNMutableNotificationContent()
notificationContent1.title = "Good morning! Would you like to listen to a morning meditation to start the day fresh ? Or maybe a cool relaxing soundscape? Open the app and browse our collection which is updated daily."
notificationContent1.subtitle = ""
notificationContent1.body = ""
notificationContent1.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "AlarmSound.wav"))
var dateComponents1 = DateComponents()
dateComponents1.hour = 08
dateComponents1.minute = 00
let trigger1 = UNCalendarNotificationTrigger(dateMatching: dateComponents1, repeats: true)
let request1 = UNNotificationRequest(
identifier: UUID().uuidString,
content: notificationContent1,
trigger: trigger1
)
let notificationContent2 = UNMutableNotificationContent()
notificationContent2.title = "Good evening! Hope you had a great day. Would you like to meditate on a particular topic ? Or just some relaxing sleep sounds ? Open the app and pick what you want so you can go to sleep relaxed."
notificationContent2.subtitle = ""
notificationContent2.body = ""
notificationContent2.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "AlarmSound.wav"))
var dateComponents2 = DateComponents()
dateComponents2.hour = 20
dateComponents2.minute = 00
let trigger2 = UNCalendarNotificationTrigger(dateMatching: dateComponents2, repeats: true)
let request2 = UNNotificationRequest(
identifier: UUID().uuidString,
content: notificationContent2,
trigger: trigger2
)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(request1, withCompletionHandler: nil)
UNUserNotificationCenter.current().add(request2, withCompletionHandler: nil)

iOS 10 - Repeating notifications every "x" minutes

In iOS 10, how can I set local notifications to repeat in minutes starting from a particular date/time.
For example, trigger local notification every 15 minutes starting from 11 AM on 8th September? Assume that, below, dateTimeReminder.date has 09/08 11 AM.
let dateStart = self.dateTimeNotif.date
let notifTrigger = UNCalendarNotificationTrigger.init(dateMatching: NSCalendar.current.dateComponents([.day, .month, .year, .hour, .minute], from: dateStart), repeats: true)
let notificationRequest = UNNotificationRequest(identifier: "MYNOTIF", content: notifContent, trigger: notifTrigger)
UNUserNotificationCenter.current().add(notificationRequest, withCompletionHandler: nil)
With the above code, I have a possibility to schedule at a particular minute of every hour, at a particular hour of each day and so on. But how do I turn it into "every "x" minutes"? Any help is appreciated.
Similar question - How do I set an NSCalendarUnitMinute repeatInterval on iOS 10 UserNotifications?
Swift 3/4 and iOS 10/11:
According with this bug seems there is no way to use DateComponents() to repeat correctly a local notification.
Instead of this method you can change your trigger with TimeInterval (this method works if you interval is major than 60 seconds):
let thisTime:TimeInterval = 60.0 // 1 minute = 60 seconds
// Some examples:
// 5 minutes = 300.0
// 1 hour = 3600.0
// 12 hours = 43200.0
// 1 day = 86400.0
// 1 week = 604800.0
let trigger = UNTimeIntervalNotificationTrigger(
timeInterval: thisTime,
repeats: true)
As you are already aware, you can schedule maximum of 64 notifications per app. If you add more than that, the system will keep the soonest firing 64 notifications and will discard the other.
One way to make sure all notifications get scheduled is to schedule the first 64 notifications first, and then on regular time intervals (may be on every launch of the app or each time a notification fires) check for the number of notifications scheduled and if there are less than 64 notifications, lets say n notifications, then schedule the next (64 - n) notifications.
int n = [[[UIApplication sharedApplication] scheduledLocalNotifications] count];
int x = 64 - n;
// Schedule the next 'x' notifications
for rest add a NSTimer for X minutes to come and set the notification.
override func viewDidLoad() {
super.viewDidLoad()
//Swift 2.2 selector syntax
var timer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: #selector(MyClass.update), userInfo: nil, repeats: true)
//Swift <2.2 selector syntax
var timer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: "update", userInfo: nil, repeats: true)
}
// must be internal or public.
func setNotification() {
// Something cool
}
Make sure you remove old and not required notifications.
After searching around quite a bit, I've come to the conclusion that Swift 3, at this point, doesn't support this feature. For everyone looking for this functionality, I'd suggest using UILocalNotification for now (although deprecated in iOS 10), but later migrate to UNUserNotification once it supports this feature. Here are some additional questions and resources that have helped me to reach this conclusion. Also, please follow all the answers and comments in this thread to get more insight into which particular scenario it talks about.
It is usually a bad idea to use deprecated APIs. As a general practice, migrate to new APIs as soon as possible. The above solution is NOT recommended as a permanent solution.
Local Notification every 2 week
http://useyourloaf.com/blog/local-notifications-with-ios-10/
https://github.com/lionheart/openradar-mirror/issues/14941
This is how I set local notification based on time interval repeatedly and executed method customized snooze and delete
let content = UNMutableNotificationContent()
content.title = "Time Based Local Notification"
content.subtitle = "Its is a demo"
content.sound = .default
content.categoryIdentifier = "UYLReminderCategory"
//repition of time base local notification in foreground, background, killed
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)
let request = UNNotificationRequest(identifier: "IOS Demo", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
let snoozeAction = UNNotificationAction(identifier: "Snooze", title: "Snooze", options: [])
let deleteAction = UNNotificationAction(identifier: "UYLDeleteAction", title: "Delete", options: [.destructive])
let cat = UNNotificationCategory(identifier: "UYLReminderCategory", actions: [snoozeAction, deleteAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([cat])
I think this thread has what you're looking for.
Do something every x minutes in Swift
The '60.0' represents seconds between the code running. Simply change that to x minutes * 60
The link you shared and the new details you added you need to save the last hour and minute of time for which notification got scheduled. Then when the timer ticks to next minute, there change the hour and minute
var date = DateComponents()
date.hour = 8
date.minute = 30
let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)
let content = UNNotificationContent()
// edit your content
let notification = UNNotificationRequest(identifier: "myNotification", content: content, trigger: trigger)
You can first use the UNCalendarNotificationTrigger to trigger the notification at a particular date and set its repeat property to false so that it notifies only once. When you receive that notification, use the UNTimeIntervalNotificationTrigger API to set the time interval at which you want to trigger the local notification

Resources