I have an app with push notifications and RealmDB. User himself chooses the date of notifications after that the notification date goes to the RealmDB. Every time a user receives a notification, the date of the next notification changes, and the user can see the date in Table View. Problem is...after the user has received the notification, he goes to the application through the push notification so func doesn't work. If user go to app by open it - everything is OK.
In cellForRowAt:
center.getDeliveredNotifications { (notifications) in
for notification:UNNotification in notifications {
print(notification.request.identifier)
if (notification.request.identifier == timeId){
let today = notification.date
let realNextDate = Calendar.current.date(byAdding: .day, value: timeInterval!, to: today)
let realm = try! Realm()
try! realm.write {
for plants in realm.objects(MyPlant.self).filter("id == %#", timeId as Any) {
plants.nextDate = realNextDate
plants.prevDate = today
}
}
}
}
}
The reason behind this I believe is that you are configuring Realm in the AppDelegate with
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
This is what is executed when you open the app by tapping on the App icon. In the case where you open the app by tapping the push notification, the above AppDelegate method isn't executed. Instead a method shown below is executed,
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
// unused
}
Due to this, you will have to do some configurations in this method as well for it to work when tapped on the notification.
Related
I want to schedule local notification for X minutes and take user to a specified link when actioned.
currently when the app is in foreground or inactive the delegate method UNUserNotificationCenter(didReceive: withCompletionHandler:) is called and the app works as expected (the deep link opens)
the issue I'm running into is when the notification is received when the app is suspended or background and the notification launches the application I cannot seem to capture where the link is received and cannot follow the link, from what I can see that delegate method is not called?
below is the implementation for UNUserNotificationCenter(didReceive: withCompletionHandler:)
defer {
completionHandler()
}
guard
response.actionIdentifier == UNNotificationDefaultActionIdentifier ||
response.actionIdentifier == "open-dl" else {
return
}
guard
let url = response.notification.request.content.userInfo["link-to"] as? String,
let linkTo = URL(string: url) else {
return
}
if UIApplication.shared.applicationState == .background {
UserDefaults.standard.set(linkTo, forKey: "localdeeplink")
UserDefaults.standard.synchronize()
} else {
_ = UIApplication.shared.delegate?.application?(UIApplication.shared, open: linkTo, options: [:])
}
When I attempt to read that localdeeplink entry back out of UserDefaults it's empty.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//add this function
if launchOptions != nil {
pushAction(launchOptions: launchOptions)
}
return true
}
func pushAction(launchOptions: [NSObject: AnyObject]?) {
//add code of UNUserNotificationCenter(didReceive: withCompletionHandler:) here
}
Add the code of UNUserNotificationCenter(didReceive: withCompletionHandler:) in pushAction function
When app is closed didRecive is not getting called, didFinish is called with the push payload.
Apple link go to Handling Remote Notifications at bottom
I am working on a notification app and was wondering if it is possible to detect if the app is opened from a notification action in ViewController.Swift instead of the AppDelegate.swift. How can I do this?
Krish, Whenever the app is launched the AppDelegate gets called(if the default Main class is not modified) and in the AppDelegate class you can check for the launch option in launch option delegate whether its opened through remote notification. This is the first action where you will catch the app opening event.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let remoteNotif = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? NSDictionary
if remoteNotif != nil {
let notifName = remoteNotif?["aps"] as! String
print("Notification: \(notifName )")
}
else {
print("Not remote")
}
}
Here is a complete and easy answer to that
After you handle the event on AppDelegate you can use an observer to let your ViewController of that event.
Normally you should redirect the user to a specific view, depending on the notification payload.
In my application I'm using Firebase Messaging and I'm testing to receive notification.
I'm using Postman as Rest service to configure the notification's body like:
{
"to": "/topics/test",
"priority": "high",
"notification": {
"title": "Test",
"body": "New",
"badge": "0"
},
"data": {
"foo": "bar"
}
}
Certificate is ok. I don't understand how to start programmatically a ViewController looking at the data passed..For example if data contains:
"data": {
"foo": "viewcontroller1"
}
I'd like to start ViewController1 when user clicks on the notification.
I can only print data in AppDelegate? How can I use values passed?
This is my AppDelegate.swift:
import UIKit
import Firebase
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
let notificationTypes : UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let notificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
application.registerForRemoteNotifications()
application.registerUserNotificationSettings(notificationSettings)
return true
}
// [START refresh_token]
func tokenRefreshNotification(notification: NSNotification) {
let refreshedToken = FIRInstanceID.instanceID().token()!
print("InstanceID token: \(refreshedToken)")
// Connect to FCM since connection may have failed when attempted before having a token.
connectToFcm()
}
// [START connect_to_fcm]
func connectToFcm() {
FIRMessaging.messaging().connectWithCompletion { (error) in
if (error != nil) {
print("Unable to connect with FCM. \(error)")
} else {
print("Connected to FCM.")
}
}
}
//Receive and handle messages
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// Print message ID.
print("Value for foo -> \(userInfo["foo"])")
//start viewcontroller programmatically
}
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.
}
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.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
Can someone please explain me please?
Lets handle the code in didReceiveRemoteNotification First we extract which view controller should we present:
let type = userInfo["foo"] as! String
if type == "viewcontroller1" {
// here we go to start the view controller
}
You will need to use helping method to find the top most view controller to present on top of it.
func getTopViewController()->UIViewController{
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
// topController should now be your topmost view controller
}
return UIViewController()
}
To start a ViewController you should make an identifier for that in Storyboard. lets say its also called : viewcontroller1 then :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("viewcontroller1") as! viewcontroller1
self.getTopViewController().presentViewController(vc, animated: true, completion: nil)
Note: When receiving the notification you'll need to check if the app was in background or it was in app or it was outside the app . For each one has different handling of how and when you'll need to show or present your view controller.
So i have push notifications setup and as part of the did register for push notifications i need to add the current user to the installation table. This works fine up untill there is no user signed in.
This is my code
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let types:UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let settings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: types, categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
return true
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let installation = PFInstallation.currentInstallation()
installation.setObject(PFUser.currentUser()!, forKey: "user")
installation.setDeviceTokenFromData(deviceToken)
installation.saveInBackground()
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
if error.code == 3010 {
print("Push notifications are not supported in the iOS Simulator.")
} else {
print("application:didFailToRegisterForRemoteNotificationsWithError: %#", error)
}
}
I need a way of registering the user with the installation table without it crashing if there is no user on, i would do a simple check to see if there is a user signed in and then run the code if there is a user, but then if someone sent a notification to them they wouldnt get it because their PFUser.currentUser() has not been added. Thanks in advance
Have you tried looking into Anonymous users (http://blog.parse.com/announcements/protect-user-data-with-new-parse-features/)? This allows you to create a PFUser for a logged out user.
This way, you can still save PFUser reference on the current installation via a PFUser.currentUser() call, but the user does not have to sign up.
I was already able to set Interactive LOCAL notifications, but the Remote notifications aren't working. I'm using Parse.com to send the JSON
My AppDelegate.Swift looks like this:
//
// AppDelegate.swift
// SwifferApp
//
// Created by Training on 29/06/14.
// Copyright (c) 2014 Training. All rights reserved.
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
UINavigationBar.appearance().barTintColor = UIColor.orangeColor()
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
Parse.setApplicationId("eUEC7O4Jad0Kt3orqRouU0OJhkGuE20n4uSfrLYE", clientKey: "WypmaQ8XyqH26AeWIANttqwUjRJR4CIM55ioXvez")
let notificationTypes:UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
let notificationSettings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
return true
}
func application(application: UIApplication!, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings!) {
UIApplication.sharedApplication().registerForRemoteNotifications()
}
func application(application: UIApplication!, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData!) {
let currentInstallation:PFInstallation = PFInstallation.currentInstallation()
currentInstallation.setDeviceTokenFromData(deviceToken)
currentInstallation.saveInBackground()
}
func application(application: UIApplication!, didFailToRegisterForRemoteNotificationsWithError error: NSError!) {
println(error.localizedDescription)
}
func application(application: UIApplication!, didReceiveRemoteNotification userInfo:NSDictionary!) {
var notification:NSDictionary = userInfo.objectForKey("aps") as NSDictionary
if notification.objectForKey("content-available"){
if notification.objectForKey("content-available").isEqualToNumber(1){
NSNotificationCenter.defaultCenter().postNotificationName("reloadTimeline", object: nil)
}
}else{
PFPush.handlePush(userInfo)
}
}
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.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active 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.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
on Parse, I'm setting the Push payload like this:
{
"alert": "Tune in for the World Series, tonight at 8pm EDT",
"badge": "Increment",
"sound": "chime",
"category": "FIRST_CATEGORY"
}
and I receive the push, but not with the custom buttons I've set.
I'm not sure if my problem is the same to yours (Make sure your problem is not due to Parse). Just post my solution here in case anyone else would encounter the same issue.
My problem is in the notification category.
Make sure you have set the category when registering the notification settings (I'm using Objective-C, no much difference):
UIMutableUserNotificationCategory *notificationCategory = [[UIMutableUserNotificationCategory alloc] init];
notificationCategory.identifier = #"CallNotificationCategory";
[notificationCategory setActions:#[declineAction, answerAction] forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [[NSSet alloc] initWithObjects:notificationCategory, nil];
And, when you send remote notification, make sure you have "category" in the payload and the value is the same as you defined in the client. In my case it's something like:
{
"alert": "Tune in for the World Series, tonight at 8pm EDT",
"badge": "Increment",
"sound": "chime",
"category": "CallNotificationCategory"
}
This is in case if anyone comes across this problem while using Firebase Remote Notifications.
Just ask the backend developer to send:
"notification" : {
"title" : YOUR_TITLE,
"body" : YOUR_BODY,
"click_action" : YOUR_CATEGORY_NAME
}
You need to pass categories while registering for APNS.
Look at my sample :
var replyAction : UIMutableUserNotificationAction = UIMutableUserNotificationAction()
replyAction.identifier = "REPLY_ACTION"
replyAction.title = "Yes, I need!"
replyAction.activationMode = UIUserNotificationActivationMode.Background
replyAction.authenticationRequired = false
var replyCategory : UIMutableUserNotificationCategory = UIMutableUserNotificationCategory()
replyCategory.identifier = "REPLY_CATEGORY"
let replyActions:NSArray = [replyAction]
replyCategory.setActions(replyActions, forContext: UIUserNotificationActionContext.Default)
replyCategory.setActions(replyActions, forContext: UIUserNotificationActionContext.Minimal)
let categories = NSSet(object: replyCategory)
let settings : UIUserNotificationType = UIUserNotificationType.Sound | UIUserNotificationType.Alert | UIUserNotificationType.Badge
UIApplication.sharedApplication().registerUserNotificationSettings(UIUserNotificationSettings(forTypes: settings, categories: categories))
UIApplication.sharedApplication().registerForRemoteNotifications()
This worked for me to get interactive push notifications displaying and working in Swift with Parse. Note that you need to create a UIMutableNotificationAction for each interactive button you want to create. Source below goes into much more detail of configuring options for buttons.
In your app delegate file:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let notificationTypes:UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
var notificationActionAccept :UIMutableUserNotificationAction = UIMutableUserNotificationAction()
notificationActionAccept.identifier = "ACCEPT_IDENTIFIER"
notificationActionAccept.title = "Accept"
notificationActionAccept.destructive = true
notificationActionAccept.authenticationRequired = false
notificationActionAccept.activationMode = UIUserNotificationActivationMode.Background
var notificationCategory:UIMutableUserNotificationCategory = UIMutableUserNotificationCategory()
notificationCategory.identifier = "CallNotificationCategory"
notificationCategory .setActions([notificationActionAccept], forContext: UIUserNotificationActionContext.Default)
let notificationSettings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: NSSet(array:[notificationCategory]))
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
return true
}
And in Parse Cloud when you send the push, you would match the category to the UIMutableUserNotificationCategory like #Xialin mentioned above.
Looks like you weren't setting categories or at least not setting them until after you had registered the UINotificationSettings - you need to set them before or it won't work.
I got most of this info at the link below. It goes into more detail if needed. Hope this helps:
http://thecodeninja.tumblr.com/post/90742435155/notifications-in-ios-8-part-2-using-swift-what-is