Show a View Controller that is already on Navigation Stack - ios

I have a Tab bar Controller (with a bottom menu) and also a top menu. The problem is that I don't want to link the yellow and green views to the tab bar (because the user is going to change the views using the top menu rather than the bottom menu).
I'm having a problem that every time I click the buttons a new instance of the view is going to stack (so I end up having something like V1 -> V2 -> V3 -> V2 -> V4 and so on)
My partial solution is to make something like this:
#IBAction func yellowViewButtonAction(_ sender: AnyObject)
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YelloViewController") as! YelloViewController
if let viewControllers = navigationController?.viewControllers {
for viewController in viewControllers {
// some process
if viewController is YelloViewController {
print("View is on stack")
}
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YelloViewController") as! YelloViewController
self.navigationController?.pushViewController(controller, animated: false)
}
}
I can see that the view is on navigation stack because the if statement inside the for is true. The question is, how can I retrieve it instead of pushing a new instance of the same view? (Because besides the huge memory problem that this present I also lose any data I had on the view).
I want to keep everything on the stack intact.
Example:
V1 -> V2 -> V3 -> V4 (current view)
If I go back to V1 from V4 I still want to have V4, V3 and V2 on the navigation controller stack.
Another question is that if this solution is something that Apple might refuse.
I appreciate any help.

looks like you don't use and need navigation controller. Whenever you call self.navigationController?.pushViewController(controller, animated: false) a new instance of that controller is on its way to stack.
Ideally you would call popViewController from that view controller where you have navigated. When creating custom behavior of tab bar controller it is quite difficult to get the navigation logic exactly as you planned, at least in my opinion.
In cases like this I usually take care of showing and hiding view controllers manually.
#IBAction func didPressTab(sender: UIButton) {
let previousIndex = selectedIndex
selectedIndex = sender.tag
buttons[previousIndex].selected = false
let previousVC = viewControllers[previousIndex]
previousVC.willMoveToParentViewController(nil)
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
sender.selected = true
let vc = viewControllers[selectedIndex]
addChildViewController(vc)
vc.view.frame = contentView.bounds
contentView.addSubview(vc.view)
vc.didMoveToParentViewController(self)
}
where every 'navigation button' has unique id and calls didPressTab function.
I actually learnt this from this tutorial: https://github.com/codepath/ios_guides/wiki/Creating-a-Custom-Tab-Bar

Pops view controllers until the specified view controller is at the top of the navigation stack.
Reference - https://developer.apple.com/documentation/uikit/uinavigationcontroller
func popToViewController(UIViewController, animated: Bool) -> [UIViewController]?

Related

Tabbar not showing when i open from side menu

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)

remove navigation controller from superview

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.

Instantiate view, then navigate away

