My notification works fine on simulator but on my device (running iOS 10.3.3) the notification does not show at all (phone locked at time of trigger) but the phone does vibrate when triggered and the app does get updated to correct settings so I know that the notification is getting triggered - its just not visually showing on the device. Have I missed anything?
This is where I setup authorisation for notifications in AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
locationManager.delegate = self
// Setup notifications authorization request
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Check if granted, if not then notify user
if granted {
print("Notification access granted")
} else {
print(error?.localizedDescription ?? "General Error: notification access not granted")
self.window?.rootViewController?.showAlertApplicationSettings(forErorType: .turnOnNotifications)
}
}
Location trigger in AppDelegate:
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if region is CLCircularRegion {
print("DidEnter: \(region.identifier)")
handleEvent(forRegion: region)
}
}
Handle event code that gets data from core data and then send to notifyUser method in AppDelegate:
func handleEvent(forRegion region: CLRegion) {
guard let reminder = getReminder(fromRegionIdentifier: region.identifier) else {
// There was a problem access the notification data, inform user
notifyUser(title: "Reminder notifiction error", subtitle: "One of your notifications has just been triggered but error restriving notification data", notes: nil)
return
}
notifyUser(title: reminder.titleString, subtitle: "Reminder has been triggered", notes: reminder.notesString)
updateRemindersState(reminder: reminder)
}
This is the actual notification trigger code in AppDelegate:
func notifyUser(title: String, subtitle: String, notes: String?) {
// show an alert if applocation is active
if UIApplication.shared.applicationState == .active {
window?.rootViewController?.showAlert(title: title, message: subtitle)
} else {
let notification = UNMutableNotificationContent()
notification.title = title
notification.subtitle = subtitle
if let notes = notes {
notification.body = notes
}
notification.sound = UNNotificationSound.default()
let request = UNNotificationRequest(identifier: "Notification", content: notification, trigger: nil)
UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
if let error = error {
print(error)
}
})
}
}
p.s. checked that on my device in settings its has Show in Notifications Centre, sounds, show on lock screen, banners all turned on.
Related
With the following code I can successfully create a reminder event and add an alarm to it that triggers 10 seconds after the event has been created. What I don't like about the way the reminder is created is that it shows in the Apple's Reminders app and when you get the notification message in your device, it shows the Reminders' app icon.
Is it possible to make the reminder private so it doesn't show in Apple's Reminders app? If not, what are my options to achieve such of task?
Note: I don't mind storing the reminders in the standard reminders local database as long as they don't show in the default Reminders app.
import EventKit
class ViewController: UIViewController{
var eventStore = EKEventStore()
override func viewDidLoad(){
super.viewDidLoad()
// get user permission
eventStore.requestAccess(to: EKEntityType.reminder, completion: {(granted, error) in
if !granted{
print("Access denied!")
}
})
}
#IBAction func createReminder(_ sender: Any) {
let reminder = EKReminder(eventStore: self.eventStore)
reminder.title = "Get Milk from the Store"
reminder.calendar = eventStore.defaultCalendarForNewReminders()
let date = Date()
let alarm = AKAlarm (absoluteDate: date.addingTimeInterval(10) as Date)
reminder.addAlarm(alarm)
do {
try eventStore.save(reminder, commit: true)
} catch let error {
print("Error: \(error.localizedDescription)")
}
}
}
FYI - To make the above code work you would need to add the NSRemindersUsageDescription key in the info.plist file.
Just for the record, what I was looking for was User Notifications.
Here is a complete example.
User Notifications
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
if granted{
print("User gave permissions for local notifications")
}else{
print("User did NOT give permissions for local notifications")
}
}
return true
}
override func viewDidLoad() {
super.viewDidLoad()
setReminderAtTime()
}
func setReminderAtTime(){
let reminderTime:TimeInterval = 60
let center = UNUserNotificationCenter.current()
center.removeAllPendingNotificationRequests()
let content = UNMutableNotificationContent()
content.title = "Title"
content.body = "Notification Message!."
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: reminderTime, repeats: false)
let request = UNNotificationRequest(identifier: "reminderName", content: content, trigger: trigger)
center.add(request) { (error) in
if error != nil{
print("Error = \(error?.localizedDescription ?? "error local notification")")
}
}
}
I have a working application with notifications that appear well. Now I would like to handle some event on notification like when the user tap on the banner.
My iOS Deployment Target is 11.0 and the same for my deployment target.
I implemented everything in my AppDelegate.swift file:
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, RCTBridgeDelegate {
var window: UIWindow?
var didFinishLaunching: Bool = false
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let bridge = RCTBridge(delegate: self, launchOptions: launchOptions)
let rootView = RCTRootView(bridge: bridge, moduleName: "root", initialProperties: nil)
rootView?.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1)
self.window = UIWindow(frame: UIScreen.main.bounds)
let rootViewController = UIViewController()
rootViewController.view = rootView
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
didFinishLaunching = true
Fabric.with([Crashlytics.self])
FirebaseApp.configure()
// This block is necessary to ask user authorization to receive notification
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: {(grant, error) in
if error == nil {
if grant {
print("### permission granted")
application.registerForRemoteNotifications()
} else {
//User didn't grant permission
}
} else {
print("error: ",error)
}
})
} else {
// Fallback on earlier versions
let notificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(notificationSettings)
}
self.firstLaunchAction()
self.initTracker()
RNSplashScreen.showSplash("LaunchScreen", inRootView: rootViewController.view)
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenParts = deviceToken.map { data -> String in
return String(format: "%02.2hhx", data)
}
let token = tokenParts.joined()
print("### Device Token: \(token)")
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("### Failed to register for remote notifications with error: \(error)")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "like" {
print("### Handle like action identifier")
} else if response.actionIdentifier == "save" {
print("### Handle save action identifier")
} else {
print("### No custom action identifiers chosen")
}
// Make sure completionHandler method is at the bottom of this func
completionHandler()
}
As you can see, I use Firebase to send remote notification so I have a payload in json like this :
return {
notification: {
title,
body
},
data: {
title,
body,
...data
},
android: {
ttl: 3600 * 1000,
notification: {
icon: 'stock_ticker_update',
color: '#002559'
}
},
apns: {
payload: {
aps: {
alert: {
title: 'NITL ACUMEN BI RFS CODA Dev Offshore',
body: 'Send a Message - PN testing group'
},
sound: 'default'
}
}
},
condition
};
Most part of my code is copied from website. But my actual behaviour is that, when I click on the notification banner nothing happen ...
I see the nofication banner so I think there is no problem with my APNs registration. I don't know why didReceive is not trigger on click event.
Do I miss something in my implementation ? My notification payload is wrong ?
Does someone can help me to figure out where is my mistake ? I've read an infinite number of tutorials without results. Some help would be really appreciated thank you :)
To support a background update notification, make sure that the payload’s aps dictionary includes the content-available key with a value of 1.
{
"aps" : {
"content-available" : 1,
"sound" : “default"
....
},
....
}
When a background update notification is delivered to the user’s device, iOS wakes up your app in the background and gives it up to 30 seconds to run. In iOS, the system delivers background update notifications by calling the application: didReceiveRemoteNotification: fetchCompletionHandler: method of your app delegate.
Reference: Creating the Remote Notification Payload
i am new in estimote beacon programming .i want to detect the estimote beacon when app is closed. And when beacon is detected then fire the particular notification. how i can done it?
give me suggestions please. i had done the following code. but i can not getting any notification
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
beaconManager.delegate = self
beaconManager.startMonitoring(for: region)
beaconManager.startRangingBeacons(in: region)
}
func beaconManager(_ manager: Any, didEnter region: CLBeaconRegion) {
NSLog("beaconManager : didEnter Called")
let content = UNMutableNotificationContent()
content.title = "Beacon Detected"
content.body = "Enter region"
content.sound = UNNotificationSound.default()
let trigger = UNLocationNotificationTrigger(region:region, repeats:false)
let request = UNNotificationRequest(identifier: "Enter region", content: content, trigger: trigger)
center.add(request) { (error) in
if let error = error {
print(error.localizedDescription)
}
}
}
func beaconManager(_ manager: Any, didExitRegion region: CLBeaconRegion) {
NSLog("beaconManager : didExitRegion Called")
let content = UNMutableNotificationContent()
content.title = "Beacon Detected"
content.body = "Exit region"
content.sound = UNNotificationSound.default()
let trigger = UNLocationNotificationTrigger(region:region, repeats:false)
let request = UNNotificationRequest(identifier: "Enter region", content: content, trigger: trigger)
center.add(request) { (error) in
if let error = error {
print(error.localizedDescription)
}
}
}
In app delegate write the desired code under estimate delegate methods. Sample code as follows,
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
if launchOptions["UIApplicationLaunchOptionsLocationKey"] != nil {
beaconManager = ESTBeaconManager()
beaconManager.delegate = self
// don't forget the NSLocationAlwaysUsageDescription in your Info.plist
beaconManager.requestAlwaysAuthorization()
beaconManager.startMonitoring(for: ESTBeaconRegion(proximityUUID: ESTIMOTE_PROXIMITY_UUID, identifier: "AppRegion"))
}
return true
}
func beaconManager(_ manager: ESTBeaconManager, didEnter region: ESTBeaconRegion) {
let notification = UILocalNotification()
notification.alertBody = "Enter region"
notification.soundName = UILocalNotificationDefaultSoundName as? String
UIApplication.shared.presentLocalNotificationNow(notification)
}
func beaconManager(_ manager: ESTBeaconManager, didExitRegion region: ESTBeaconRegion) {
let notification = UILocalNotification()
notification.alertBody = "Exit region"
notification.soundName = UILocalNotificationDefaultSoundName as? String
UIApplication.shared.presentLocalNotificationNow(notification)
}
This is old code, may have few changes. Follow estimate community's thread for more information. You can find your problem on this thread https://community.estimote.com/hc/en-us/articles/203253193-Pushing-notifications-from-iBeacon-when-the-app-is-in-the-background-or-killed
I have a public function for local notification and i want to call that function in applicationWillTerminate. When i try this, nothing happens. I'm sure local notification function is ok because when i call that in viewDidLoad, it works properly.
This is the function for local notification;
func scheduleLocalNotification(second: Double) {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.badge = 1
content.title = "Title!"
content.body = "Message!"
content.categoryIdentifier = "id"
content.userInfo = ["key": "value"]
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: second, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
} else {
// Fallback on earlier versions
}
}
AppDelegate;
func applicationWillTerminate(_ application: UIApplication) {
scheduleLocalNotification(second: 30.0)
print("Terminating!")
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
I searched SO for similar problems and found 2 questions, but none of them solve my issue.
Send local notification after termination app swift 2
Local notification on application termination
Edit: Request Authorization;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let launchedBefore = UserDefaults.standard.bool(forKey: "launchedBefore")
if (!launchedBefore) {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Authorization granted!")
} else {
print("Authorization not granted!")
}
}
} else {
let settings = UIUserNotificationSettings(types: [.alert, .badge , .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
UserDefaults.standard.set(true, forKey: "launchedBefore")
}
// Override point for customization after application launch.
return true
}
An answer to the second question you linked to seems relevant - applicationWillTerminate is not guaranteed to get called if the system decides to terminate your app while it's in the background.
just a note about applicationWillTerminate: as Apple states, and "Uncommon" says, there is no guarantee that is called.
But I think is a design problem:
a) if You want to add a local notification, do it as soon as user have set it.
b) if You are passing via "applicationWillTerminate" to save cpu time calling "add notification" only when exiting, be sure there is no big penalty saving notifications every time. As a std behaviour, in iOS we should save everything as soon as user are set it.
in my apps I simply add notification on "back" or "close".
c) Be sure to control/reset notifications already set.
If you are only testing iOS behaviour, you are in corner edge, see "eskimo" answer here:
https://forums.developer.apple.com/thread/13557
I'm using google cloud messaging to send notifications to my iOS application.
I've configured the GCM services in my App and the notifications are working correctly. I mean that my device can request a gcmRegistrationToken and I can send notification using postman to my app. These notifications are working both when app is active and inactive.
Now I want to use the Device Groups Notifications feature. So I created a group for all my user's device:
https://android.googleapis.com/gcm/notification
Content-Type:application/json
Authorization:key=API_KEY
project_id:SENDER_ID
{
"operation": "create",
"notification_key_name": "appUser-userA",
"registration_ids": ["regToken1", "regToken2"]
}
I receive my device group key:
{
"notification_key": "deviceGroupKey"
}
But no when I want to send notification to the device group using postman, I only receive the notification when the app is ACTIVE.
Here is my POST request:
https://android.googleapis.com/gcm/send
Content-Type:application/json
Authorization:key=API_KEY
{
"to" : "deviceGroupToken",
"content_available" : true,
"priority": "high",
"sound": "default",
"notification" : {
"body_loc_key" : "NOTIF_FRIEND_REQUEST",
"body_loc_args" : ["UserB"],
"click_action": "NEW_FRIEND_REQUEST"
},
"data" : {
"friendRequestId": "12345678901234567890"
}
}
Here is my AppDelegate.swift:
//
// AppDelegate.swift
//
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GGLInstanceIDDelegate, GCMReceiverDelegate {
var window: UIWindow?
var connectedToGCM = false
var gcmSenderID: String?
var gcmRegistrationToken: String?
var gcmRegistrationOptions = [String: AnyObject]()
let gcmRegistrationKey = "onRegistrationCompleted"
var subscribedToTopic = false
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
print("bundleId=\(NSBundle.mainBundle().bundleIdentifier)")
// Configure Google Analytics
// Configure tracker from GoogleService-Info.plist.
var configureError:NSError?
GGLContext.sharedInstance().configureWithError(&configureError)
assert(configureError == nil, "Error configuring Google services: \(configureError)")
// Optional: configure GAI options.
let gai = GAI.sharedInstance()
gai.trackUncaughtExceptions = true // report uncaught exceptions
gai.logger.logLevel = GAILogLevel.Verbose // remove before app release
// Override point for customization after application launch.
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)) // types are UIUserNotificationType members
// Register for remotes notifications
UIApplication.sharedApplication().registerForRemoteNotifications()
// Get the gcm sender id
gcmSenderID = GGLContext.sharedInstance().configuration.gcmSenderID
var gcmConfig = GCMConfig.defaultConfig()
gcmConfig.receiverDelegate = self
GCMService.sharedInstance().startWithConfig(gcmConfig)
return true
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
GCMService.sharedInstance().disconnect()
connectedToGCM = false
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
// -->The app go through this point
// Connect to the GCM server to receive non-APNS notifications
GCMService.sharedInstance().connectWithHandler(gcmConnectionHandler)
// -->The app go through this point
}
func gcmConnectionHandler(error: NSError?) {
// -->The app never enter in this function
if let error = error {
print("Could not connect to GCM: \(error.localizedDescription)")
} else {
self.connectedToGCM = true
print("Connected to GCM")
// ...
}
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
self.saveContext()
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
// Create a config and set a delegate that implements the GGLInstaceIDDelegate protocol.
let instanceIDConfig = GGLInstanceIDConfig.defaultConfig()
instanceIDConfig.delegate = self
// Start the GGLInstanceID shared instance with that config and request a registration
// token to enable reception of notifications
GGLInstanceID.sharedInstance().startWithConfig(instanceIDConfig)
gcmRegistrationOptions = [kGGLInstanceIDRegisterAPNSOption:deviceToken,
kGGLInstanceIDAPNSServerTypeSandboxOption:true]
GGLInstanceID.sharedInstance().tokenWithAuthorizedEntity(gcmSenderID,
scope: kGGLInstanceIDScopeGCM, options: gcmRegistrationOptions, handler: gcmRegistrationHandler)
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
print("-- Failed to get deviceToken: \(error.localizedDescription)")
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
application.applicationIconBadgeNumber += 1
print(userInfo)
let apsInfo = userInfo["aps"] as! NSDictionary
var alertMessage = ""
print("********************** Received Notif")
if let alert = apsInfo["alert"] as? String{
alertMessage = alert
print(alertMessage)
}
else if let alert = apsInfo["alert"] as? NSDictionary {
if let body = alert["body"] as? String {
alertMessage = body
print(alertMessage)
}
else if let locKey = alert["loc-key"] as? String {
var alertBody = ""
if let locArgs = alert["loc-args"] as? [String] {
var safeArgs = ["", "", "", "", "", "", "", "", "", ""]
for var i = 0; i < locArgs.count && i < safeArgs.count; i++ {
safeArgs[i] = locArgs[i]
}
alertBody = String(format: NSLocalizedString(locKey, comment: ""),
safeArgs[0], safeArgs[1],
safeArgs[2], safeArgs[3],
safeArgs[4], safeArgs[5],
safeArgs[6], safeArgs[7],
safeArgs[8], safeArgs[9])
}
else {
alertBody = NSLocalizedString(locKey, comment: "")
}
alertMessage = alertBody
}
}
// If the application is currently on screen "Active" then we trigger a custom banner View for that notification to be shown
// Else the system will handle that and put it in the notification center
if application.applicationState == UIApplicationState.Active {
AGPushNoteView.showWithNotificationMessage(alertMessage, autoClose: true, completion: { () -> Void in
// Do nothing
})
}
else {
// if the app is inactive I try to trigger a local notification to check if the notification is received but not displayed
// or just not received because consol print doesn't work when app is inactive
var localNotification: UILocalNotification = UILocalNotification()
localNotification.alertBody = alertMessage
localNotification.fireDate = NSDate(timeIntervalSinceNow: 1)
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
}
}
func gcmRegistrationHandler(registrationToken: String!, error: NSError!) {
if (registrationToken != nil) {
self.gcmRegistrationToken = registrationToken
print("GCM Registration Token: \(registrationToken)")
let userInfo = ["registrationToken": registrationToken]
NSNotificationCenter.defaultCenter().postNotificationName(
self.gcmRegistrationKey, object: nil, userInfo: userInfo)
} else {
print("Registration to GCM failed with error: \(error.localizedDescription)")
let userInfo = ["error": error.localizedDescription]
NSNotificationCenter.defaultCenter().postNotificationName(self.gcmRegistrationKey, object: nil, userInfo: userInfo)
}
}
// MARK: - GGLInstanceIDDelegate
func onTokenRefresh() {
// A rotation of the registration tokens is happening, so the app needs to request a new token.
print("The GCM registration token needs to be changed.")
GGLInstanceID.sharedInstance().tokenWithAuthorizedEntity(gcmSenderID,
scope: kGGLInstanceIDScopeGCM, options: gcmRegistrationOptions, handler: gcmRegistrationHandler)
}
// MARK: - GCMReceiverDelegate
func willSendDataMessageWithID(messageID: String!, error: NSError!) {
if (error != nil) {
// Failed to send the message.
} else {
// Will send message, you can save the messageID to track the message
}
}
func didSendDataMessageWithID(messageID: String!) {
// Did successfully send message identified by messageID
}
// [END upstream_callbacks]
func didDeleteMessagesOnServer() {
// Some messages sent to this device were deleted on the GCM server before reception, likely
// because the TTL expired. The client should notify the app server of this, so that the app
// server can resend those messages.
}
func subscribeToTopic() {
// If the app has a registration token and is connected to GCM, proceed to subscribe to the
// topic
let subscriptionTopic = "/topics/test-global"
if(gcmRegistrationToken != nil && connectedToGCM) {
GCMPubSub.sharedInstance().subscribeWithToken(gcmRegistrationToken, topic: subscriptionTopic,
options: nil, handler: {(NSError error) -> Void in
if (error != nil) {
// Treat the "already subscribed" error more gently
if error.code == 3001 {
print("Already subscribed to \(subscriptionTopic)")
} else {
print("Subscription failed: \(error.localizedDescription)");
}
} else {
subscribedToTopic = true;
NSLog("Subscribed to \(subscriptionTopic)");
}
})
}
}
}
Do I have forgotten something in my code? or maybe it is not possible?
Any help would be appreciated.
PS: I tryed to send notification to topics "/topics/general" and it workss fine.