When a user taps a push notification, I want them to be taken to a specific table view controller in my app. This table view controller is embedded in a navigation controller, which is embedded in a tab bar controller (my root view controller). The image shown below visualizes this.
The root view Tab Bar Controller has a storyboard ID of "HomeVC" and a class name of "HomeViewController", the Navigation Controller has a storyboard ID of "SettingsNavigationVC", and the Table View Controller has a storyboard ID of "SettingsTableVC" and a class name of "SettingsNavigationVC".
I have push notifications working. By working, I mean I can send a message from a device and receive it on another device, but when the receiver taps the notification I can't seem to get any other view controller to open other than the root view controller. According to the push notification guide I'm using, I'm using the following code in the didReceiveRemoteNotification method:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject: AnyObject]) {
let rootVC = self.window!.rootViewController
if PFUser.currentUser() != nil {
let settingsTableVC = SettingsTableViewController()
rootVC?.navigationController?.pushViewController(settingsTableVC, animated: false)
}
}
What am I doing wrong, or what must I do to present the right view controller?
Try this
let storyboard = UIStoryboard(name: "YourStoryboardName", bundle: nil)
let rootVC = storyboard.instantiateViewControllerWithIdentifier("HomeVC") as! UITabBarController
if PFUser.currentUser() != nil {
rootVC.selectedIndex = 2 // Index of the tab bar item you want to present, as shown in question it seems is item 2
self.window!.rootViewController = rootVC
}
In order to push to the specific view controller, you must replace below line:
Yours:
rootVC?.navigationController?.pushViewController(settingsTableVC, animated: false)
Mine:
rootVC?.visibleViewController.navigationController?.pushViewController(settingsTableVC, animated: true)
And for presenting any view controller, you must use this:
rootVC?.visibleViewController.navigationController?.presentViewController(settingsTableVC, animated: false, completion: nil)
Related
Im trying to open a certain view from a push notification but i keep losing the nav bar and the back and next references. this what my storyboard looks like this (with the view i want to open)
this is what i have in my AppDelagate:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController
self.window?.rootViewController = vc
What you're doing is completely replacing the root view controller of your application, this means that all the current UI will be discarded.
What you should do instead is to use your knowledge of your application to direct it to the new content. For example, if your root view controller is a navigation controller, you can cast rootViewController to a nav controller and push it (this will fail if your root view controller is something else, like a tab bar controller).
guard let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController else {
fatalError("Main Storyboard doesn't have a notification controller")
}
guard let nav = self.window?.rootViewController as? UINavigationController else {
return //handle unexpected state
}
nav.push(vc, animated: true)
Another option would be to embed your notification controller into a navigation controller, add a Close button, and present it modally, that way you can present it on top of rootViewController no matter what that controller is.
As we can see in the screenshot your provided, the application's root view controller is the UINavigationController instance.
And according to that, let me offer the next code:
func handleNotification(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController else{
debugPrint("NotificationViewController with identifier 'notification' not found")
return
}
guard let navVC = self.window?.rootViewController as? UINavigationController else{
debugPrint("RootViewController is not an UINavigationController")
return
}
navVC.pushViewController(vc, animated: true) //perhaps your will prefer to use false
}
Beside that, you can use more flexible implementation.
In your AppDelegate post a (NS)Notification when notification intercepted, the relevant view-controller(s) observe the notification, and act when notification broadcasted.
You can also set an identifier to the segue and invoke performSegue method from the observing view-controller
You can set from storyboard -> add view controller -> Embed in navigation controller -> set second view controller -> Attach seque between that controllers. You will see same view controllers like that image .
So i've implemented universal link in my app and when pressing on a button I open my second app with :
UIApplication.shared.open(aUrl!)
also I'm getting the call in :
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {}
which is an alert.
The question is like the title says..how could I navigate to a specific VC ,(from the FirstApp to the Second which i opened it with universal link), more like mapping a navigation controller stack .
I've writed something like :
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MyAdsController") as? MyAdsViewController {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(controller, animated: true, completion: nil)
}
}
but it's an ugly representation, i need something like the hole navigation stack...
And also , can i make a helper class for this if i want to handle more with universal link? Any suggestion are appreciated.
You need to use the .viewControllers property of your UINavigationController you can do this using setViewControllers(_:animated:) method or modifying directly .viewControllers property where rootViewController will be the viewControllersArray[0] and topViewController will be viewControllersArray[count-1]
This property description and details can be founded in the UINavigationController documentation
viewControllers
Property
The view controllers currently on the navigation stack.
Declaration
SWIFT
var viewControllers: [UIViewController]
Discussion The root view controller is at index 0 in the array, the back view controller is at index n-2, and the top controller is at
index n-1, where n is the number of items in the array.
Assigning a new array of view controllers to this property is
equivalent to calling the setViewControllers:animated: method with the
animated parameter set to false.
example
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let firstViewController = storyboard.instantiateViewController(withIdentifier: "FirstViewController") as? FirstViewController
let secondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController
let thirdViewController = storyboard.instantiateViewController(withIdentifier: "ThirdViewController") as? ThirdViewController
self.navigationController?.setViewControllers([firstViewController,secondViewController,thirdViewController], animated: false)
Alright so this is less of a Universal Link question and more of a navigation preference. When you are passed a universal link in your continueUserActivity function you should parse it and navigate accordingly from your app delegate. You should really switch your Universal Link handling to Branch since they will handle this for you for free and they pass metadata through a callback instead of link parameters which will make your links more powerful. Anyways...
Start with a UINavigationViewController
This consists of having a rootViewController which should be an instance of your main page. Store this navigation view controller as an instance variable so your app is only dealing with one Navigation view controller on launch. If you're using a storyboard then you can just just get reference to that navigation view controller:
self.nav = self.window?.rootViewController as? UINavigationViewController
Pushing vs presenting a View Controller onto Nav
When you receive a link and determined that you need to bring that user to a new page, you should decide whether to push or present the VC.
Pushing
Pushing is necessary if you would like to maintain that view as part of a stack. For example, if you want to show the user a pair of shoes, you may want to take them into the shoes category, then present them with the shoe detail controller. In this case you could take the user to the home page and push the proper navigation stack onto your UINavigationViewController.
self.nav.popToRootViewController(animated: true)
self.nav.pushViewController(firstController, animated: true)
self.nav.pushViewController(secondViewController, animated: true)
This way your users UINavigationController stack will look like root > firstVC > secondVC and they will have bread crumbs to be able to traverse back through the flow.
Presenting
In the case that you do not want to push a view onto the stack. Maybe you want to present a popup regardless of where they are at in the app and you don't want to mess up their location, you should use present a VC. This will simply present a ViewController over the entire NavigationController without being added to the NavigationController's stack.
self.nav.presentViewController(modalVC, animated:true, completion: nil)
From within that modalVC you can call
self.dismiss(animated: true, completion: nil)
I am trying to accomplish what I think is a pretty common set of steps:
When my app starts, load a home controller in a navigation controller:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.rootViewController = UINavigationController(rootViewController: HomeViewController())
window.makeKeyAndVisible()
self.window = window
return true
}
When my app loads check if the user is logged in. If not, present a registration view controller:
func applicationDidBecomeActive(application: UIApplication) {
if ref.loggedIn != nil {
// user authenticated
print(ref.userData)
} else {
// No user is signed in
let registerViewController = RegisterViewController()
if let navController = window?.rootViewController as? UINavigationController {
navController.presentViewController(registerViewController, animated: true, completion: nil)
}
}
}
In my RegisterViewController, provide a button that switches to a LoginViewController:
#IBAction func login(sender: UIButton) {
print("LOGIN")
let loginViewController = LoginViewController()
// How do I present the view controller here?
}
My question is: how can I present the LoginViewController so that calling self.dismissViewControllerAnimated(false, completion: nil) will return to my HomeViewController?
Things I've tried:
If I call self.presentViewController from the RegisterViewController then dismissing returns back to the RegisterViewController instead of HomeViewController
If I try to get a reference to rootViewController via UIApplication.sharedApplication() and present the login controller on rootViewController then I get an error "Attempt to present ... on ... whose view is not in the window hierarchy!"
Thanks!
It's obvious that you will get the error "Attempt to present ... on ... whose view is not in the window hierarchy!"
First you need to make the instance of ViewController using storyboard identifier.
Please try this :-
let storyboard = UIStoryboard(name: "YourStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("viewControllerToBePresented") as! UIViewController //use your class name here to cast it.
self.presentViewController(vc, animated: true, completion: nil)
EDIT
In your case you can use protocol on register screen which will get
implemented on home screen.
And in that implementation you can write code to dismiss register view
and then present Login view.
If you are trying to present a new view controller add that view controller in navigation controller and then present the navigation controller..
From the presented view controller you can dismiss to previous view controller.
I hope this will work..
In your case you can use protocol on register screen which will get implemented on home screen.
And in that implementation you can write code to dismiss register view and then present Login view.
(reposting due to initial wrong title)
When users receive a push notification for our app (and they tap on it), the app should open up a details screen. From a navigation perspective, the app usually has this structure anytime the user opens it:
TabBarController -> Navigation Controller -> View Controller
Once a use open the push notification, I'd like to instantiate a UIViewController. However, this VC should be part of the tabbarcontroller, so that the user can navigate to other areas of the app as well. Right now I am able to display the VC itself, but I can't make it appear as part of the tabbarcontroller:
class private func instantiateJobDetailsViewController(job: JobModel) {
if let currentViewController = UIApplication.sharedApplication().delegate?.window??.rootViewController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let jobDetailViewController = storyboard.instantiateViewControllerWithIdentifier("JobDetailController") as! JobDetailController
jobDetailViewController.job = job
currentViewController.presentViewController(jobDetailViewController, animated: true, completion: nil)
} else {
ClientHelper.displayAlertAsync("Error", message: "Info for support: cannot present jobDetails view controller", controller: nil)
}
}
Can someone provide some guidance about how to instantiate such a VC and, at the same time, preserve the tab bar controller so the user can navigate within the app?
For reference, here is how I resolved this situation:
class private func instantiateJobDetailsViewController(job: JobModel) {
if let tabBarController = UIApplication.sharedApplication().delegate?.window??.rootViewController as? UITabBarController {
tabBarController.selectedIndex = 0
let currentNavigationController = tabBarController.selectedViewController as! UINavigationController
let currentViewController = currentNavigationController.topViewController!
currentViewController.performSegueWithIdentifier("JobDetailSegue", sender: job)
} else {
ClientHelper.displayAlertAsync("Error", message: "Info for support: cannot present jobDetails view controller", controller: nil)
}
}
So it is basically:
Get the instance of the tab bar controller.
Make sure you are in a relevant screen on the tab bar controller by setting its selectedIndex property.
Get the instance of the navigation controller
Get the instance of the view controller being presented by the navigation controller.
In this way, I can open up a relevant screen from a push notification while a) showing the tab bar controller and b) preserving the navigation element to allow the user to navigate back from the screen.
You try change to structure
Navigation Controller -> TabBarController -> View Controller
You have a main navigationcontroller, set it is a global variable-a singleton ( var mainNavigation:UINavigationController?)
and when users receive a push notification, you can push to detailScreen from mainNavigation. Like this storyboard
I want to display a modal view controller to the user should there not be a user logged in. Here is my method implementation:
func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool
{
// Notifications
//
// User
//
NSNotificationCenter.defaultCenter().addObserver(self, selector: "noCurrentUser:", name: UserCurrentUserNotSetNotificationName, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "currentUserDidChange:", name: UserCurrentUserDidChangeNotificationName, object: nil)
// Root window
//
if managedObjectContext != nil && User.currentUser(managedObjectContext!) == nil
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let logInViewController = storyboard.instantiateViewControllerWithIdentifier("Log In View Controller") as? LogInViewController
{
window?.rootViewController?.presentViewController(logInViewController, animated: false, completion: nil)
}
}
return true
}
I include the notifications because currently, the noCurrentUser: method presents my log in view controller modally and animated. This works well except when the app launches, the user sees a flash of the app (the root view controller) before the the notification is sent and the modal log in view controller is presented.
I’ve tried setting the modal animation option to false on presenting, but because it’s not the root view controller, this still doesn’t work.
So how do I properly set a root view controller to a modal view controller which I can then dismiss modally.
You can't. The root view controller should be considered the visual representation of your application. To make this modal implies that your application itself is modal, which obviously isn't the case.
What you are really looking to be able to do is present the modal view controller as the first thing users see. This is not the same as being the root view controller.
Set the root view controller to a regular view controller (or navigation controller) and then push your modal view controller onto it. When the modal view controller is dismissed, your application may begin.