Trigger Xcode notification after certain time has passed (Swift 3) - swift

I've tried a lot of things but I just can't seem to figure out which command to use to trigger an alert in (example) 2 days, or 5 hours. Can anyone help me out? What I'd like to be able to do is the following:
var number = 3
var repeat = day
*Code with 'number' and 'repeat' in it.*
So in this case it would send out an alert in 3 days. Anyone here knows how to do that? ^^
Thanks in advance!

First, place this on the top of the file that needs notifications:
import UserNotifications
Then place this in application(_:didFinishLaunchingWithOptions:) of AppDelegate
let notifCenter = UNUserNotificationCenter.current()
let options: UNAuthorizationOptions = [.alert, .badge, .sound]
notifCenter.requestAuthorization(options: options, completionHandler: nil)
So now request the Notification as follows:
// timeInterval is in seconds, so 60*60*12*3 = 3 days, set repeats to true if you want to repeat the trigger
let requestTrigger = UNTimeIntervalNotificationTrigger(timeInterval: (60*60*12*3), repeats: false)
let requestContent = UNMutableNotificationContent()
requestContent.title = "Title" // insert your title
requestContent.subtitle = "Subtitle" // insert your subtitle
requestContent.body = "A body in notification." // insert your body
requestContent.badge = 1 // the number that appears next to your app
requestContent.sound = UNNotificationSound.default()
// Request the notification
let request = UNNotificationRequest(identifier: "PutANameForTheNotificationHere", content: requestContent, trigger: requestTrigger)
// Post the notification!
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
// do something to the error
} else {
// posted successfully, do something like tell the user that notification was posted
}
}
This part of code does three things:
Define the trigger
Create the notification context
Post the context
If you intend to continue to work with notifications, look at this tutorial, which also talks about how to react when the user taps on your notification.
Note: UNMutableNotificationContext is mutable, despite the name, UN is just the namespace. Don't get confused!
If you provide a subtitle, it will be between the title and body.
This is the badge property:

Related

Is it possible to mutate the content of a local notification in swift?

