having 2 or more navigation controllers embedded on a stack - ios

Im trying to get two navigation controller on the same stack, and pass data between them , the best way to explain this is by the image below .
first nav controller(the one thats furthest to the left ) is connected to a TabBarController .
When passing data from the TEST tableViewController to the 2nd tableViewController (subDuaList) , i get an error in my code in the prepareForSegue method ...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var subDua:subDuaList = segue.destinationViewController as subDuaList //ERROR OCCURS HERE
var Index = tableView!.indexPathForSelectedRow()?.row
var duaIndex = 130 - Index!
var selectedDua = self.packagedChapter[duaIndex]
println(selectedDua.title)
subDua.duaArray = selectedDua.dataArray
subDua.rowPressed = Index
}
Reason for embedding the second tableViewController in a Navigation controller is so that I can add a bar button item to the nav bar.

EDIT
As #Shim commented right, my answer just explains the reason for the error output, but does not tell the things I already said in my comment on your question:
It is not allowed to push a UINavigationController on an existing navigation stack. Therefore you should remove the second navigation controller and just add a UIBarButtonItem to the navigationItem of your subDuaList view controller.
The reason for your error is, that the segue.destinationViewController is the second UINavigationController and not the subDuaList view controller.
If it was allowed, you could have repaired your code as follows by replacing:
var subDua:subDuaList = segue.destinationViewController as subDuaList //ERROR OCCURS HERE
with
var navCtrl:UINavigationController = segue.destinationViewController as UINavigationController
var subDua:subDuaList = navCtrl.childViewControllers[0] as subDuaList

You don't need a new navigation controller to add a bar button item. You can add a bar button item in the storyboard by dragging it onto the navigation bar (if the view controller is in a navigation stack and you don't see one, check the simulated metrics in the inspector), or you can add it in code like so:
self.navigationItem.rightBarButtonItem = myBarButtonItem;

Related

How to hide Tabbarcontroller's first view controller and go directly to the next controller but should show the tab bar items at the bottom

I have a view controller as my initial view controller. there's a button in it(GO button) which when the user taps, it should go to another view controller(let's call it Destination view controller with label 'This is where i wanna go'). Meanwhile i want to pass it through a Tabbar controller. The reason is i want to have tabbar in my navigation stack.
I wish to go directly to the Destination view controller while pressing go button but it should show the tab bar items at the bottom.
So for achieving this in FirstViewController didLoadMethod I checked a bool value and pushed the view controller to the Destination view controller. I achieved the result I.e when pressing the Go button it goes to the Destination view controller and has tab bar items at it's bottom.
But the problem since it passes through the Tabbarcontroller the FirstViewController is shown for some seconds and then it pushes to the Destination view controller. I wish to hide FirstViewController while this transition takes place.
How to achieve this?
Picture shows what i want. what can I do to hide firstviewcontroller while having it in navigation stack?
I think this can be done in a simple way -
In the first viewController of the tab bar has a viewDidLoad() function or you can use loadView() which is called before the viewDidLoad() function. Push to the next viewController in the function.
Put your push navigation code in one of those functions
*You cant see the current view coltroller, it will push the screen to your required viewcontroller before loading the tab bar initial view contoller.
Hope it will work for you.
or >>>> you can check it out
let storyboard = UIStoryboard(name: "your_storyBoard_name", bundle: nil)
let viewController1 = storyboard.instantiateViewController(withIdentifier: "firstViewController")
let viewController2 = storyboard.instantiateViewController(withIdentifier: "secondViewcontroller")
let controllers = [viewController1, viewController2]
self.navigationController!.setViewControllers(self.navigationController!.viewControllers + controllers, animated: true)
The effect you're trying to produce is hard to do in a storyboard. Programmatically you would simply create the Tabbar Controller (with its children) and the "This is where I want to go" Controller, and then ask the navigation controller to show both at the same time.
For example, after "Go" is tapped, this is the code I would run inside your first view controller:
let tabBarController = UITabBarController()
let finalDestination = UIViewController()
var viewControllers = self.navigationController?.viewControllers ?? []
viewControllers.append(tabBarController)
viewControllers.append(finalDestination)
self.navigationController?.setViewControllers(viewControllers, animated: true)
Given the structure you shown, where the view controller A is the root view controller of the TabBar, you should push the second view controller B on the navigation stack inside either willAppear or didLoad of view controller A, according to your personal business logic (flag, conditions, etc.).
The trick here is to use either pushViewController or setViewControllers with animated: false so that the navigation stack will be set immediately during willAppear/didLoad and it won't show the push animation of B over A. This way, at onDidAppear the layout will be already fully rendered in it's final state: with B at the top of the navigation stack and no animations in progress.

