1

I have a view controller with a container view that has a tab bar controller embedded in it. Im using this to display 3 different view controllers based on what is pressed from a segmented control in the main vc. I have been following this solution: https://stackoverflow.com/a/38283669/11536234

My problem is that when I change the segmented control index (by pressing a different segment) I can't figure out how to "reach" the prepare function.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    print("prepare reached")

    //super.prepare(for: segue, sender: sender)
    //switch(segue.identifier ?? "") {
    //case "TabBar":

        guard let TabController = segue.destination as? UITabBarController else {
            fatalError("Unexpected destination: \(segue.destination)")
        }

        TabController.selectedIndex = toggle.selectedSegmentIndex

    //default:
        //fatalError("Unexpected Segue Identifier; \(segue.identifier)")
    //}
}

@IBAction func toggleAction(_ sender: UISegmentedControl) {
    print("toggle is now at index: ", toggle.selectedSegmentIndex)
    //performSegue(withIdentifier: "TabBar", sender: sender)
    //container.bringSubview(toFront: views[sender.selectedSegmentIndex])
}

So far i have tried placing a performsegue function in an action function linked to the segmented control. This doesn't work, however, because it essentially adds another embedded tab bar programmatically or calls the embed segue again and I receive this error statement: "There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?"

*The commented lines of code are there to show what I've tried that hasn't worked vs where I'm at.

Brahm
  • 13
  • 3

2 Answers2

0

You can't do what you are trying to do.

With embed segues the segue fires when the host view controller is first loads. That invokes your prepare(for:sender) method, once and only once, before the embedded view controller's views are loaded. It doesn't get called again.

What you need to do is to save a pointer to your child view controller in an instance variable. Define a protocol that the parent uses to talk to the child, and then use that protocol to send a message to the child when the user selects a different segment in your segmented control.

Then the child (presumably the tab bar controller) can switch selected tabs in response to the message you define.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • Sorry I'm fairly new to swift and I'm a little confused. What was the person in the answer that I was following saying about using the prepare statement to switch the tab then? Also going with what you're saying, would that work like in my action function for the segmentedcontrol like: pointer.tabcontroller.selectedIndex = toggle.selectedSegmentIndex? Would that work as the protocol you're describing? (pointer being the variable used to describe the child view controller) – Brahm May 21 '19 at 22:20
  • As far as I can tell the person in the answer you were following was giving bad advice. You might be able to alter the _initial_ view controller that's displayed in the tab bar controller. (I've never tried that, but I guess it would work.) The point is that `prepare(for:sender)` gets called once and only once for each embed segue or other segue. – Duncan C May 21 '19 at 23:07
  • "What you need to do is to save a pointer to your child view controller in an instance variable" Oh, I don't think that's very good advice. You already have such a pointer; it is your `children[0]`. Defining another pointer is a good way to mess things up. – matt May 22 '19 at 03:44
  • thanks for responding. I understand what i was doing wrong thanks to your comments. His program was different as was his goal. What i was following was for initial setup based on a button press (i believe) whereas what i needed was an adjustable method. Thanks for helping me understand. I upvoted but i have no reputation and it's not showing sorry. – Brahm May 23 '19 at 16:57
  • If I've answered your question you should accept my answer. That you CAN do with low reputation. You only need like 16 points of rep to vote, so you should be able to do it soon. – Duncan C May 23 '19 at 19:27
0

When you embed a view controller to a container view from another view controller(MainVC), the segue is performed only once when MainVC loads. To pass values to the embedded UIViewController/UITabBarController, you need to get the child view controller and send the data

class MainVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    @IBAction func segmentControlAction(_ sender: UISegmentedControl) {
        if let tabBarVC = self.children.first(where: { $0 is UITabBarController }) as? UITabBarController {
            tabBarVC.selectedIndex = sender.selectedSegmentIndex
        }
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        //called before mainvc viewDidLoad
        let destinationVC = segue.destination as? UITabBarController
        destinationVC?.selectedIndex = 1
    }
}

enter image description here

RajeshKumar R
  • 15,445
  • 2
  • 38
  • 70
  • Thanks! Duncan helped me realize this was the direction i needed to go in and your code helped me reach my childVC...just to understand what is going on here: what is the purpose of the $0 is UITabBarController statement? It seems redundant to write that statement along with the as? UITabBarController? Couldn't I just do if let tabVC = self.childViewControllers[0] as? UITabBarController – Brahm May 23 '19 at 17:14
  • @Brahm Yes you can use. But I've used it because I don't know whether you have any other child view controller in MainVC or not. But checking `$0 is UITabBarController` is safer – RajeshKumar R May 23 '19 at 17:30
  • Gotcha thanks, yea I only have the tabBarController as a child to the main then three children to the tabBarController. – Brahm May 23 '19 at 17:43