Reputation: 853
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?
Upvotes: 0
Views: 4252
Reputation: 853
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?
Upvotes: 0
Reputation: 21536
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.
Upvotes: 1