I want to push UIViewController when notification is clicked and app is closed
following is my code in didReceiveRemoteNotification
if application.applicationState == .inactive || application.applicationState == .background {
DeeplinkHandler.handleNotification(userNotification: userNotification)
completionHandler(UIBackgroundFetchResult.newData)
}
following is code to handle notification deep link
class func handleNotification(userNotification : UserNotification?){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var navigationVC = UINavigationController()
if let tabBarVC = appDelegate.window?.rootViewController as? UITabBarController {
if let navVC = tabBarVC.viewControllers?[tabBarVC.selectedIndex] as? UINavigationController {
navigationVC = navVC
}
else {
tabBarVC.selectedIndex = 0
navigationVC = tabBarVC.viewControllers?[0] as! UINavigationController
}
}
// let navigationVC = appDelegate.window?.rootViewController as! UINavigationController
switch userNotification?.type ?? "" {
case DeeplinkHandler.NOTIF_TYPE_WEBVIEW:
let appWebView = AppStrings.appStoryBoard.instantiateViewController(withIdentifier: "webPageViewControllerID") as! WebPageViewController
appWebView.url = userNotification?.url ?? ""
navigationVC.pushViewController(appWebView, animated: true)
//case DeeplinkHandler.NOTIF_TYPE_PAGE_ID:
//case DeeplinkHandler.NOTIF_TYPE_FLIGHT_STATUS:
default:
let appWebView = AppStrings.appStoryBoard.instantiateViewController(withIdentifier: "notificationViewControllerID") as! NotificationViewController
//appWebView.url = userNotification?.url ?? ""
navigationVC.pushViewController(appWebView, animated: true)
}
}
But notification is click is causing a crash when clicked on notification when app is closed.
How to handle this?
tried following code in didFinishLaunchingWithOptions
var notification: [AnyHashable: Any]? = (launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] as? [AnyHashable: Any])
if let notification = notification {
print("app received notification from remote\(notification)")
var userNotification : UserNotification?
if notification is [String : Any] {
userNotification = createNSaveNotification(notification)
DeeplinkHandler.handleNotification(userNotification: userNotification)
}
}
else {
print("app did not receive notification")
}
this is also crashing app on notification click when app is closed
when app close, you have to check notification in method
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions;
and check data in launchOptions
Related
I looked on SO but I wasn't able to find any question that discusses when you receive a push notification how can you then open a specific view controller.
For example if you are creating an app like WhatsApp and you receive two different push notifications ie messages from two different users how would you direct from the app delegate to the respective viewController?
As far as I know in the userinfo dictionary that the appDelegate gives you you can give an ID to a specific viewController but I don't know how to give any a tribute to a specific view controller so that then you could again direct to that viewController.
Kindly include a code snippet with your answer
**** Swift or Objective-C answers are both acceptable ****
You can detect if the app opened from the notification with this code in app delegate. You will need to set the initial view controller when the application state is UIApplicationStateInactive before the app has become active. You can perform any logic there to decide which view controller should be opened and what content should be shown in that view controller.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
if(application.applicationState == UIApplicationStateActive) {
//app is currently active, can update badges count here
} else if(application.applicationState == UIApplicationStateBackground){
//app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here
} else if(application.applicationState == UIApplicationStateInactive){
//app is transitioning from background to foreground (user taps notification), do what you need when user taps here
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *viewController = // determine the initial view controller here and instantiate it with [storyboard instantiateViewControllerWithIdentifier:<storyboard id>];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
}
}
Here is the Swift 3 Version with switch/case instead of if/else
open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
switch application.applicationState {
case .active:
print("do stuff in case App is active")
case .background:
print("do stuff in case App is in background")
case .inactive:
print("do stuff in case App is inactive")
}
}
//This method is called when user tap on the notification
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("user clicked on the notification")
let userInfo = response.notification.request.content.userInfo
print(userInfo)
//check your response and navigate to specific view controller
moveToNextViewController()
}
func moveToNextViewController() {
//Add code for present or push view controller
let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier:"ViewController") as! ViewController
self.navigationController?.pushViewController(vc, animated: true)
}
This method is called when user tap on the notification. implement in appdelegate
var navigationC: UINavigationController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if USERDEFAULT.value(forKey: "isLogin") != nil {
let tabbarvc = MainStoryboard.instantiateViewController(withIdentifier: "TabBarVC") as! TabBarVC
self.navigationC = UINavigationController(rootViewController: tabbarvc)
}else {
let loginvc = MainStoryboard.instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
self.navigationC = UINavigationController(rootViewController: loginvc)
}
self.navigationC?.setNavigationBarHidden(true, animated: false)
self.window?.clipsToBounds = true
self.window?.rootViewController = navigationC
self.window?.makeKeyAndVisible()
return true
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
print("-------------\nUser info in notification -> \(userInfo)\n-------------")
let dict = userInfo["aps"] as? NSDictionary ?? [:]
if USERDEFAULT.value(forKey:"isLogin") != nil{
let type = dict["type"] as? Int ?? 0
switch type {
case 0:
break
case 1:
for vc in self.navigationC!.viewControllers {
if vc is TabBarVc {
let exam = dict["exam"] as? String ?? ""
if exam == ""{
let TestVC = MainStoryboard.instantiateViewController(withIdentifier: "TestVC") as! TestVC
TestVC.chapterId = dict["chapter_id"] as? String ?? ""
TestVC.strSubTitle = dict["chapter"] as? String ?? ""
self.navigationC?.isNavigationBarHidden = true
self.navigationC?.pushViewController(TestVC, animated: true)
}else if exam != ""{
let ExamTestVC = MainStoryboard.instantiateViewController(withIdentifier: "ExamTestVC") as! ExamTestVC
ExamTestVC.examId = dict["exam_id"] as? String ?? ""
ExamTestVC.strSubTitle = dict["exam"] as? String ?? ""
self.navigationC?.isNavigationBarHidden = true
self.navigationC?.pushViewController(ExamTestVC, animated: true)
}
return;
}
}
break
case 2:
for vc in self.navigationC!.viewControllers {
if vc is TabBarVc {
let VideoListVC = MainStoryboard.instantiateViewController(withIdentifier: "VideoListVC") as! VideoListVC
VideoListVC.chapterId = dict["chapter_id"] as? String ?? ""
VideoListVC.video_id = dict["video_id"] as? String ?? ""
VideoListVC.strSubTitle = dict["chapter"] as? String ?? ""
VideoListVC.questionsCount = dict["question_count"] as? Int ?? 0
VideoListVC.testCount = dict["test_count"] as? Int ?? 0
self.navigationC?.isNavigationBarHidden = true
self.navigationC?.pushViewController(VideoListVC, animated: true)
return;
}
}
break
case 3:
break
default: break
}
}else{
let loginVC = SignupiPadStoryboard.instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
SignupStoryboard = SignupiPadStoryboard
self.navigationC = UINavigationController(rootViewController: loginVC)
}
}
OR you can direct open view controller like whatsapp chat..etc.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void)
{
let userInfo = notification.request.content.userInfo
let dictAps = userInfo["aps"] as! NSDictionary
print(dictAps)
let notification_type = dictAps["notification_type"] as? Int ?? 0
if notification_type == 6{
if isChatActive == false{
completionHandler([.alert, .badge, .sound])
}else{
if USERDEFAULT.value(forKey:"isLogin") != nil{
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ReloadChatMessages"), object: nil, userInfo: (dictAps as! [AnyHashable : Any]))
}else{
completionHandler([.alert, .badge, .sound])
}
}
}else{
completionHandler([.alert, .badge, .sound])
}
}
I use firebase to send a message to ios device, I debug, I have received data payload in Appdelegate in func
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
I want to do how to open different view controllers based on that data, which means that when I click on the message, I will go to the corresponding view controllers.
I used the code below in Appdelegate but failed
let sb = UIStoryboard(name: "Main", bundle: nil)
let otherVC = sb.instantiateViewController(withIdentifier: "UserNotLoginViewController") as! UserNotLoginViewController
self.window?.rootViewController = otherVC;
when you recive notification in didReceiveRemoteNotification delegate then call the function to pushview to nextviewcontroller.
func application(_ application: UIApplication, didReceiveRemoteNotification
data: [AnyHashable : Any]) {
let state: UIApplicationState = UIApplication.shared.applicationState
if state == .background {
// background
pushToPage(data: data)
}
}
func pushToPage(data:[AnyHashable : Any]){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let window = appDelegate.window {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController =
storyBoard.instantiateViewController(withIdentifier: "NextViewController") as! NextViewController
window.rootViewController = nextViewController
}
}
Declare this function in your appDelegate and then use it to change rootViewController.
public func makeRootVC(vcName : String) {
let vc = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
let navigation = UINavigationController(rootViewController: vc)
navigation.navigationBar.isHidden = true
self.window?.rootViewController = navigation
}
usage:
self.makeRootVC("YourViewControllerStoryboardID")
Here it is
func handlePushNotification(userInfo: [String: Any]) {
guard let notificationType = userInfo["nt"] as? String else {
return
}
if notificationType.toInt() == 1 {
self.navigateToViewController1()
} else if notificationType.toInt() == 2 {
self.navigateToViewController2()
}
}
And for navigation, you can use this below function
fileprivate func navigateToViewController1() {
if let rootViewController = self.window?.rootViewController as? UINavigationController {
if let _ = rootViewController.topViewController as? VC1 {
let vc = AppStoryboard.Main.viewController(viewControllerClass: VC3.self)
rootViewController.pushViewController(vc, animated: true)
}
}
}
fileprivate func navigateToViewController2() {
if let rootViewController = self.window?.rootViewController as? UINavigationController {
if let homeVC = rootViewController.topViewController as? VC2 {
}
}
}
Still, you face any issue so please let me know.
I got stuck specific view controller is not move when I tap on push notification alert when application is not open stage totally.
Here is my code:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
/*
fetch and add push notification data
*/
goAnotherVC()
}
func goAnotherVC() {
if (application.applicationState == UIApplicationState.active) {
/* active stage is working */
} else if (application.applicationState == UIApplicationState.inactive || application.applicationState == UIApplicationState.background) {
if (type == "1" || type == "2") {
let storyboard: UIStoryboard = UIStoryboard(name: "MyAppointments", bundle: nil)
let apptVC = storyboard.instantiateViewController(withIdentifier: "NotificationDetailViewController") as! NotificationDetailViewController
let navigationController = UINavigationController.init(rootViewController: apptVC)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
} else if (type == "3") {
let storyboard: UIStoryboard = UIStoryboard(name: "MyAppointments", bundle: nil)
let apptVC = storyboard.instantiateViewController(withIdentifier: "NotificationDetailViewController") as! NotificationDetailViewController
let navigationController = UINavigationController.init(rootViewController: apptVC)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
} else if (type == "4") {
let storyboard: UIStoryboard = UIStoryboard(name: "Enquiry", bundle: nil)
let enqVC = storyboard.instantiateViewController(withIdentifier: "EnquiryDetailViewController") as! EnquiryDetailViewController
let navigationController = UINavigationController.init(rootViewController: enqVC)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
}
}
}
I can get notification and tap to move specific VC when application is active. Please help me what I am missing.
Swift 5
Simply, implement the following function which will be called when the user clicked on the notification.
In AppDelegate:
// This method is called when user clicked on the notification
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void)
{
// Do whatever you want when the user tapped on a notification
// If you are waiting for specific data from the notification
// (e.g., key: "target" and associated with "value"),
// you can capture it as follows then do the navigation:
// You may print `userInfo` dictionary, to see all data received with the notification.
let userInfo = response.notification.request.content.userInfo
if let targetValue = userInfo["target"] as? String, targetValue == "value"
{
coordinateToSomeVC()
}
completionHandler()
}
private func coordinateToSomeVC()
{
guard let window = UIApplication.shared.keyWindow else { return }
let storyboard = UIStoryboard(name: "YourStoryboard", bundle: nil)
let yourVC = storyboard.instantiateViewController(identifier: "yourVCIdentifier")
let navController = UINavigationController(rootViewController: yourVC)
navController.modalPresentationStyle = .fullScreen
// you can assign your vc directly or push it in navigation stack as follows:
window.rootViewController = navController
window.makeKeyAndVisible()
}
Note:
If you navigate to a specific controller based on the notification, you should care about how you will navigate back from this controller because there are no controllers in your stack right now. You must instantiate the controller you will back to. In my case, when the user clicked back, I instantiate the home controller and make it the app root again as the app will normally start.
When you app is in closed state you should check for launch option in
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { }
and call your API.
Example:
if let option = launchOptions {
let info = option[UIApplicationLaunchOptionsKey.remoteNotification]
if (info != nil) {
goAnotherVC()
}
}
Swift 5, iOS 13 -
Since iOS 13 "window" is available in SceneDelegate. But the didReceiveNotification method is still present in AppDelegate.
So you have to first access the window from SceneDelegate
let window = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window
Now You can set the rootViewController property of the window
window.rootViewController = viewControllerObject
window.makeKeyAndVisible()
I'm using Firebase notification in order to send and receive RemoteNotification.
When I receive a notification, I receive also an ID that let me to identify a specifically "post".
Now, when I click on notification, I need that app to open a viewController passing it the received ID, in order to start a method (in viewDidLoad()) that load "post" informations.
this is the code of notification:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// Print message ID.
print("Message ID: \(userInfo["gcm.message_id"]!)")
let aps = userInfo["aps"] as! NSDictionary
let body_notifica = aps["alert"]! as! NSDictionary
let titolo_notifica = body_notifica["title"]! as! String
let testo_notifica = body_notifica["body"]! as! String
let id_lavoro = userInfo["id_lavoro"]! //THIS IS THE ID I NEED TO PASS TO VIEW CONTROLLER
if application.applicationState == UIApplicationState.inactive || application.applicationState == UIApplicationState.background{
let viewController = self.window!.rootViewController!.storyboard!.instantiateViewController(withIdentifier: "annuncio_view_controler")
viewController.performSegue(withIdentifier: "a", sender: <#T##Any?#>)
}
let banner = Banner(title: titolo_notifica, subtitle: testo_notifica, image: UIImage(named: "Info"), backgroundColor: UIColor(red:0.0/255.0, green:0.0/255.0, blue:0.0/255.0, alpha:0.5))
banner.dismissesOnTap = true
banner.show(duration: 3.0)
print(userInfo)
}
thank you guys!
Thanks to #Vadim Kozak
if application.applicationState == UIApplicationState.inactive || application.applicationState == UIApplicationState.background{
guard let rootViewController = self.window?.rootViewController as? UITabBarController else {
return
}
// select second tab
rootViewController.selectedIndex = 1
guard let navigationController = rootViewController.selectedViewController as? UINavigationController else {
return
}
let viewController = self.window!.rootViewController!.storyboard!.instantiateViewController(withIdentifier: "annuncio_view_controler") as! LavoroViewController
viewController.id_lavoro = id_lavoro!
rootViewController.present(viewController, animated: true, completion: nil)
}
you can handle this by using this example of code
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if ( application.applicationState == .inactive || application.applicationState == .background){
//handle tapping on push notification and here you can open needed controller
}
}
to know exactly which controller you need to open need to see on controllers hierarchy
in my case its
`guard let rootViewController = self.window?.rootViewController as? TabBarVC else {
return
}
// select second tab
rootViewController.selectedIndex = 1
guard let navigationController = rootViewController.selectedViewController as? UINavigationController else {
return
}
`
EDIT: I tried with this, but nothing.
if application.applicationState == UIApplicationState.inactive || application.applicationState == UIApplicationState.background{
let viewController = self.window!.rootViewController!.storyboard!.instantiateViewController(withIdentifier: "home_view_controller") as! HomeViewController
viewController.toPassId = id_lavoro!
viewController.performSegue(withIdentifier: "segue_annuncio_profilo", sender: self)
}
How can I check hierarchy? My view is not a view of TabBar, it can be open from a lot of view (that stay also in TabBar)
I'm trying to show a specific viewcontroller when I receive a remote push notification. I've added all of my code into the method didReceiveRemoteNotification:
func application(application: UIApplication, didReceiveRemoteNotification userinfo: [NSObject: AnyObject])
I've added following code:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let code = (userInfo["aps"] as! [String: AnyObject])
// Call to retrieve blog
if let blog = code["b"] as? NSNumber {
let blogId = blog as! Int
// Show blog from notification
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main",bundle: nil)
var controller = mainStoryboard.instantiateViewControllerWithIdentifier("blogCtrl") as! BlogController
controller.blogId = blogId
var rootController = mainStoryboard.instantiateViewControllerWithIdentifier("navCtrl1") as! UINavigationController
self.window?.rootViewController = rootController
rootController.pushViewController(controller, animated: true)
self.window?.makeKeyAndVisible()
}
if let tonic = code["t"] as? NSNumber {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main",bundle: nil)
var controller = mainStoryboard.instantiateViewControllerWithIdentifier("tonicDetail") as! TonicDetailController
controller.tonicId = tonic as! Int
var rootController = mainStoryboard.instantiateViewControllerWithIdentifier("navCtrl1") as! UINavigationController
self.window?.rootViewController = rootController
rootController.pushViewController(controller, animated: true)
self.window?.makeKeyAndVisible()
}
if let gin = code["g"] as? NSNumber {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main",bundle: nil)
var controller = mainStoryboard.instantiateViewControllerWithIdentifier("GinDetail") as! GinDetailController
controller.ginId = gin as! Int
var rootController = mainStoryboard.instantiateViewControllerWithIdentifier("navCtrl1") as! UINavigationController
self.window?.rootViewController = rootController
rootController.pushViewController(controller, animated: true)
self.window?.makeKeyAndVisible()
}
}
When the app is in background everything works but when the app is quited, and I receive a remote notification it only starts up the application.
Is there a method which can be called if the application was quited before?
When your app launches after quitting the app, in the meanwhile you got remote notification, then didFinishLaunchingWithOptions is first method in AppDelegate which fires on launching the app, check whether you have received any notification or not and perform your operation accordingly.
You must look for this method in app delegate:-
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
Now check whether your app has received any push notification or not
UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (notification) {
NSLog(#"app received a notification %#",notification);
[self application:application didReceiveRemoteNotification:(NSDictionary*)notification];
}else{
NSLog(#"app did not receive a notification");
}