Search code examples
iosswiftuitableviewmkmapview

Cannot switch to another child view controller in container view


I have a main view controller, a container view and 2 child view controllers and i would like to be able to switch between the children (for example: when the application loads for the first time, i would like that the controller containing the MapView to be loaded and when i press the Search Bar found in the main view, the controller with the table to be loaded). Here is my storyboard: https://i.sstatic.net/rDPMe.png

MainScreen.swift

class MainScreen: UIViewController {

@IBOutlet private weak var searchBar: UISearchBar!
@IBOutlet private weak var ContainerView: UIView!
//private var openSearchBar: Bool?
private var openMapView: Bool = true
private var openPlacesList: Bool = false
private var containerView: ContainerViewController!

override func viewDidLoad() {
    super.viewDidLoad()
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    //let containerView = segue.destination as? ContainerViewController
    if containerView == nil{
        containerView = segue.destination as? ContainerViewController
    }
    if openMapView == true{
        containerView!.moveToMapView()
    }
    else if openPlacesList == true{
        containerView!.MoveToOpenPlaces()
    }
  }
}
//search bar delegate functions
extension MainScreen: UISearchBarDelegate{
//detects when text is entered
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
    openPlacesList = true
    openMapView = false
    containerView!.MoveToOpenPlaces()
  }
}

ContainerViewController.swift:

class ContainerViewController: UIViewController {

private var childViewController: UIViewController!

private var first: UIViewController?
private var sec: UIViewController?

override func viewDidLoad() {
    super.viewDidLoad()
}

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

    if segue.identifier == "MainToMap"{

        first = segue.destination as! MapViewController
        self.addChild(first!)
        self.view.addSubview(first!.view)
        self.didMove(toParent: self)
    }else{
       sec = segue.destination as! PlacesListController
    }

    if(first != nil && sec != nil){
        interchange(first!,sec!)
    }
}

func interchange(_ oldVc: UIViewController,_ newVc: UIViewController ){
    oldVc.willMove(toParent: nil)
    self.addChild(newVc)
    self.view.addSubview(newVc.view)

    self.transition(from: oldVc, to: newVc, duration: 2, options: UIView.AnimationOptions.transitionCrossDissolve, animations: {
        newVc.view.alpha = 1
        oldVc.view.alpha = 0
    }, completion: { (complete) in
        oldVc.view.removeFromSuperview()
        oldVc.removeFromParent()
        newVc.willMove(toParent: self)
    })
}

func moveToMapView(){
    performSegue(withIdentifier: "MainToMap", sender: nil)
}

func MoveToOpenPlaces(){
    performSegue(withIdentifier: "MainToSearches", sender: nil)
}

}

The problem is that when I press the search bar, it calls the method interchange and then it just gives a SIGABRT 1 error. I tried this tutorial: https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html#//apple_ref/doc/uid/TP40007457-CH11-SW1 and many more but so far no luck. I am stucked here and don't know how i can solve this problem.

Stack: https://i.sstatic.net/Zqpm1.png SIGABR 1 Error: https://i.sstatic.net/NBgEN.png


Solution

  • You appear to be trying to manually transition between child view controllers, but at the same time using segues (which do their own transitioning for you). Eliminate the segues (other than the initial embed segue, if you're using a storyboard with a "container view"), and just manually instantiate the child view controllers using their storyboard IDs. But don't use segues and then try to replace the child view controllers in prepare(for:sender:).

    Also, when you use transition(from:to:duration:options:animations:completion:), you should not add the views the the view hierarchy yourself. That method does that for you (unless you use the showHideTransitionViews option, which tells the method that you're taking this over, something we don't need to do here). Likewise, when you use the transitionCrossDissolve option, you don't need to mess with alphas, either.


    Thus, using the code snippet from that article you reference, you can do:

    class FirstViewController: UIViewController {
    
        @IBOutlet weak var containerView: UIView!  // the view for the storyboard's "container view"
        @IBOutlet weak var redButton: UIButton!    // a button to transition to the "red" child view controller
        @IBOutlet weak var blueButton: UIButton!   // a button to transition to the "blue" child view controller
    
        // tapped on "transition to red child view controller" button
    
        @IBAction func didTapRedButton(_ sender: UIButton) {
            redButton.isEnabled = false
            blueButton.isEnabled = true
    
            let oldVC = children.first!
            let newVC = storyboard!.instantiateViewController(withIdentifier: "RedStoryboardID")
            cycle(from: oldVC, to: newVC)
        }
    
        // tapped on "transition to blue child view controller" button
    
        @IBAction func didTapBlueButton(_ sender: UIButton) {
            blueButton.isEnabled = false
            redButton.isEnabled = true
    
            let oldVC = children.first!
            let newVC = storyboard!.instantiateViewController(withIdentifier: "BlueStoryboardID")
            cycle(from: oldVC, to: newVC)
        }
    
        func cycle(from oldVC: UIViewController, to newVC: UIViewController) {
            // Prepare the two view controllers for the change.
            oldVC.willMove(toParent: nil)
            addChild(newVC)
    
            // Get the final frame of the new view controller.
            newVC.view.frame = containerView.bounds
    
            // Queue up the transition animation.
            transition(from: oldVC, to: newVC, duration: 0.25, options: .transitionCrossDissolve, animations: {
                // this is intentionally blank; transitionCrossDissolve will do the work for us
            }, completion: { finished in
                oldVC.removeFromParent()
                newVC.didMove(toParent: self)
            })
        }
    
        func display(_ child: UIViewController) {
            addChild(child)
            child.view.frame = containerView.bounds
            containerView.addSubview(child.view)
            child.didMove(toParent: self)
        }
    
        func hide(_ child: UIViewController) {
            child.willMove(toParent: nil)
            child.view.removeFromSuperview()
            child.removeFromParent()
        }
    
    }
    

    That yields:

    enter image description here