(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
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 .
I am using this code
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "PrintMainViewController")
self.navigationController!.pushViewController(controller, animated: true)
and also added:
self.tabBarController?.tabBar.isHidden = false
If I understand your hierarchy correctly you have a tab bar controller which has navigation controllers in it. So basically any of the tabs can push additional view controllers and tab bar is still visible.
Now you want to push some new controller on the currently selected view controller in the tab bar and you want to do it from another part of the app, another view controller that has no relation to tab bar.
The quickest way to do that is to expose a static instance of your tab bar view controller. This will only work if you always have only 1 tab bar controller in your application (probably 99% of the applications).
First add a current instance to your tab bar view controller:
class MyTabBarViewController: UITabBarController {
static private(set) var currentInstance: MyTabBarViewController?
override func viewDidLoad() {
super.viewDidLoad()
MyTabBarViewController.currentInstance = self
}
}
So when view loads a static value is assigned and can now be accessed anywhere in your project via MyTabBarViewController.currentInstance.
The rest is then just accessing the currently selected view controller and pushing a new view controller. Something like this should do:
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "PrintMainViewController")
(MyTabBarViewController.currentInstance?.selectedViewController as? UINavigationController)?.pushViewController(controller, animated: true)
You must push in TabBarController
self.tabBarController?.pushViewController(controller, animated: true)
So i have an app were you're supposed to chose a location from a map but the way i'm doing it is by a popup off a view controller that has a MapKit and a search bar and a button for choosing users location, the problem lies on when i'm done with the View I normally called the
self.view.removeFromSuperview()
but the thing that is being remove is the View controller itself but il leaves behind the navigation bar and it doesn't let me do nothing else (in fact when I go to another tab and return to the same one the VC returns).
the way i'm instantiating the VC is like this:
func location ()
{
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "mapV") as! UBICACIONViewController
let nav1 = UINavigationController()
nav1.viewControllers = [vc]
self.addChildViewController(nav1)
self.view.addSubview(nav1.view)
vc.delegate = self //this for the info that i'm getting afte(works fine)
nav1.didMove(toParentViewController: self)
}
So until here works fin but when the user pick te button that says "Use My Location" and tries to return there's the problem.
in my VC view controller this is what happens when finished:
func Remove()
{
if let del = delegate {
del.dataChanged(str: Event.sharedInstance.Location!)
}
self.view.removeFromSuperview() //Problem Here :/
}
Second VC
Return to First VC
Actually you have added a child view controller nav1. And nav1 has a subview nav1.view.
while removing you are removing this view from its superview, not the child view controller added.
Any ways, From you screen short..I understand that the pop up view is off full screen then why dont you present this VC modally, or else push it to navigation stack.
I want to send a user to a specific ViewController in my app once a notification is clicked.
I now that I can do something like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewControllerWithIdentifier("Home") as? HomeViewController
presentedVC?.presentViewController(destinationViewController!, animated: true, completion: nil)
But my app has a tab bar and looks like this
Tab bar
tab1: navigationController -> VC1
tab2: navigationController -> VC2 -> HomeVC
tab: navigationController -> VC3
Each tab has a navigationController as a infront of it.
So how can I send the user to HomeVC? I must first select tab 2 then the navigation controller then push the user tvice:
tab2: navigationController -> VC2 -> HomeVC
And the other problem, if there any way to tell if the user is already in HomeVC? I dont want to send the user to the same VC if his already there.
You must have access to your UITabbarController in you UIApplicationDelegate or wherever you're handling the notification tap.
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController //or whatever your way of getting reference is
So first you'll get the reference to UINavigationController in your second tab like this:
let navInTab:UINavigationController = tabBar.viewControllers?[1] as! UINavigationController
Now push your home view at second tab's navigation controller:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewControllerWithIdentifier("Home") as? HomeViewController
navInTab.pushViewController(destinationViewController!, animated: true)
And finally switch your tab to second to show the just pushed home controller
tabBar.selectedIndex = 1
Keep in mind, this answer assumes that your application has already set the tab bar as the root view controller of application window prior handling the notification tap.
Try something like this:
if let tabBarController = window?.rootViewController as? UITabBarController {
tabBarController.selectedIndex = 1 // in your case the second tab
}
The idea is to switch to get the tab bar instance and switch it to your desired tab (where you have your view controller).
The above code works in AppDelegate / you can easily call it anywhere by getting the tabBarController instance.
You can check which tab is selected by user with the method var selectedIndex: Int. You can check which view controller is present like this self.navigationController?.presentingViewController?.presentedViewController. This will solve your problem.
This is my storyboard:
Short Description:
My App starts with the LaunchController
a modal segue shows the Reveal View Controller
this bring the Menu Controller and my Main Navigation Controller (ID
"NavController"; green Navbar) together. this will create a slide
menu. (Basic Code:
appcoda.com/ios-programming-sidebar-navigation-menu/)
my Main Navigation Controller shows a TableViewController.
this one have a menu button (3 Lines) which make the slide menu
visible
the plus icon willo push the last view Controller (ID: "VC1").
My Problem:
I would like to set quick actions for my app.
This code help works for that:
#available(iOS 9.0, *)
func handleShortcut( shortcutItem:UIApplicationShortcutItem ) -> Bool {
var succeeded = false
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewControllerWithIdentifier("NavController")
self.window?.rootViewController = initialViewController
let navVC = self.window?.rootViewController as! UINavigationController
switch shortcutItem.type {
default:
navVC.pushViewController(storyboard.instantiateViewControllerWithIdentifier("VC1"), animated: false)
succeeded = true
break
}
return succeeded
}
This Code set the NavController as initial Controller und push the to VC1.
This works fine.
With the X icon in VC 1, i use the unwind function back to TableView.
The Problem: if i use the undwind function and fall back to tableview i can't open the slide menu. A Touch on the menu icon give no reaction.
The Problem can be, that i start the app with the quick action behind the Reveal View Controller.
How can I solve this problem?
Did you try to make the reveal view controller the entry point of your app and deleting the launch controller, because if you just want to go back to the main view you could just do this :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateInitialViewController() as UIViewController!
self.presentViewController(controller, animated: false, completion: nil)
If you cannot could you explain the purpose of the launch controller?