I have a little app using SwiftUI and location triggered notifications.
I got started with this project as far as the location triggered notifications part is concerned.
It is already working quite well and I have notifications firing where I expect them, but there is one thing which I don't understand.
The function userNotificationCenter:didReceive:withCompletionHandler does not seem to fire at any time. This is the code for the function in case that may be useful, though it can only ring a bell for people who know the project.
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
if response.notification.request.identifier == "nyc_promenade_notification_id" {
let notificationData = response.notification.request.content.userInfo
let message = "You have reached \(notificationData["location"] ?? "your location!")"
let alertController = UIAlertController(title: "Welcome!",
message: message,
preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alertController, animated: true)
}
completionHandler()
}
Here is some additional information.
To be precise, I first made the assumption the userNotificationCenter:didReceive:withCompletionHandler method was called at some point. Without knowing if it was to be called explicitly or in the background by some framework. But here is what I can now say:
No explicit call to the function appears in the source of my project, neither in the GitHub project which I used as a starting point.
When I want to use the debugger to catch a passing through the method, I get nothing.
I also tried to put some trace (leaving some logs in a DB) in case the function would be called while walking and entering a given area. But this also led to nothing.
As a result I am not even sure this method is called (or supposed to be called).
In the end, if it is not meant to be fired. I will still have to figure out how and where I can take some action, on entering (or leaving) a given area (i.e. circular region).
Finally, here is the function fired when I decide to set a location based notification.
func requestNotification(notificationInfo: LocationNotificationInfo) {
let notification = notificationContent(notificationInfo: notificationInfo),
destRegion = destinationRegion(notificationInfo: notificationInfo)
let trigger = UNLocationNotificationTrigger(region: destRegion, repeats: false),
request = UNNotificationRequest(identifier: notificationInfo.notificationId,
content: notification, trigger: trigger)
UNUserNotificationCenter.current().add(request) {
[weak self] (error) in
DispatchQueue.main.async {
self?.delegate?.notificationScheduled(error: error)
}
}
}
Having no suggestion from any expert on the matter, I decided to make "a big test".
For that I recompiled the app, commenting out the function:
/*func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
.......
}*/
To see what would happen.
The result is that the app kept working as usual. Thus showing the function was not really useful, maybe even useless, at least in the range of my testing.
I hope this will be useful to someone else facing the same question. If you know more about the issue, please comment.
Related
I am working on an iOS application which involves device to device push notification. In Foreground and Background state, I am able to receive notification and able to perform respective actions in respective custom buttons (ACCEPT & REJECT). Everything works fine in the two mentioned states. But in killed/terminated state, although I am able to receive notification, but I am not able to perform action on clicking custom buttons (ACCEPT & REJECT). Can you guys help me this?
//Notification action button function
func setActionCategories(){
let acceptAction = UNNotificationAction(
identifier: NAString().notificationAcceptIdentifier(),
title: NAString().accept().capitalized,
options: [.init(rawValue: 0)])
let rejectAction = UNNotificationAction(
identifier: NAString().notificationRejectIdentifier(),
title: NAString().reject().capitalized,
options: [.init(rawValue: 0)])
let actionCategory = UNNotificationCategory(
identifier: NAString().notificationActionCategory(),
actions: [acceptAction,rejectAction],
intentIdentifiers: [],
options: [.customDismissAction])
UNUserNotificationCenter.current().setNotificationCategories(
[actionCategory])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
//Here we are performing Action on Notification Buttons & We created this buttons in "setActionCategories" function.
if response.notification.request.content.categoryIdentifier == NAString().notificationActionCategory() {
//Created Firebase reference to get currently invited visitor by E-Intercom
var gateNotificationRef : DatabaseReference?
gateNotificationRef = GlobalUserData.shared.getUserDataReference().child(Constants.FIREBASE_CHILD_GATE_NOTIFICATION).child(userUID).child(guestType!).child(guestUID!)
//Performing accept & reject on click of recently invited visitor by E-Intercom from Notification view.
switch response.actionIdentifier {
//If Accept button will pressed
case NAString().notificationAcceptIdentifier():
gateNotificationRef?.child(NAString().status()).setValue(NAString().accepted())
}
break
//If Reject button will pressed
case NAString().notificationRejectIdentifier(): gateNotificationRef?.child(NAString().status()).setValue(NAString().rejected())
break
default:
break
}
}
UIApplication.shared.applicationIconBadgeNumber = 0
completionHandler()
}
Hi Ashish can you provide some code for us to better assist you. There should be a completion handler in there where you can add an action function. Then you can perform whatever you need the buttons to do.
Add the below condition in didFinishLaunchingWithOptions delegate
if launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] != nil {
// Do what you want to do when remote notification is tapped.
}
you can call didReceive delegate method in it.
I'm using Firebase Messaging (Notifications) to send push reminders to users on iOS. For my app, that is a todo app, I'm using Swift 3. When the user gets the push notification I want them to be able to complete the task right from the push notification.
Everything works almost great. The user gets the push. When they 3d-touch they see the "complete button". When the "complete button" is tapped the didReceive response method in the app is triggered in the background.
Now to the problem, in that method I'm using a closure and then a closure in that closure. For some reason the first part of the code runs in the background without the user opening the app but the last part is only running when the user opens the app again (see below). Why is that?
This is my code:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if response.actionIdentifier == notificationActionComplete, let actionKey = userInfo["actionKey"] as? String {
getAction(actionKey: actionKey, completion: { (action) in
action.complete {
}
})
}
completionHandler()
}
func getAction(actionKey: String, completion:#escaping (Action)->Void) {
Database.database().reference(withPath: "actions/\(actionKey)").observeSingleEvent(of: .value, with: { snapshot in
let action = Action(snapshot: snapshot)
completion(action)
})
}
In action class:
var ref: DatabaseReference?
init(snapshot: DataSnapshot) {
key = snapshot.key
ref = snapshot.ref
//Other inits here
}
func complete(completion:#escaping (Void) -> Void) {
//This code to remove the node is running fine in background
ref.removeValue { (error, ref) in
//The code in here is not running until the user opens the app next time
otherRef.updateChildValues(self.toAnyObject(), withCompletionBlock: { (error, ref) in
completion()
})
}
Your app is basically suspended after the runloop cycle where userNotificationCenter() is called, so if your completion handler is in response to asynchronous work, that work will never happen until your app resumes again. To get around this you will probably need to begin a background task inside that function, and then have your completion handler end the background task when it is finished. This tells the system you need to stay alive for a while in the background (although it is not guaranteed, if you take too long)
See "Executing Finite Limit Tasks" at this URL (sorry, it's Obj-C, but there should be a Swift way to do it too):
https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
I have a weather app for iOS, and I'd like to allow the user to receive a notification each morning at a time of their choosing which would fetch the weather forecast for the day and display a notification.
I'd like to avoid using push notifications, and I thought I might be able to use local notifications, except I can't see a way to fetch the content to be shown from a server. It looks like the content has to be set at the time of scheduling. Is that right?
That makes me think I might be able to register my application to use background execution to periodically fetch the weather and schedule a notification with the latest content, but this seems wasteful.
In short, I'd like to tell iOS to run a specific function at a specific time. Is there a good option for this that I'm missing? Are push notifications the only/best way to accomplish this sort of thing?
Push notification is best option for your if you want to display weather forecast .
More about this : https://stackoverflow.com/a/41901767/3901620
You can schedule a local notification for a specific time and when a user sees it and if he wants he can open your app by tapping on that notification. At that time, you will able to know that, a user has tapped on a notification and thus the app is open, you can make a network call to fetch the data and show it inside the application. This will not require any background calls therefor and only make a network call to an action by a user.
Another option: You can create a widget of your app (like Weather Widget). Whenever a user goes into widget area you will get a delegate call and make a network call to get the latest weather data. If a user wants more information on it, he can simply tap on it and your app will open. Then, everything will be in your hands.
Your option: You can always get dynamic content whenever the user opens your app for a particular date and set a notification for it. But this is not suggestible as the user may not get updated data.
Push Notification: This may not be required with your case, however, if you want to send the dynamic data over your server to your app. This is always the best option.
i have created a function. In which this will call your function at a specific time, when you want. Am creating a clock app so i need to trigger a local notification when ever user created the alarm. And in the notification Center Delegate method, you can handle your response and call the whatever method you want.
class LocalNotificationMethod : NSObject {
static let notificationInstance = LocalNotificationMethod()
let requestIdentifier = "SampleRequest" //identifier is to cancel the notification request
internal func scheduleLocalNotification(titleOfNotification:String, subtitleOfNotification:String, messageOfNotification:String, soundOfNotification:String, dateOfNotification:String) {
if #available(iOS 10.0, *) {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd hh:mm a"
let date3 = formatter.date(from: dateOfNotification)
let content = UNMutableNotificationContent()
content.body = NSString.localizedUserNotificationString(forKey: titleOfNotification, arguments: nil)
content.sound = soundOfNotification.characters.count > 0 ? UNNotificationSound.init(named: soundOfNotification + ".mp3") : UNNotificationSound.default()
let trigger = UNCalendarNotificationTrigger.init(dateMatching: NSCalendar.current.dateComponents([.day, .month, .year, .hour, .minute], from: date3!), repeats: false)
let request = UNNotificationRequest(identifier:requestIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request){(error) in
if (error != nil){
print(error?.localizedDescription)
} else {
print("Successfully Done")
}
}
} else {
// Fallback on earlier versions
}
}
}
And in AppDelegate Methods : - You can handle whenever user click on your notification or whenever your notification will be present.Is up to you what you want to done.
//MARK:- Notification Delegates
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("Tapped in notification")
}
//This is key callback to present notification while the app is in foreground
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("Notification being triggered")
//You can either present alert ,sound or increase badge while the app is in foreground too with ios 10
//to distinguish between notifications
if notification.request.identifier == "SampleRequest" {
completionHandler( [.alert,.sound,.badge])
}
}
I have been trying to use a Firebase listener to trigger local notifications. I have found a post that addresses exactly what I am trying to do with much of it explained, however I do not have the reputation to comment on the post and there seems to be no indication of how to accomplish what I want anywhere else.
The original poster says this.
I figured it out! I had to use a different approach but i was able to
get my Firebase Database observer to trigger notifications in the
background.
As long as the object containting the database observer is not
deallocated from memory it will continue to observe and trigger. So I
created a global class which contains a static database object
property like this:
class GlobalDatabaseDelegate {
static let dataBase = DataBase()
}
This is where I am confused as to what to do for my own project. It is my understanding that I have to create a class similar to DataBase() which contains my database reference. The problem is I do not understand how to create class object that will contain the database listener.
say for example my reference is :
let userRef = FIRDatabase.database.reference().child("users")
And I want to observe any users added to the database and then trigger a local notification. I am able to write the code to do so, just not sure how to contain it in an object class of its own and then make it static.
Forgive me for being a little slow. Any help would be very much appreciated.
The rest of the post follows :
I also extended the DataBase class to be the
UNUserNotificationCenterDelegate so it can send the push notitications
like this:
extension DataBase: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("Tapped in notification")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("Notification being triggered")
completionHandler( [.alert,.sound,.badge])
}
func observeNotificationsChildAddedBackground() {
self.notificationsBackgroundHandler = FIREBASE_REF!.child("notifications/\(Defaults.userUID!)")
self.notificationsBackgroundHandler!.queryOrdered(byChild: "date").queryLimited(toLast: 99).observe(.childAdded, with: { snapshot in
let newNotificationJSON = snapshot.value as? [String : Any]
if let newNotificationJSON = newNotificationJSON {
let status = newNotificationJSON["status"]
if let status = status as? Int {
if status == 1 {
self.sendPushNotification()
}
}
}
})
}
func sendPushNotification() {
let content = UNMutableNotificationContent()
content.title = "Here is a new notification"
content.subtitle = "new notification push"
content.body = "Someone did something which triggered a notification"
content.sound = UNNotificationSound.default()
let request = UNNotificationRequest(identifier: "\(self.notificationBackgroundProcessName)", content: content, trigger: nil)
NotificationCenter.default.post(name: notificationBackgroundProcessName, object: nil)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(request){ error in
if error != nil {
print("error sending a push notification :\(error?.localizedDescription)")
}
}
}
}
In essence I am trying to keep a firebase listener in memory when the app is in background.
So the original post that I have linked in has the answer but it is a matter of understanding it. I have also implemented my code in a slightly different approach.
I found another post detailing the technique needed to run a custom data service class. Custom Firebase Data Service Class : Swift 3
To set keep the firebase listener in memory there are few steps.
1.Create a firebase data service class. In that class I have a static variable that is of the same class
class FirebaseAPI {
var isOpen = false
static let sharedInstance = FirebaseAPI()
// I added functions for firebase reference in this class
func observeNotifications(){
//firebase call here
}
}
2.Set up notification settings in app delegate. This is where my set up differs from the original post.
let notificationSettings = UIUserNotificationSettings(types: [.badge, .alert, .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(notificationSettings)
3.Create a reference to the firebase class in a viewcontroller of your choice, it works in app delegate but not advisable.
let sharedInstance = FirebaseAPI.sharedInstance
4.Call functions to setup observer
self.sharedInstance.observeNotifications()
You can then trigger fire a local notification using a completion handler with the function or fire off notifications within the firebase function.
Update: Apple have implemented updates in regards to background modes which have stopped this method from working . Currently the only method is to use APNS
To test local notifications, I wrote a test app with a single view controller.
In viewDidLoad, I set up the custom action, the notification category, and the userNotificationCenter delegate.
In viewDidAppear, I set the notification content, setup a trigger that fires after 5 sec, create the notification request, and add it to the notification center.
I expect the following:
Foreground mode:
When the app is launched, it should present after 5 sec the notification in foreground. Before, the delegate function „willPresent notification“ should be called.
Background mode:
If, however, the app is put into background by pressing the home button before the trigger fires, the notification should be presented in the home screen, and the delegate function „willPresent notification“ is not called.
After the notification has been presented, the user can tap the action button.
This should bring the app into foreground, and trigger the „didReceive response“ delegate function.
What happens is:
The action button in never shown, only title and body.
When I tap the body, the delegate function „didReceive response“ is triggered using the default action identifier.
The problem:
Why is the custom action button not shown?
Here is my code:
import UIKit
import UserNotifications
class ViewController: UIViewController, UNUserNotificationCenterDelegate {
let userNotificationCenter = UNUserNotificationCenter.current()
let categotyId = "categoryID"
let actionID = "actionID"
override func viewDidLoad() {
super.viewDidLoad()
userNotificationCenter.requestAuthorization(options: [.alert]) { (granted, error) in
if granted {
let okAction = UNNotificationAction(identifier: self.actionID,
title: "OK",
options: [])
let category = UNNotificationCategory(identifier: self.categotyId,
actions: [okAction],
intentIdentifiers: [],
options: [.customDismissAction])
self.userNotificationCenter.setNotificationCategories([category])
self.userNotificationCenter.delegate = self
} else {
print("local notifications not granted")
}
}
userNotificationCenter.removeAllPendingNotificationRequests()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: "Title", arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: "Body", arguments: nil)
content.categoryIdentifier = categotyId
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: (5), repeats: false)
let request = UNNotificationRequest.init(identifier: "requestID",
content: content,
trigger: trigger)
userNotificationCenter.add(request, withCompletionHandler: { (error) in
if let error = error {
print("Could not add notification request. Error: \(error)")
}
})
}
// MARK: - Notification Delegate
// Will be called while app is in the foreground
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
// Show alert to the user
print("App in foreground. Show alert.")
completionHandler([.alert])
}
// Should be called after the user tapped the action button
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let request = response.notification.request
let requestID = request.identifier
switch response.actionIdentifier {
case actionID:
print("Custom OK action triggered in background")
case UNNotificationDefaultActionIdentifier:
print("Default action triggered in background")
default:
print("Unknown action triggered in background, action identifier: \(response.actionIdentifier)")
}
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [requestID])
completionHandler()
}
}
Sorry for my question, but maybe somebody else has the same problem:
I simply did not know that first, only title/body is displayed:
However, I was not aware of the thin grey bar below the body. If this bar is pulled down, the custom action button appears:
Update: As of iOS 10 beta 2, rich notifications are also available on pre-3D touch devices. Pull down on the regular notification to see it.
Make sure you are testing on a iPhone6s/iPhone6s plus simulator/device, it doesn't seem to work on pre-3D touch devices.
On a iPhone6 simulator, try to click and drag down on the stock notification you get and you should see your custom UI appear.