Hello,
so i have this storyboard structure i have a button in ViewController B i need that button to point to the bottom NavigationController in which it has the ViewController C as rootViewController so when i press a button in ViewContoller B it takes me to C now i need a back button on C to take me back to B
i have tried looking but all of the reference materials show how to navigate between ViewController not NavigationControllers
I think you cannot push Navigation Controller inside another uinavigation controller but alternativaly you can present another nav controller.
Hope it works for you.
I have created simple demo like this.
class ViewController: UIViewController {
#IBOutlet weak var btnCustom: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btnForward(_ sender: Any) {
/*Uncomment the code if you want to present programatically*/
/*if let navi2 = self.storyboard?.instantiateViewController(withIdentifier: "nav2"){
self.present(navi2, animated: true) {
}
}*/
}
}
class ViewController2: UIViewController {
#IBOutlet weak var btnCustom: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnBack(_ sender: Any) {
if let parentNav = self.navigationController {
parentNav.dismiss(animated: true) {
}
}
}}
You can use a single navigation controller, but overwrite the back buttons action.
By default it will pop the current view controller from the navigation stack and go to the previous one.
On the back action in VC B just push to the VC C, you can do this either in storyBoard by drag-drop from the back button to VC C or by code by writing:
self.navigationControlller.push(VC C)
If using 2 navigationControllers is a requirement that cannot be avoided, you can solve the problem statement in the following way.
In ViewControllerC on a UIButton's tap event, get the instance of ViewControllerB in the parent navigationController and pop to it like so,
#objc func backAction() {
let parentNav = self.navigationController?.navigationController
if let vcB = parentNav?.viewControllers.first(where: { $0 is ViewControllerB }) {
parentNav?.popToViewController(vcB, animated: true)
}
}
Add the backAction() to backButton's target like,
backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside)
Put this code inside your C's viewController back button action method. Also you have to give identifiers names to your views in the storyboard screen.
...
//It possible to change between different storyboards, but in your case all the views share the same storyboard.
UIStoryboard* storyboardOfA_B = [UIStoryboard storyboardWithName: #"storyboardOfA_B" bundle:nil];
UINavigationController* navigationControllerOfA_B = [storyboardOfA_B instantiateViewControllerWithIdentifier:#"navigationControllerOfA_B"];
UIViewController* viewControllerB = [storyboardOfA_B instantiateViewControllerWithIdentifier:#"viewControllerB"];
//I'm supposing that you have the action method of the back button in the C's view controller so I use self.
UINavigationController* navigationControllerOfC = self.navigationController;
[navigationControllerOfC pushViewController: navigationControllerOfA_B animated:NO];
[navigationControllerOfA_B pushViewController: viewControllerB animated:NO];
...
Maybe this is not the best way but is a way
Related
I want to create a navigation hierarchy where I want to go to a SecondViewController from FirstViewController using a NavigationController. The FirstViewController contains a button B that I intend to use to go to a SecondViewController. I am employing the navigation controller concept since I want to return to the FirstViewController later. I have done the following steps to achieve it:
I have embedded a NavigationController into a storyboard X containing both the FirstViewController and SecondViewController.
I have created a segue from the button B present in the FirstView (has FirstViewController associated with it) to the SecondView (has SecondViewController).
I set the navigationViewController nvc as following after I have presented the firstViewController:
nvc = UINavigationController.init(rootViewController: firstViewController)
Later on, when the button B gets pressed, I execute the following code to push the secondViewController onto the navigation hierarchy:
self.nvc?.pushViewController(secondViewController, animated: true)
However, the secondView doesn't present itself even after pushing.
I have named the segue between the button B and secondViewController as kSegue, so I tried to perform the segue as an addition to see if the secondViewController presents itself or not:
self.performSegue(withIdentifier: "kSegue", sender: self)
An exception occurs when both the 4th and 5th steps are performed together. The exception states that I'm pushing a viewController that already exists on the navigation hierarchy, but the second view still doesn't open even if I comment the performSegue code.
May I ask what mistake I am making here?
In the storyboard, make sure there is a rootViewController segue from the navigation controller to the first view controller. Also, make sure the navigation controller is marked as the initial view controller.
In the code, change
self.nvc?.pushViewController...
To
self.navigationController?.pushViewController...
1) Take a navigation controller on your storyboard
2) Set navigation controller as initial view controller
3) set view controller A as a root view controller of your navigation controller
4) in "GoToB" method access View controller B's instance and push it in navigation controller
5) On View controller B's "Go Back" method write code to pop it.
6) Dont forget to set storyboard Id on both A & B view controller
class FirstViewController: UIViewController
{
lazy var secondViewController : SecondViewController? =
{
let secondViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController
return secondViewController
}()
override func viewDidLoad()
{
super.viewDidLoad()
}
#IBAction func goToB(sender : UIButton)
{
guard let secondViewController = self.secondViewController else
{
return
}
self.navigationController?.pushViewController(secondViewController, animated: true)
}
}
class SecondViewController: UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func goBack(sender : UIButton)
{
self.navigationController?.popViewController(animated: true)
}
}
I have a TabBarController in my Main.storyboard file.
In my Upload.storyboard I am presenting a ViewController from the Main.storyboard file, however, it doesn't contain the tab bar.
The viewProfile button should go to a tab called Sharks and within that present a view controller based on data gathered in the Upload.storyboard (modal view).
Can I add the tab bar programmatically or am I not properly presenting the correct VC?
// MARK: - Actions
#IBAction func viewProfileButtonPressed(_ sender: UIButton) {
let stb = UIStoryboard(name: "Main", bundle: nil)
let sharkProfile = stb.instantiateViewController(withIdentifier: "sharkProfile") as! SharkProfileTableViewController
self.present(sharkProfile, animated: true) {
// add tab bar here?
}
}
What you need to do is present the tab bar view controller, not the view controller that is embedded in it
One method is to create a Delegate Protocol to allow a tap on View Profile button to "call back" to the View Controller that presented it. When that callback is received, the VC sets the "current" tab.
It will look something like this:
// define "call back" delegate protocol
protocol EncounterUploadedDelegate : class {
func didTapSharkProfileButton()
}
The Encounter view controller will need to conform to that protocol:
class EncounterViewController: UIViewController, EncounterUploadedDelegate {
// the normal stuff for this VC and all the other code for it
// ...
// conform to the protocol
func didTapSharkProfileButton() -> Void {
// when we get this call-back, switch to the Shark Profile tab
// tabs are zero-based, so assuming "SharkProfile" is
// is the 4th tab...
self.tabBarController?.selectedIndex = 3
}
// assuming a "Show Modal" segue named "ShowEncounterUploadSegue" is used
// to present the Modal View
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEncounterUploadSegue" {
let vc = segue.destination as! TabModalVC
vc.encounterDelegate = self
}
}
}
The view controller to be presented as modal:
class TabModalVC: UIViewController {
weak var encounterDelegate: EncounterUploadedDelegate?
#IBAction func profileButtonTapped(_ sender: Any) {
// dismiss self (the modal view)
self.dismiss(animated: true, completion: nil)
// this will call back to the delegate, if one has been assigned
encounterDelegate?.didTapSharkProfileButton()
}
}
Hope that all makes sense :)
I have three View Controllers embedded in Navigation Controller. I want to go from VC1 to VC3 so that in VC3 the Navigation Item's back button would direct the user to VC2 instead of VC1. I think this should be done either by adding the VC2 to the Navigation Stack between VC1 and VC3 when VC3 is created or by skipping the second View Controller.
I tried this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "JumpToThirdVCSegue":
if let secondvc = segue.destinationViewController as? SecondViewController {
secondvc.performSegueWithIdentifier("ThirdVCSegue", sender: self)
}
default: break
}
}
}
but I couldn't get it working. Perhaps performing a segue isn't possible when the View Controller isn't open yet?
What is the best way to skip a View Controller / add a View Controller in the middle of Navigation Stack? I hope this helps you to understand what I'm trying to do:
Something like this should work:
self.navigationController?.pushViewController(viewController2, animated: true)
self.navigationController?.pushViewController(viewController3, animated: true)
EDIT:
If you want to push the second view controller without being noticed by the user you need to add it to the navigation controller after the third view controller is pushed. This can be done by implementing UINavigationControllerDelegate. You can store your second view controller in a variable and insert it to the navigation controllers hierarchy in delegate method. Your main view controller will look like following:
class MyViewController: UIViewController, UINavigationControllerDelegate {
var viewControllerToInsertBelow : UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
func pushTwoViewControllers() {
if let viewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("id1"),
let viewController3 = self.storyboard?.instantiateViewControllerWithIdentifier("id2") { //change this to your identifiers
self.viewControllerToInsertBelow = viewController2
self.navigationController?.pushViewController(viewController3, animated: true)
}
}
//MARK: - UINavigationControllerDelegate
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if let vc = viewControllerToInsertBelow {
viewControllerToInsertBelow = nil
let index = navigationController.viewControllers.indexOf(viewController)!
navigationController.viewControllers.insert(vc, atIndex: index)
}
}
}
if var navstack = navigationController?.viewControllers{
navstack.append(contentsOf: [vc1,vc2])
navigationController?.setViewControllers(navstack, animated: true)
}
Works well
Edit: using swift and segues, this should work:
override func performSegueWithIdentifier(identifier: String?, sender: AnyObject?) {
super.performSegueWithIdentifier(identifier, sender: sender);
if identifier == "JumpToThirdVCSegue" {
// now the vc3 was already pushed on the navigationStack
var navStackArray : [AnyObject]! = self.navigationController!.viewControllers
// insert vc2 at second last position
navStackArray.insert(viewController2, atIndex: navStackArray.count - 2)
// update navigationController viewControllers
self.navigationController!.setViewControllers(navStackArray, animated:false)
}
}
So you override the performSegueWithIdentifier in the VC1 in order to change the navigation stack when the segue to VC3 has actually been performed (and is not only in preparation).
This assumes, that you want to handle this special navigation logic in VC1.
You can use method
func setViewControllers(_ viewControllers: [UIViewController],
animated: Bool)
Here's the documentation (link) that solves the UIViewController skipping problem.
Simply pass all the needed UIViewControllers to it (in navigational order) and the last one will be made as the current active UIViewController (if it already isn't the active one).
My solution would be to keep a BOOL property of when you should skip to third and when not skip, like declaring ’shouldSkip’ in VC2 so that if u set it in prepare segue like below you can act according to that in VC2
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "JumpToThirdVCSegue":
secondvc.shouldSkip=true
}
default: break
}
}
}
and then in viewDidload VC2 you should check this BOOL and if it is true and perform segue and if not just move on
you could also pass pass whenever that kind of skip is not necessary
Building off of MarkHim's answer, I was getting a Black screen when navigating back to the inserted view controller so I came up with the following solution.
BTW - I wanted to insert the new view controller directly under the view controller I just segued to hence the navStack.count - 1 instead of navStack - 2.
Here is my solution:
override func performSegue(withIdentifier identifier: String, sender: Any?) {
super.performSegue(withIdentifier: identifier, sender: sender)
if identifier == "JumpToThirdViewControllerSegue",
let navController = navigationController { // unwrap optional
// initialize the view controller you want to insert
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewControllerToInsert = storyboard.instantiateViewController(
withIdentifier: "SecondVC") as! SecondViewController
// set any passed properties
viewControllerToInsert.passedProperty = propertyToPass
// create an object using the current navigation stack
var navStackArray: [UIViewController]! = navController.viewControllers
// insert the newly initialized view controller into the navStackArray
navStackArray.insert(viewControllerToInsert, at: navStackArray.count - 1)
// replace the current navigation stack with the one you just
// inserted your view controller in to
navController.setViewControllers(navStackArray, animated: false)
}
}
I'm really having trouble instantiating a custom view controller.
This is my storyboard set up. The third view controller is the one I'm trying to present.
I've tried both of these methods.
1: This results in black screen.
var searchController: SearchController = SearchController()
self.presentViewController(searchController, animated: true, completion: nil)
2: This results in a white, empty view controller popping up.
let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let searchController : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("searchController") as! UIViewController
self.presentViewController(searchController, animated: true, completion: nil)
Here is the code for the actual view controller:
class SearchController: UIViewController {
lazy var searchBar: UISearchBar = UISearchBar(frame: CGRectMake(0, 0, 200, 20))
override func viewDidLoad() {
super.viewDidLoad()
var leftItem = (UIBarButtonItem)(customView: searchBar)
self.title = "Search"
self.navigationItem.leftBarButtonItem = leftItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
It's very confusing because another custom view controller I am is presented properly while using method #1 above. Why wouldn't it work for this controller?
Thanks a lot everyone.
When using Storyboards you don't need to use presentViewController or instantiate your view controllers manually. Instead it's best to use a segue to move from one UIViewController to the next.
1. In your case, you want a show segue, which it looks like you've already done judging by your storyboard.
2. You need to give your segue an Identifier which you can do by selecting your segue and going to the Attributes Editor.
3. To perform your segue just call the following from your first view controller (instead of presentViewController).
self.performSegueWithIdentifier("YourIdHere", sender: self)
This will cause the storyboard to instantiate your SearchViewController and present it in the way that you've defined for that segue.
4. If you want to pass any values to your SearchViewController, you can override prepareForSegue in UIViewController. In your first view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let searchViewController = segue.destinationViewController where segue.identifier == "YourIdHere") {
// Now you can set variables you have access to in `searchViewController`.
}
}
And that should be it!
People, i want to pop a viewController using the normal back button on NavigationView controller without release the customized view that was created by the user, any one know some way to do that?
Because the natural flow of the navigation controller is release the "poped" viewController! Thanks for the help!
You need to retain a copy of the view controller elsewhere. Perhaps inside the class containing the navigation controller. Then push this back onto the stack when required.
Additionally check out UINavigationControllerDelegate
can you save the datas in the "popped" view controller? when it comes out again, populate it? When view controller is popped, it should be released.
You can navigation.viewControllers array copy in to global array before pop the custom view. After pop view global navigation.viewControllers assign global array.
NSArray create in AppDelegate
appDelegate.nav = self.navigationController.viewControllers;
[self.navigationController popViewControllerAnimated:YES];
then after assign global array in poped view
-(void)viewWillAppear:(BOOL)animated
{
self.navigationController.viewControllers = appDelegate.nav;
}
Well people tha answer is, make your controller from the StoryBoard, and don't use segue to call.
if(comparacao == nil)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle: nil];
comparacao = [storyboard instantiateViewControllerWithIdentifier:#"ComparacaoView"];
}
[self.navigationController pushViewController:comparacao animated:YES];
So with this, I every use the before instance created, and every thing that my user do in this view was maintained.
Swift Code:
#IBAction func pushButtonAction(_ sender: UIButton) {
if let exitingSecViewCon = secondViewController {
navigationController?.pushViewController(exitingSecViewCon, animated: true)
}else{
self.performSegue(withIdentifier: "second", sender: self)
}
}
var secondViewController: SecondViewController? //Keep Strong Pointer
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "second" {
if let sec = segue.destination as? SecondViewController {
sec.name = "ABC"
secondViewController = sec;
}
}
}
Console Log:(Same address for SecondViewController)
ADD=<RetainCount.SecondViewController: 0x7fb5ac630cf0>
ADD=<RetainCount.SecondViewController: 0x7fb5ac630cf0>
ADD=<RetainCount.SecondViewController: 0x7fb5ac630cf0>