Posting NSNotification when receiving Push crash - ios

When I receive a push notification, I postNotificaionName via NSNotificationCenter (from the AppDelegate method below):
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
playSoundWithUserInfo(userInfo)
NSNotificationCenter.defaultCenter().postNotificationName(kNotifPushReceived, object: nil)
}
I am making one of my view-controllers an observer for this notification:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(retrieveUsersAndSetData(_:)), name: kNotifPushReceived, object: nil
}
func retrieveUsersAndSetData(completed : (() -> Void)?) {
Friendship.retrieveFriendshipsForUser(backendless.userService.currentUser, includeGroups: false) { (friendships, fault) -> Void in
guard let friendships = friendships else { return }
self.friendships = friendships
self.tableView.reloadData()
}
}
Most of the time I receive a push notification, I receive a crash in the appDelegate where postNotificationName() is called:
The crash reads:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x10070)
Now most answers I've read that seem similar to this problem suggest that an object who was made an observer was never properly released, however I do not believe that is the case here because this is currently the only view-controller in the app, and I am implementing the following from the view-controller who is the observer of the notification:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
All in all, I cannot figure out why this crash occurs most of the time when I receive the push notification in the app delegate and then post my NSNotification.

The handler method 'fetchCompletionHandler' expects by you to call the completionHandler at the end of your processing:
completionHandler(UIBackgroundFetchResult.NewData)
Please read the documentation of this method, there is more to consider then just that.

Related

Open app in specific view when user taps on push notification with iOS 13 Swift 5

My app allows remote push notifications to a user. How do I enable it to be opened in a specific view controller when the user taps on the push notification? I want the app to open and navigate to a specific view controller depending on the push notification received.
you have to check when your app is on close state using launch option in " func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool{ :
if let option = launchOptions {
let info = option[UIApplication.LaunchOptionsKey.remoteNotification]
if (info != nil) {
self.goAnotherVC()
}
}
and in view did load in your landing VC set the observer
NotificationCenter.default.addObserver(self, selector: #selector(self.goToVc(notification:)), name:NSNotification.Name(rawValue:identifier), object: nil)
Selector Method :
#objc func goToVc(notification:Notification) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier:"landingVC") as! landingVC
self.navigationController?.pushViewController(vc, animated: true)
}
In app delegate :
func application(_ application: UIApplication,didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
NotificationCenter.default.post(name:NSNotification.Name(identifier), object: userInfo)
}
This answer is for iOS 13+
In AppDelegae :
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
NotificationCenter.default.post(name: NSNotification.Name(identifier), object: nil)
completionHandler()
}
In viewDidLoad of Landing ViewController :
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.showChatController(notification:)), name:NSNotification.Name(rawValue: "noti"), object: nil)
}
In your landing viewController add following selector method :
#objc
func showChatController(notification: Notification) {
print("DEBUG: show chat controller here..")
}
You can trigger a notification once you receive and the user clicks on the notification.
Along with the notification, you can pass the value which will later used to identify to which view controller you need to navigate.
Create a class which will be responsible for all the push notification navigation handling. You can name it like PushNotificationHandler. Let the PushNotificationHandler handler take care of all the logic to navigate to the view controllers.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
}
Get the value from the deep link using the above delegate method
Trigger a notification with the value
Handle the notification inside PushNotificationHandler class

How to know when app received notification and when user clicked on notification in iOS