Instantiate View controller with its tab bar from unlinked viewcontroller

I have a viewController related to a Tab Bar Controller: the first one.
Clicking on a cell of its tableview, I'll show programmatically another viewController that's not linked to the first viewController with no segue (because of right reasons).
Now, my goal is to present/instantiate the second viewController related to the tab bar mentioned at the beginning of this question.
If I'll use this:
let vc=storyboard?.instantiateViewController(withIdentifier: "offerteView") as! SecondViewController
It'll be presented the mentioned viewController without the tab bar of course.
How can I solve it?
Embed the first view controller in a navigation controller and use its pushViewController function to show the second view controller.
let vc = storyboard?.instantiateViewController(withIdentifier: "offerteView") as! SecondViewController
navigationController?.pushViewController(vc, animated: true)
when using tab bars the view controllers are called on the basis of their Index and because of this the tab bars are still maintained and this can be done like this.
self.tabBarController!.selectedViewController! = self.tabBarController!.viewControllers[3]
where [3] is the index position of the View Controller.
or
self.tabBarController.selectedIndex = 1;
//Hope it was helpful. Happy Coding.

UINavigationController "acting crazy" after I added a new initial UIViewController

I had a UINavigationController set as initial view controller. The root view controller for that navigation controller shows a menu where users can see product categories (In case that's interesting for anyone :)). The root view controller loads JSON data from an URL, so it takes a little while until the view shows up (which is fine). That data will be use in all the app, so it's an important step.
Well, since the data load takes a while, I added a new UIViewController where I show an image with an UIActivityIndicator. Of course, the data load takes place in this UIViewController now instead of the root view controller.
So, the idea is that after the data is loaded, the root view controller takes the place. To accomplish this I put an "invisible" UIButton on the new view controller and added a UIStoryboardSegue to the navigation controller, so I trigger that segue programmatically after the data is loaded.
After the data gets loaded, the navigation controller actually appears and shows (of course) the root view controller. The problem is when I click any button on the root view controller, the segue performs correctly, but from the "second" view controller (UITableViewController) if I press any cell, it takes me back to the new view controller (the one that shows an image and loads the data). Well not actually any cell, because sometimes it takes me actually forwards to the "third" view controller (also UITableViewController), but the same problem occurs in this view controller. If I click the back button on the navigation bar in any view controller, it takes me back to the new view controller and not really back in the navigation stack.
Here is ViewDidLoad:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
load.loadAllProducts()
load.loadCategories()
self.performSegueWithIdentifier("mainStoryboardSegue", sender: self)
}
And PrepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as? UINavigationController
let homeViewController = destination!.viewControllers.first as? HomeViewController
homeViewController!.categories = load.categories
homeViewController!.products = load.products
}
I really appreciate any help, thanks a lot!
The solution that worked for me was to change the current active view controller by setting this:
let newController = storyboard.instantiateViewControllerWithIdentifier(storyboardId) as? UINavigationController
UIApplication.sharedApplication().keyWindow!.rootViewController = newController

Passing data between navigation controller and tab controller [duplicate]

