One possible solution I arrived at with the help of http://sandmoose.com/post/35714028270/storyboards-with-custom-container-view-controllers
In your entry point view controller, place a container view that has auto layout constraints aligning the top and bottom of the container view to the top and bottom layout guides.
The container view itself then needs to embed any view controller that you segue to. This way destination view controllers will always live in the confines of the container view. The container view will also be constrained correctly by the top and bottom layout guides of the initial view controller.
One way to achieve this:
Implement a protocol that will receive notifications for segues - something like:
protocol SegueDelegate {
func willSegue(to: UIViewController)
func didSegue(to: UIViewController)
}
Implement a subclass of UIViewController that has a reference to SegueDelegate e.g.
class ContainedViewController: UIViewController {
weak var segueDelegate: SegueDelegate?
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
segueDelegate?.willSegue(to: segue.destination)
}
}
Have your initial messages view controller implement the protocol.
extension MSMessagesAppViewController: SegueDelegate {
func didSegue(to destination: UIViewController) {
guard let destination = destination as? ContainedViewController else {
return
}
// Reference through IBOutlet or something
containerViewController.embed(destination)
}
}
The containerViewController here must implement an embed method that swaps the displaying view controller with the new one e.g.
class ContainerViewController: UIViewController {
...
func embed(_ viewController: UIViewController) {
let source = childViewControllers.first
if source == viewController {
return
}
source?.willMove(toParentViewController: nil)
addChildViewController(viewController)
if let source = source {
// Do transition here if you want
viewController.view.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height)
view.addSubview(viewController.view)
viewController.didMove(toParentViewController: self)
source.willMove(toParentViewController: nil)
source.removeFromParentViewController()
} else {
viewController.view.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height)
view.addSubview(viewController.view)
viewController.didMove(toParentViewController: self)
}
// Assigned in viewDidLoad of MSMessagesAppViewController or similar
segueDelegate?.didSegue(to: viewController)
}
}