I know there has been a lot written about this topic but I just can't find the right answer.
Is there a way how to know when the user received remote notification and when the user clicked on one on iOS 8.
I would like to know this because when I receive it I want to save it and when user clicks on it I want to open some view.
I have found this answer https://stackoverflow.com/a/16393957/1241217 but the problem is when user is in the app and opens notification center and clicks on one, the app is not inactive and not in the background.
I also found this answer https://stackoverflow.com/a/12937568/1241217 but I know that this is ran only when the app is killed and started from new.
I also don't want to do this https://stackoverflow.com/a/32079458/1241217 since I need to detect when I received notification.
So is there a way how to know if the user only clicked on notification. As far as I understood it has to be done in didReceiveRemoteNotification but I don't know how to separate between them. And I need an answer for before iOS 10 because the app target is iOS 8.
MY SOLUTION:
So as I wrote in the comment of Shabbir Ahmad answer my solution was to remember date when the application did become active and the date when the notification was received. If the difference between this dates was a second or less I accepted that as the user clicked on the notification.
You have to implement UNUserNotificationCenterDelegate and its method
userNotificationCenter(_:willPresent:withCompletionHandler:) and userNotificationCenter(_:didReceive:withCompletionHandler:) which gets called when a user taps a notification. In willPresent: you have to call the completionHandler with an option that would indicate what should happen when a notification arrives while the app is in foreground.
Registering such a delegate is easy:
UNUserNotificationCenter.current().delegate = self
So e.g.:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(UNNotificationPresentationOptions.alert)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let userInfo = userInfo as? [String: Any] {
// TODO: implement your logic
// just don't forget to dispatch UI stuff on main thread
}
}
You can implement that delegate by AppDelegate, but also by any NSObject, I would go with the latter to keep AppDelegate as clean as possible.
P.S.: Of course, this assumes that you have been granted permissions by the user (UNUserNotificationCenter.current().requestAuthorization(options:completionHandler:)) and you are registered to accept notifications (UIApplication.shared.registerForRemoteNotifications()).
Read more in Scheduling and Handling Local Notifications, section Responding to the Delivery of Notifications - while the section is about local notifications, it is exactly the same for the remote ones (they are handled both by the same delegate).
when you click on notification in background mode before ios 10 and when you are in foreground,in both cases your below method will call,
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
So you can differentiate the behaviour,
First of all you assign a boolean variable in AppDelegate class like this:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var isUserTapOnNotification = false
after that make true isUserTapOnNotification in
func applicationWillEnterForeground(_ application: UIApplication) {
isUserTapOnNotification = tue
}
because when you tap on notification bar, your app will came in foreground and applicationWillEnterForeground will call first,
after that your didReceiveRemoteNotification will call:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if #available(iOS 10.0, *) {
//do nothing
}else { //<ios 10
if isUserTapOnNotification == true {//when app is in background and user tap on notification bar
//do action whatever you want
} else { //when user is in foreground and notification came,
//before ios10,notification bar not display in foreground mode,So you can show popup by using userInfo
}
}
after that applicationDidBecomeActive will call and you reset isUserTapOnNotification to false like this:
func applicationDidBecomeActive(_ application: UIApplication) {
isUserTapOnNotification = false
}
I hope this answer will help you.

App goes stuck when click on firebase push notification in Killed state

I was handling push notification data and then after call API based in push notification custom data. This will work fine when app is in Active and background state.
But when app is not running and then click on notification, I was able to get custom data from custom date But, API is not called and app getting stuck.
I checked in iOS 10 and 11, but not working
Handling push is like this.
AppDelegate
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
redirectToScreen(notificaiton: userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
//Notify through Notification center
func redirectToScreen(notificaiton: [AnyHashable: Any]) {
let dictPayload = notificaiton as NSDictionary
print(dictPayload)
if let type = dictPayload.value(forKey: "type") as? String {
var dict = ["type" : type]
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "handlePush"), object: dict)
}
}
HomeViewController
//Notification Observer goes here and call API
let spinner = showLoader(view: self.view) // App goes stuck here and loaded process continuously, response is not getting
Alamofire.request(kURl, method: .post, parameters: param, encoding: URLEncoding.httpBody, headers: nil).authenticate(user: R.string.keys.basicAuthUsername(), password: R.string.keys.basicAuthPassword()).responseSwiftyJSON(completionHandler: {
spinner.dismissLoader()
})
Swift 4.0
As per #TarasChernyshenko statement, I put Post Notification code in DispatchQueue.main.async { } block and now it works fine.
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "handlePush"), object: dict)
}
As mentioned by #TarasChernyshenko,
When you get callback from Notification Observer via didRecieveNotification(_:), app remains in background. Any UI updates such as :
let spinner = showLoader(view: self.view)
Must keep in Main thread queue as given below :
DispatchQueue.main.async {
let spinner = showLoader(view: self.view)
//other ui stuffs...
}