I'm currently writing an app where we are launching a walkthrough the first time the user opens the app. At the end of it, we require the user to fill in a few details, and to do so I would like him to press a button to get redirected to the settings page.
The thing is, this page is one level down the navigation controller (from the landing page). As it stands, I can correctly instantiate the landing page, but the redirection to the settings page never happens.
let mainView = self.storyboard?.instantiateViewControllerWithIdentifier("NavCtrl")
self.presentViewController(mainView!, animated: false, completion: nil)
// the above works correctly and sends us to the landing screen
// (rootView of the navigation controller)
// the following lines never have any effect though
let settingsView = self.storyboard?.instantiateViewControllerWithIdentifier("Settings View")
self.navigationController?.pushViewController(settingsView!, animated: false)
I think it's because I'm trying to call .pushViewController before the storyboard had time to instiate either the first or second view.
So I have a few questions:
Is there a way to, indeed, instantiate a view and then navigate to another one right after it has been instantiated ? (this in order to keep the navigation stack and maintain accurate nav bar behaviour)
If there isn't, would it be possible to programmatically populate the navigation stack so that I would only need to instantiate the settings view ? This in order to still have the back button in the nav bar that would send to the landing screen ?
Thanks in advance guys
Ok thanks to #Leonardo for showing me the correct direction !
I solved the issue by doing the following in appDelegate:
/*
* Override window root view and set it to a newly initialized one
* view: StoryboardID of view to display
* navigateTo: if true, set the root view to NavCtrl and then navigate to the desired view
*/
func setWindowViewTo(view: String, navigateTo: Bool) {
//initalize storyboard & window programmatically
window = UIWindow.init(frame: UIScreen.mainScreen().bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
//if true, navigate from landing page to specified view through navigationController
if(navigateTo) {
//instantiate the navigation controller
let navCtrl = storyboard.instantiateViewControllerWithIdentifier("NavCtrl") as! UINavigationController
//instantiate the landing page & the page we wish to navigate to
let landingView = storyboard.instantiateViewControllerWithIdentifier("Main View")
let vc = storyboard.instantiateViewControllerWithIdentifier(view)
//manually set the navigation stack to landing view + view to navigate to
navCtrl.setViewControllers([landingView, vc], animated: false)
//replace the rootViewController to the navigation controller
window!.rootViewController = navCtrl
//make it work
window!.makeKeyAndVisible()
} else {
window!.rootViewController = storyboard.instantiateViewControllerWithIdentifier(view)
window!.makeKeyAndVisible()
}
}
The important step was to indeed force downcast as! UINavigationController when instantiating the NavigationController with the storyboard.instantiateViewControllerWithIdentifier() method.
Then it's just a case to correctly instantiate the views of the navigation stack you want, and finally calling navCtrl.setViewControllers([view1, view2], animate: false).
Thanks all for your help !
You can use the - setViewControllers:animated: method to set the navigation stack of a UINavigationController
Here's the reference
But I don't think that's your problem, if I undestood your code correctly, it should be
//This is the navigation controller
if let mainView = self.storyboard?.instantiateViewControllerWithIdentifier("NavCtrl"){
//Nav being modally presented
self.presentViewController(mainView, animated: false, completion: nil)
// Instantiate the settings view
if let settingsView = self.storyboard?.instantiateViewControllerWithIdentifier("Settings View"){
//Push it to the navigation controller presented
mainView.pushViewController(settingsView, animated: false)
}
else{
//Can't Instantiate, deal with error
}
}
else{
//Can't Instantiate, deal with error
}

swift ios cant remove old modal viewcontrollers from stack

In my app I am making the account page the new root VC when a user logs in.
It looks like this:
Navigation controller -> table view -> menu(modal segue) -> login screen(modal segue) -> account page
When transitioning from login to account I am using:
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let vc = storyboard.instantiateViewControllerWithIdentifier("testVc")
let navigationController = self.view.window?.rootViewController as! UINavigationController
navigationController.setViewControllers([vc], animated: true)
This makes the account page the new root VC. But the only problem is that once is shows up both the menu and login form is still visible ontop of the screen.
So how do I clear two old VC's shown as modal?
Update got it to work using:
#IBAction func loginButtonDidTouch(sender: AnyObject) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("testVc")
let navigationController = self.view.window?.rootViewController as! UINavigationController
self.presentingViewController!.presentingViewController!.dismissViewControllerAnimated(false) { () -> Void in
navigationController.setViewControllers([vc], animated: true)
}
}
But I dont know if this is the right way to do it?
You need to get the reference of those controllers, and then dismissing them.
Try this:
let loginScreen = self.window.rootViewController.presentedViewController
loginScreen.dismissViewControllerAnimated(false) { () -> Void in
let menuScreen = self.window.rootViewController.presentedViewController
menuScreen.dismissViewControllerAnimated(false, completion: nil)
}
When you're calling the original navigation stack and modifying it:
let navigationController = self.view.window?.rootViewController as! UINavigationController
You are setting the new view controller (#testVc) by replacing the only other view controller, "tableview", in that navigation stack.
The modally presented views are not a part of that particular navigation stack and instead are presented above the current navigation stack as new stacks (this gives you a pointer to the new Navigation Controller on top in the form of self.navigationController to push new views)
You can explicitly dismiss the two modally presented views by calling dismissViewControllerAnimated(_:completion:) on each, most likely by propagating the communication through a delegate response or through the completion handler.

How to push viewcontroller correctly

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?

Resources