This question already has answers here:
Pass data through view controller, tab bar controller, navigation controller and to view controller
(2 answers)
Closed 8 years ago.
If a storyboard contains a view in a navigation controller that segues to another view controller already embedded in a tab controller how can a variable be passed from the initial view to the first view in a tab controller?
Specifically, I am looking to pass a variable using a basic performSegueWithIdentifier approach. By connecting the first view controller (embedded in a navigation controller) to the tab controller, I am unable to pass the data. However, if the connection is made directly between the first view controller and the destination view controller, the destination is no longer embedded in the tab.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "internalItemDetail" {
let destination = segue.destinationViewController as InternalInventoryDetail
let index = itemTable.indexPathForSelectedRow()!
destination.fmRecordId = sectionedItemArray[index.section][index.row]["fmRecordId"] as String!
}
}
To be clear, the goal is to pass a variable from a navigation controller into another view which is embedded in a tab controller while the UI would be showing a tab controller inside a navigation controller.
The UIStoryBoardSegue has a destinationViewController property that will give you the target view controller- in your case a UITabBarController (if I'm reading your question correctly). You'll have to cast it to a UITabBarController and from there you can access its subviews. (Code in objective c but should be easy to convert, I've just never used swift)
UITabBarController *tabControl = (UITabBarController *)segue.destinationViewController;
MyViewController *mv = (MyViewController *)tabControl.viewControllers[0];// whatever index you want

Swift - Use segmented control to navigate to different view controllers

I've got an app with a mapViewController embedded in a navController. In the mapVC ive got a single bar button item which when clicked I want to conditionally "push" segue to one of a number of different view controllers. To achieve that ive set up an ibaction on the button and have the conditional "performSegueWithIdentifier" code in the relevant buttons ibaction method ie
#IBAction func addButtonClicked(sender: UIBarButtonItem) {
let lastAdd = "addItem"
if lastAdd == "addItem"{
self.performSegueWithIdentifier("addItem", sender: self)
} else {
self.performSegueWithIdentifier("addEvent", sender: self)
}
}
this will take me to either the addItemVC or the addEventVC. in each of those viewControllers (ie the addItemVC and the addEventVC) I want to have a segmented control in the navigation bar which, when clicked, will take me to the alternative VC ie if addItemVC is currently displayed, and the addEvent section of the segmented control is clicked, I want to display the addEventVC. Im following Red Artisans page on how to do this but in his example he is instantiating all view controller options upfront in the app delegate and so can easily get reference to each view controller and link it to the clicked segement of the segmented control within his rootVC
Where im confused is .. seeing Im using conditional code before performing each segue, i assume that im only instantiating one viewController at a time when the bar button item is pressed. So how can i get an array of view controllers to pass to the VC im segueing to so that i can create the required segmented control in that VC. I assume i could manually create the destination VC array in my mapViewController and pass these across but wouldnt that mean im instatiating a different instance to the ones automatically created by the segue process?
Yes, you are right: if you manually create the two VCs in your mapViewController, they will be different instances from those created by a segue. So if you want to stick with Red Artisan's solution, present the VCs using code rather than segues. You can still design the two VCs in your storyboard, give them each a unique identifier and then use the instantiateViewControllerWithIdentifier function of self.storyboard to create the instances.
You can use most of Red Artisan's app delegate code in your mapViewController, but with a few tweaks: eg. to use the existing navigation controller (in which your mapViewController is embedded), and the [window ...] lines are superfluous. The thing to watch out for will be the indexDidChangeForSegmentedControl function, which assumes that the VCs you are switching between are the rootViewControllers for the navigation controller (ie. that they are the only item in the navigation controller's viewControllers array). In your case you have mapViewController as (I assume) the rootViewController, so you will have to amend the indexDidChangeForSegmentedControl function to create an array with the mapViewController at index 0 and the relevant (addItem or addEvent) VC at index 1. I don't know how well this method will animate, nor whether back buttons etc will be properly set.
If you want to stick with segues, there are a couple of solutions: one would be to use a UITabBarController (and hide the tabBar). You would have the addItem and addEvent VCs as separate tabs, and when you segue to the tabBarController, you could set which tab is selected. But my preferred solution would be to segue to a UIPageViewController. You would could either create the VCs in mapViewController and pass them as part of the segue, or just pass an indicator as to which was selected, and have the pageViewController instantiate them and present the relevant one. You could then use the UISegmentedControl to trigger switching between VCs. See this answer for something similar.
thanks pbasdf for your detailed instructions. its taken me quite a while but i seem to be close to getting it working. i followed most of your instructions and you were spot on with what you said.
first from the mapVC on press of the + bar button item i create the addItem and addEvent VCs using instantiateViewControllerWithIdentifier and create the segmented control.
#IBAction func addButtonClicked(sender: UIBarButtonItem) {
//at this stage just manually set default target VC
let lastAdd = "addItem"
//get an array of the target viewcontrollers
var viewControllers = segmentViewControllers()
//initz the segmentscontoller with the current navcontroller if doesnt already exist
if segmentsController == nil {
segmentsController = SegmentsController(navController: self.navigationController!, viewControllers: viewControllers)
segmentedControl.addTarget(segmentsController, action: "indexDidChangeForSegmentControl:", forControlEvents: UIControlEvents.ValueChanged)
//add the segmented control to the VC by setting first user experience which calls indexdidchangeforsegmentedcontrol
firstUserExperience()
}
segmentsController?.indexDidChangeForSegmentControl(segmentedControl)
}
//create an array of the target view controller. called from addbutton clicked
func segmentViewControllers() -> [UIViewController] {
//create an instance of the viewcontrollers
let addItemVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("AddItemVC") as ViewController
let addEventVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("AddEventVC") as ViewController
var viewControllers = [addItemVC, addEventVC]
return viewControllers
}
in the segmentsController i created 2 arrays, 1 to hold the different VCs and one to hold the navigation stack. i also set the passed the segmented control to the incoming VCs
class SegmentsController: NSObject {
var navController: UINavigationController?
var viewControllersOptionsArray: [UIViewController] = []
var viewControllersNavArray: [UIViewController] = []
//MARK: - INITIALIZER
init(navController: UINavigationController, viewControllers: [UIViewController]) {
self.navController = navController
self.viewControllersOptionsArray = viewControllers
}
//MARK: SEGMENT INDEX METHOD
func indexDidChangeForSegmentControl(segmentedControl: UISegmentedControl) {
var index = segmentedControl.selectedSegmentIndex
var incomingViewController = viewControllersOptionsArray[index]
//set the viewControllersNavArray
if let mapVC = navController?.viewControllers[0] as? MapViewController {
viewControllersNavArray = [mapVC, incomingViewController]
}
//set the navcontroller with a new array of viewcontrollers
navController?.setViewControllers(viewControllersNavArray, animated: true)
//set the title of the incoming view controller
incomingViewController.navigationItem.titleView = segmentedControl
//set the seg control variable of the incoming VCs
if let iVC = incomingViewController as? AddItemViewController {
iVC.segmentedControl = segmentedControl
} else if let iVC = incomingViewController as? AddEventViewController {
iVC.segmentedControl = segmentedControl
}
}
}
In the addItem and addEvent VCs i figured i needed to pass the current selectedSegmentIndex back to the mapVC if the user presses the back button - wasnt sure how to do this and ended up using an extension i downloaded called UIViewController+BackButtonHandler to handle it.
Im sure my code could be much better written but the only thing that im still having trouble with is that i want the VC transitions to be animated. the navigation seems to work fine if i set animated to false in the navController?.setViewControllers(viewControllersNavArray, animated: true) line but if i set it to true, the segmentedControl briefly flashes an appearance on the nav bar of the incoming VC but then disappears. Its still there so i can still navigate but you cant see it. i figure that it has something to do with setting it before the view has properly loaded but even if i put the code to set it ie
incomingViewController.navigationItem.titleView = segmentedControl
in the new top VC in viewDidLoad or viewDidAppear i still get the same problem. i also thought i might be able to fix it if i could put the incomingViewController.navigationItem.titleView = segmentedControl code in a completion block but the setViewControllers method doesnt appear to have a completion handler.
Any suggestions?

Resources