How to handle push notification when app is not running/app is terminated

I am using firebase notification. When applicationState is background it's working fine. But when app is not running / terminated func getPushNotiData(_ notification: NSNotification) is not being call
Implementing NotificationCenter in appDelegate file to handle notification in dashView
func application(_ application: UIApplication, didReceiveRemoteNotification
userInfo: [AnyHashable : Any]) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue:
"getPushNotificationData"), object: nil, userInfo: userInfo)
}
And if app is not running / terminated Implementing NotificationCenter in didFinishLaunchingWithOptions delegate method
if (launchOptions != nil)
{
let userInfo = launchOptions![.remoteNotification] as? [AnyHashable: Any]
if userInfo != nil {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "getPushNotificationData"), object: nil, userInfo: userInfo)
}
}
Handling NotificationCenter in dashView
override func viewDidAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(self.getPushNotiData(_:)), name: NSNotification.Name(rawValue: "getPushNotificationData"), object: nil)
}
#objc func getPushNotiData(_ notification: NSNotification){
if let url = notification.userInfo!["url"] as? String {
let destination = storyboard?.instantiateViewController(withIdentifier: "NotificationDlsViewController") as! NotificationDlsViewController
destination.notiUrl = url
self.navigationController?.pushViewController(destination, animated: true)
}
}
If the app is not running (killed state) , you can't execute any methods or code , only if the user clicks the push notification you can receive object of it in didFinishLaunchingWithOptions launchingOptions and handle it ....
It's probabile that when you check launchOptions in your didFinishLaunchingWithOptions your ViewController is not loaded yet so viewDidAppear has not been called and the ViewController is not registered in the Notification Center. Try to put some print statement and check the order of the calls when the app is launched from the notification.

MFMessageComposeViewControllerDelegate not being called

I am trying to implement a class that will present a MFMessageComposeViewController from the AppDelegate. The class declaration looks like this:
import UIKit
import MessageUI
class MyClass: NSObject, MFMessageComposeViewControllerDelegate {
func sendAMessage() {
// message view controller
let messageVC = MFMessageComposeViewController()
messageVC.body = "Oh hai!"
messageVC.recipients = ["8675309"]
// set the delegate
messageVC.messageComposeDelegate = self
// present the message view controller
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(messageVC, animated: true, completion: nil)
}
// delegate implementation
func messageComposeViewController(controller: MFMessageComposeViewController!, didFinishWithResult result: MessageComposeResult) {
switch result.value {
case MessageComposeResultCancelled.value:
controller.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
case MessageComposeResultFailed.value:
controller.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
case MessageComposeResultSent.value:
controller.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
default:
break
}
}
}
In my AppDelegate I am creating and calling an instance of MyClass after receiving a push notification like this:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// instance of class
let handler = MyClass()
// call method
handler.sendAMessage()
}
Everything works fine at first--the message view controller appears and is responsive with no errors, but whenever the send or cancel button is pressed, the message view controller does not dismiss, the screen becomes unresponsive, the delegate is not called, and I get a BAD_ACCESS error.
If I put the MFMessageComposeViewControllerDelegate in the AppDelegate and set the messageVC. messageVC.messageComposeDelegate = UIApplication.sharedApplication().delegate as! MFMessageComposeViewControllerDelegate, then everything works fine and the controller dismisses as expected.
Why is the MFMessageComposeViewControllerDelegate not called when it lives in the MyClass object? Thanks for reading and helping!
It's crashing because your handler object is getting released and deallocated right after the call to handler.sendMessage(), and then a delegate callback is attempted on that now-deallocated object when you try to send or hit cancel. The object is getting released and deallocated because nothing is holding a strong reference to it anymore at the end of application:didReceiveRemoteNotification:.
Since you are creating this object in your app delegate, I would suggest making a property in your app delegate to hold onto this object:
var handler: MyClass?
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// instance of class
handler = MyClass()
// call method
handler?.sendAMessage()
}

Resources