In the next version of my app, I want to integrate the possibility that the user can enable weekly reports like the screentime feature from apple.
I know how to setup Local Notifications (The code is already working for other types of notifications like reminders for events), but for this idea, I need the notification text to be updated shortly before firing because I don`t know the exact content of the notification when I add it to the NotificationCenter.
Is there a function which is called shortly before firing or how would I do this?
This is my working code for scheduling notifications:
fileprivate func generateNotification(title: String, body: String, triggerDate: DateComponents) -> String{
//Trigger
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: false)
//Content
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = UNNotificationSound.default
//Request
let identifier = UUID().uuidString
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) { (error) in
if let error = error {
print("Error \(error.localizedDescription)")
}
}
return identifier
}

How to show region notification for iOS at most once a month?

My app's deployment target is 10.0, and I used UNUserNotificationCenter to show region notification even when the app is closed/killed. But new mission is to show it at most once a month, though the user may enter the region more than once a month.
What I tried until now (which worked great) is...
let content = UNMutableNotificationContent()
content.title = "... Reminder"
content.body = "Welcome to \(element.name). Please let us know how we can serve you and your loved ones, and we hope ... will simplify your visit here."
content.sound = UNNotificationSound.default
content.categoryIdentifier = "LOCATION_CAT"
let centerCoordinate2D = element.location.coordinate
let identifierName = element.name.replacingOccurrences(of: " ", with: "_")
let region = CLCircularRegion(center: centerCoordinate2D, radius: 300, identifier: identifierName)
region.notifyOnExit = false
region.notifyOnEntry = true
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
// request = content + trigger
let request = UNNotificationRequest(identifier: "REGION \(element.name)", content: content, trigger: trigger)
// add (or "schedule") request to center
let center = UNUserNotificationCenter.current()
center.add(request, withCompletionHandler: { (error: Error?) in
if let theError = error {
print(theError.localizedDescription)
}
})
But then, to let it happen at most once a month, I did the following:
let centerCoordinate2D = element.location.coordinate
let identifierName = element.name.replacingOccurrences(of: " ", with: "_")
let region = CLCircularRegion(center: centerCoordinate2D, radius: 300, identifier: identifierName)
region.notifyOnExit = true
region.notifyOnEntry = true
R.shared.appleLocationManager.startMonitoring(for: region)
Also in AppDelegate.swift,
extension AppDelegate: CLLocationManagerDelegate {
// called when user Enters a monitored region
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("AppDelegate.locationManager( didEnterRegion ): called with region identifier: \(region.identifier)")
if region is CLCircularRegion {
// Do what you want if this information
self.handleEvent(forRegion: region)
}
}
func handleEvent(forRegion region: CLRegion) {
// we save 'date' with "NOTIFICATION DATE request_identifier" as its key.
let key = "NOTIFICATION DATE \(region.identifier)"
let defaultValue = defaults.double(forKey: key)
if defaultValue == 0 {
print("AppDelegate.handleEvent(): need to show notification: no key")
// set value first.
defaults.set(Date().timeIntervalSince1970, forKey: key)
showNotification(forRegion: region)
} else {
let diff = Date().timeIntervalSince(Date(timeIntervalSince1970: defaultValue))
if diff > 60 * 60 * 24 * 30 {
print("AppDelegate.handleEvent(): need to show notification: diff > 30 days")
// set value first.
defaults.set(Date().timeIntervalSince1970, forKey: key)
showNotification(forRegion: region)
} else {
// just pass.
print("AppDelegate.handleEvent(): need NOT to show notification: diff: \(dot2(diff / 24 / 60)) mins")
}
}
}
func showNotification(forRegion region: CLRegion, message: String = "") {
// customize your notification content
let content = UNMutableNotificationContent()
content.title = "... Reminder"
let hospitalName = region.identifier.replacingOccurrences(of: "_", with: " ")
content.body = "Welcome to \(hospitalName). \(message) Please let us know how we can serve you and your loved ones, and we hope ... will simplify your visit here."
content.sound = UNNotificationSound.default
// the actual trigger object
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0,
repeats: false)
// notification unique identifier, for this example, same as the region to avoid duplicate notifications
let identifier = "REGION \(hospitalName)"
// the notification request object
let request = UNNotificationRequest(identifier: identifier,
content: content,
trigger: trigger)
// trying to add the notification request to notification center
UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
if let theError = error {
print(theError.localizedDescription)
}
})
}
with the following, still for the AppDelegate class:
let appleLocationManager = CLLocationManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.appleLocationManager.delegate = self
...
}
I think there is error in the code, but it is not clear to me if the locationManager( didExitRegion: ) is to be called even when the app is closed/killed - in which case appleLocationManager is not alive?
If I can't use locationManager( didExitRegion: ) for this problem, what can I do to make the region notification happen at most once a month? I also know that there is a different type of trigger, UNTimeIntervalNotificationTrigger or UNLocationNotificationTrigger, and I wanted to use them somehow to solve this problem, but would there be any way to make it run some of my code even when the app is not running at all? If this is impossible to solve, isn't it enough to say that the region notification is too much restricted?
Described as Apple's document, region monitoring with CLLocationManager wakes up your app if needed.
c.f. https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions
Whenever the user crosses the boundary of one of your app's registered regions, the system notifies your app. If an iOS app is not running when the boundary crossing occurs, the system tries to launch it. An iOS app that supports region monitoring must enable the Location updates background mode so that it can be launched in the background.
You can also detect if the app is launched by regional notification with UIApplication.LaunchOptionsKey.
Boundary crossing notifications are delivered to your location manager's delegate object. Specifically, the location manager calls the locationManager(:didEnterRegion:) or locationManager(:didExitRegion:) methods of its delegate. If your app was launched, you must configure a CLLocationManager object and delegate object right away so that you can receive these notifications. To determine whether your app was launched for a location event, look for the UIApplication.LaunchOptionsKey in the launch options dictionary.

Change Repeating Reminder's Content Daily

In my app, I ask the user to set up a repeating reminder. In this reminder, I would like the body to say something different each day. For example, I have over 500 quotes in my Firebase db and I want my reminder to show a new quote each day. How can I programmatically change the body of the reminder each day without user interaction?
#IBAction func saveButtonPressed(_ sender: Any) {
let content = UNMutableNotificationContent()
let identifier = "myApp"
content.title = "myApp"
content.body = "I want to change this programatically each day"
let trigger = UNCalendarNotificationTrigger(dateMatching: Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: reminderTime.date), repeats: true)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request){
(error) in
if error != nil
{
print("here error in setting up notification")
print(error!)
}
else
{
print("notification scheduled")
}
}
}
Unfortunately, you can not handle the LocalNotification showing moment. You just tell the system when to show your notification and the system show it without calling your application.
But you can add multiple notification requests at once (up to 64). For example, all notifications for the next month and update your notifications on app launch. Just make sure that your notification requests has different identifiers.
For example, you create 30 notifications and user receives them for 5 days. Then he opens your app and you add 5 notifications more.
If you'll need to remove some of the pending notifications, you can make it using the following method.
let center = UNUserNotificationCenter.current()
center.removePendingNotificationRequests(withIdentifiers: identifiers)

Swift firebase listener object deallocated from memory

I have a chat feature that utilizes a firebase listener that fires when a "new message" is made in order to launch a notification. The problem is that the listener object gets deallocated from memory so there will be no notifications. After researching I have found that I need to use a data service class to hold the listener object to keep it in memory.
Keep a Firebase listener in memory when the app is in background
I am using MySQL to hold the chat IDS and doing an http request to fetch them in ViewDidAppear. Then I am looping through the array of IDs to attach the listeners like so:
for value in informationArray {
let chatRef = DatabaseReference.chats.reference()
let query = chatRef.queryOrdered(byChild: "uid").queryEqual(toValue: (value))
query.observe(.childAdded, with: { snapshot in
let chat = Chat(dictionary: snapshot.value as! [String : Any])
if !self.chats.contains(chat) {
self.chats.insert(chat, at: 0)
self.tableView.reloadData()
//self.fetchChats()
}
chat.ref.child("lastMessage").observe(.value, with: { snapshot in
//(some code that performs tasks specific to this view controller)
let content = UNMutableNotificationContent()
content.title = "You have a new Message!"
content.subtitle = chatTitle
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)
})
chat.ref.child("lastUpdate").observe(.value, with: { snapshot in
//(some more code that performs tasks specific to this view controller)
I need to keep the reference for "lastMessage" turned on so I will continue to get the notifications. I also need this reference to perform the additional VC tasks. Finally, I have to keep the additional references (chat.ref.child...) though they can be deallocated. How can I setup my data service class to accomplish my goals?

How to trigger a WK Notification

What code do I write to trigger a watch kit notification from the watch app itself? For example, if I connect a button from my watch storyboard to my WatchInterfaceController as an action then when pressed it triggers a notification on the watch.
For trigger a notification, first of all you need permission: (declared usually in the ExtensionDelegate)
func askPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert,.sound]) { (authBool, error) in
if authBool {
let okAction = UNNotificationAction(identifier: "ok", title: "Ok", options: [])
let category = UNNotificationCategory(identifier: "exampleCategoryIdentifier", actions: [okAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
UNUserNotificationCenter.current().delegate = self
}
}
}
For having this working, you need to import (in the ExtensionDelegate) "UserNotifications" and to extend:
UNUserNotificationCenterDelegate
Once you have done that, you can call askPermission where you want, in a way like this:
if let delegate = WKExtension.shared().delegate as? ExtensionDelegate {
delegate.askPermission()
}
Now you have (hopefully) the permission for trigger a notification!
For trigger a notification, you can use a function like this:
func notification() {
let content = UNMutableNotificationContent()
content.body = "Body Of The Notification"
content.categoryIdentifier = "exampleCategoryIdentifier" // Re-Use the same identifier of the previous category.
content.sound = UNNotificationSound.default() // This is optional
let request = UNNotificationRequest(identifier: NSUUID().uuidString,
content: content,
trigger: nil)
let center = UNUserNotificationCenter.current()
center.add(request) { (error) in
if error != nil {
print(error!)
} else {
print("notification: ok")
}
}
}
In order to test watch notifications, you must first create a new build scheme.
Duplicate your watch app scheme, and in the "Run" section, choose your custom notification as the executable.
Now you can run the notification scheme.
Inside the extensions group in your project, under Supporting Files is a file called PushNotificationPayload.json.
You can edit the payload file to try different notifications and categories.