I am attempting to launch a specific viewController in my CustomTabBarController after receiving remote notifications. But somehow, the app always crashes when the app is killed.
Meaning to say, kill the app -> received push notifications -> tap the notifications -> app launches and crashed. This also happens when I tap the notification from the lockscreen.
I am able to execute when the app is in the background, but not when the app is killed. My code so far:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
...
if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? NSDictionary {
guard let rootViewController = self.window?.rootViewController as? CustomTabBarController else {
return true
}
rootViewController.selectedIndex = 1
}
return true
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .background || application.applicationState == .inactive {
guard let rootViewController = self.window?.rootViewController as? CustomTabBarController else {
return
}
rootViewController.selectedIndex = 1
}
}
I've followed this post to check the launchOptions, but it still crash. What can I try next?
Comment these 2 lines
// window = UIWindow(frame: UIScreen.main.bounds)
// window?.makeKeyAndVisible()
as overriding the window property destroys the initialization from storyboard (makes rootVC nil) and before return true window must have one
Related
I'm implementing 3D touch quick actions in my app and I have the following problem:
When the app is already running and so the quick action is initiated through perform Action For Shortcut Item, it works perfectly fine. However, when the app is killed and then launched through a quick action (so didFinishLaunchingWithOptions) it does not take me to the desired view controller, but rather to the home screen of the app.
Here is my code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//... other stuff I'm doing
if let shortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
shortcutItemToProcess = shortcutItem
}
return true
NOTE: I've read previous SO answers where they said that I need to return false in didFinishLaunchingWithOptions when the app was launched through a quick action, so that performAction won't get called. I need to always return true however in my didFinishLaunching method because of the other things I'm handling there. I tried however to return false just to see if that causes the problem and the app still behaved the same way.
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
shortcutItemToProcess = shortcutItem
}
Here is how I present the view controller:
func applicationDidBecomeActive(_ application: UIApplication) {
if let shortcutItem = shortcutItemToProcess {
if shortcutItem.type == "com.myName.MyApp.myQuickAction" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myViewController = storyboard.instantiateViewController(withIdentifier: "myViewController") as! MyViewController
if let navVC = window?.rootViewController as! UINavigationController? {
navVC.pushViewController(myViewController, animated: true)
}
}
So this works fine when app is already running, but it lands me on the home page of my app when the app is killed. What am I doing wrong and how can I solve this?
I have a webview ios app that receives notifications and I pass a url so that when the user clicks on the notification it will open the webview to that url.
When the app is in the foreground and background it works fine. If the user gets the notification when the app is closed and not currently running then the app opens but does not go to that url
In my didReceiveRemoteNotification I detect the different states of the app but I thought that .background would work the same as not running but I guess it doesn't. How can I get the notification to open the url coming from when the app is closed?
AppDelegate.swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let data = userInfo as! [String: AnyObject]
let state = UIApplication.shared.applicationState
if state == .background {
// background
//print("==== Active Running ====")
if let aps = data["aps"] {
let url = aps["url"]
viewController?.loadRequestnotification(for: url as! String)
}
}
else if state == .inactive {
// inactive
//print("==== Inactive Running ====")
if let aps = data["aps"] {
let url = aps["url"]
viewController?.loadRequestnotification(for: url as! String)
}
}
}
UPDATE
So with some help I have been able to use didFinishLaunchingWithOptions to call my webview, but the notification when pressed is still not opening to the url.
I use viewController?.loadRequestnotification(for: url as! String) in some other areas of my delegate that works fine. I am suspecting the return true might be conflicting the call.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
ConnectionManager.sharedInstance.observeReachability()
// Override point for customization after application launch.
FirebaseApp.configure()
registerForPushNotifications()
if launchOptions != nil {
// opened from a push notification when the app is closed
let userInfo = launchOptions?[.remoteNotification] as? [AnyHashable : Any]
if userInfo != nil {
if let object = userInfo?["aps"] as? [String : AnyObject] {
let url = object["url"]
viewController?.loadRequestnotification(for: url as! String)
}
}
}
return true
}
didReceiveRemoteNotification won't be called while app is closed.
Try this code when your app is closed to get notification data.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
if launchOptions != nil {
// opened from a push notification when the app is closed
let userInfo = launchOptions?[.remoteNotification] as? [AnyHashable : Any]
if userInfo != nil {
if let object = userInfo?["aps"] {
let url = object["url"]")
// Now set root controller here
}
}
} else {
// opened app without a push notification.
}
}
There is one scenario like your app is not running and user click on your app's notification then following the way you can get it.
Here is code you can get it
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
if let dictionary:NSDictionary = notification as? NSDictionary{
print("Dictionary Print in didFinishLaunching :: \(dictionary)")
}
}
}
Your app can received some notification and it is in notification center but user can not click on any notification but they will open your app as normally then following is a way, you can get all notification which is received by your app.
UNUserNotificationCenter.current().getDeliveredNotifications { (notification) in
print(notification.count)
}
This is the function that called when app receives any notification.
I have used this in my chatting app.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .active {
//Application is currently active and user receive the notification
} else if application.applicationState == .background {
//app is in background, but not killed
} else if application.applicationState == .inactive {
//app is transitioning from background to foreground (user taps notification), do what you need when user taps here
//Load your URL into webView from here
}
}
If app is open and you want to perform some action when notification is received
Use this method
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(UNAuthorizationOptions.alert.rawValue | UIUserNotificationType.sound.rawValue | UIUserNotificationType.badge.rawValue)
}
You can also check weather the app is open from notification or not in
AppDelegate's didFinishLaunchingWithOptions
But it is recommended to keep the this didFinishLaunchingWithOptions method light as possible.
I hope this will work for you
I have an UITabBarController which presents in one tab a table of chats. If the user clicks on one row, a view controller will be displayed that presents the latest messages.
Now I am introducing Push notifications. The desired behavior would be to automatically go to the chat room if the user opens up the Push notification. Although there's plenty information available on how to handle these use cases, I do not succeed in implementing it.
Here's my code so far:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if (!isUserLoggedIn()) {
// show login vc
} else {
let protectedController = mainStoryboard.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
if let notificationPayload = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? NSDictionary {
if let chatId = notificationPayload["chatId"] as? String {
print(chatId)
// show chat VC
}
}
window!.rootViewController = protectedController
window!.makeKeyAndVisible()
}
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
application.registerForRemoteNotifications()
return true
}
The print statement will never be called but that may be due to the fact that the application is completely closed before opening the notification.
didFinishLaunchingWithOptions is not necessarily called, when you click on a notification, only if your app is not in the memory anymore
In your willFinishLaunchingWithOptions function of your appDelegate, set up a delegate for the currentNotificationCenter
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
}
Make sure your AppDelegate implements UNUserNotificationCenterDelegate relevant for you could be this
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let chatID = userInfo["chatID"] as? String {
// here you can instantiate / select the viewController and present it
}
completionHandler()
}
Update to answer your follow-up question: How to add a UIViewController to a UINavigationController within your UITabbarController
In order to instantiate your ViewController and add it to your UITabbarController:
let myViewController = MyViewController()
guard let tabbarController = self.window.rootViewController as? UITabbarController else {
// your rootViewController is no UITabbarController
return
}
guard let selectedNavigationController = tabbarController.selectedViewController as? UINavigationController else {
// the selected viewController in your tabbarController is no navigationController!
return
}
selectedNavigationController.pushViewController(myViewController, animated: true)
When the user taps on a notification, callback method of the app delegate is:
application:didReceiveRemoteNotification:fetchCompletionHandler:
More information about notifications from Apple.
You should put your code inside this method.
Also you can create Router class (subclass of NSObject), which will show chat view controller and will be responsible for navigation between view controllers in application.
It's a good manner incapsulate this logic into separate class, not keeping it in the AppDelegate class.
I am building an iOS app using Swift and I want to receive push notifications (Parse) when someone has mentioned me.
I have used a navigation controller as the initial view controller and first view controller is the sign in view controller. In this view controller there is an if statement which checks whether the user in already logged in or not. If the user is already logged in then the app jumps automatically to the main screen.
If the sign in is successful, the app jumps to main screen.
My notifications work fine in the following cases:
The app is running
The app is running on the background, and when I tap on the notification bar, the app jumps on the notification screen
My notifications do not work in the following cases:
The app is running on the background, when I tap the icon with the badge (i.e. 1) it shows the main screen and not the notification screen
The app is not running at all, and I am running it through the notification. In this case the app is stacked on the sign in screen and it is not responding. I think that the problem is because it waits for the user to be signed in.
I do not know whether there is an issue in the AppDelegate.swift file. I have followed the Parse documentation as well as the Starter project for Parse in order to code it.
Following are the methods from the AppDelegate.swift
Method application: didFinishLaunchingWithOptions
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if let notificationPayload = launchOptions? [UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {
let meetingId = notificationPayload["meetingId"] as? String
let targetMeeting = PFObject(withoutDataWithClassName: "Meeting", objectId: meetingId)
targetMeeting.fetchIfNeededInBackgroundWithBlock({ (object, error) -> Void in
if error == nil {
let meetingToRespond = Meeting(id: targetMeeting.objectId!, name: targetMeeting["name"] as! String, location: CLLocationCoordinate2DMake((targetMeeting["location"]?.latitude)!, (targetMeeting["location"]?.longitude)!), day: targetMeeting["dayTime"] as! NSDate)
var rootViewController = self.window?.rootViewController as! UINavigationController
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let notificationScreen = mainStoryboard.instantiateViewControllerWithIdentifier("screenForNotification") as! MeetingToRespondViewController
notificationScreen.meeting = meetingToRespond
rootViewController.pushViewController(notificationScreen, animated: true)
}
})
}
// Enable storing and querying data from Local Datastore.
// Remove this line if you don't want to use Local Datastore features or want to use cachePolicy.
Parse.enableLocalDatastore()
Parse.setApplicationId("###",
clientKey: "###")
PFFacebookUtils.initializeFacebookWithApplicationLaunchOptions(launchOptions)
PFUser.enableAutomaticUser()
let defaultACL = PFACL();
// If you would like all objects to be private by default, remove this line.
defaultACL.setPublicReadAccess(true)
PFACL.setDefaultACL(defaultACL, withAccessForCurrentUser:true)
if application.applicationState != UIApplicationState.Background {
// Track an app open here if we launch with a push, unless
// "content_available" was used to trigger a background push (introduced in iOS 7).
// In that case, we skip tracking here to avoid double counting the app-open.
let oldPushHandlerOnly = !self.respondsToSelector(Selector("application:didReceiveRemoteNotification:fetchCompletionHandler:"))
let noPushPayload: AnyObject? = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey]
if oldPushHandlerOnly || noPushPayload != nil {
PFAnalytics.trackAppOpenedWithLaunchOptions(launchOptions)
}
}
if application.respondsToSelector("registerUserNotificationSettings:") {
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
} else {
let types: UIRemoteNotificationType = [UIRemoteNotificationType.Badge, UIRemoteNotificationType.Alert, UIRemoteNotificationType.Sound]
application.registerForRemoteNotificationTypes(types)
}
return FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
}
Method application: didRegisterForRemoteNotificationsWithDeviceToken
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let installation = PFInstallation.currentInstallation()
installation.setDeviceTokenFromData(deviceToken)
installation.saveInBackground()
}
Method application: didReceiveRemoteNotification
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if application.applicationState == .Inactive {
// The application was just brought from the background to the foreground, so we consider the app as having been "opened by a push notification."
PFAnalytics.trackAppOpenedWithRemoteNotificationPayload(userInfo)
}
}
Method application:didReceiveRemoteNotification:fetchCompletionHandler
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .Inactive {
PFAnalytics.trackAppOpenedWithRemoteNotificationPayload(userInfo)
}
if let meetingId: String = userInfo["meetingId"] as? String {
let targetMeeting = PFObject(withoutDataWithClassName: "Meeting", objectId: meetingId)
targetMeeting.fetchIfNeededInBackgroundWithBlock({ (object, error) -> Void in
// Show meeting to respond view controller
if error != nil {
completionHandler(UIBackgroundFetchResult.Failed)
} else if PFUser.currentUser() != nil {
// Get the meeting
let meetingToRespond = Meeting(id: targetMeeting.objectId!, name: targetMeeting["name"] as! String, location: CLLocationCoordinate2DMake((targetMeeting["location"]?.latitude)!, (targetMeeting["location"]?.longitude)!), day: targetMeeting["dayTime"] as! NSDate)
var rootViewController = self.window?.rootViewController as! UINavigationController
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let notificationScreen = mainStoryboard.instantiateViewControllerWithIdentifier("screenForNotification") as! MeetingToRespondViewController
notificationScreen.meeting = meetingToRespond
rootViewController.pushViewController(notificationScreen, animated: true)
completionHandler(UIBackgroundFetchResult.NewData)
} else {
completionHandler(UIBackgroundFetchResult.NoData)
}
})
}
completionHandler(UIBackgroundFetchResult.NoData)
}
Method applicationDidBecomeActive: application
func applicationDidBecomeActive(application: UIApplication) {
// Clear the badge
let currentInstallation = PFInstallation.currentInstallation()
if currentInstallation.badge != 0 {
currentInstallation.badge = 0
currentInstallation.saveEventually()
}
FBSDKAppEvents.activateApp()
}
Thank you in advance!!! :D :) ;)
I have a push notification, and when app receives it, I call the following
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if userInfo["t"] as! String == "rqst" {
print("type is help request")
if let token = NSUserDefaults.standardUserDefaults().objectForKey("authToken") {
authTokenOfHelper = token as! String
}
let storyBoard = UIStoryboard.init(name: "Main", bundle: nil)
let viewController = storyBoard.instantiateViewControllerWithIdentifier("helperMap")
let navController = UINavigationController.init(rootViewController: viewController)
self.window?.rootViewController = nil
self.window?.rootViewController = navController
self.window?.makeKeyAndVisible()
helpRequestReceived = true
}
}
this initialises storyboard.But if my app was killed by system and it is off and device recieves push, after tapping on push nothing is happened.
Seems that I have to use application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) if app is switched off
But how to access userInfo in didFinishLaunchingWithOptions ?
You can check this in didFinishLaunching using UIApplicationLaunchOptionsRemoteNotificationKey as launch options.
UIApplicationLaunchOptionsRemoteNotificationKey: Indicates that a
remote notification is available for the app to process. The value of
this key is an NSDictionary containing the payload of the remote
notification. > - alert: Either a string for the alert message or a
dictionary with two keys: body and show-view. > - badge: A number
indicating the quantity of data items to download from the provider.
This number is to be displayed on the app icon. The absence of a badge
property indicates that any number currently badging the icon should
be removed. > - sound: The name of a sound file in the app bundle to
play as an alert sound. If “default” is specified, the default sound
should be played.
You can call application:didReceiveRemoteNotification: in application:didFinishLaunchingWithOptions: manually.
Objective C
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...
if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
[self application:application didReceiveRemoteNotification:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
}
return YES;
}
Swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {
self.application(application, didReceiveRemoteNotification: launchOptions![UIApplicationLaunchOptionsRemoteNotificationKey]! as! [NSObject : AnyObject])
}
return true
}
This is in Objective C but the same thing is in Swift. Put this in didFinishLaunchingWithOptions:
NSDictionary *remoteNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotif) {
[self application:application didReceiveRemoteNotification:remoteNotif];
}
Swift:
if let remoteNotif = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {...}