Search code examples
iosswiftuinavigationbarchildviewcontrollerimessage-extension

UINavigationController as subview to MSMessagesAppViewController


I'm creating an iMessage extension where for the expanded presentation style I want to use a UINavigationController. I'm trying to add the navigation controller as a subview of the MSMessagesAppViewController using the function below:

private func present(viewController: UIViewController) {
    viewController.view.frame = view.frame

    addChildViewController(viewController)
    viewController.didMove(toParentViewController: self)

    view.addSubview(viewController.view)
}

I was expecting the navigation controller to be displayed in traditional fashion, with its navigation bar starting just below the top bar of iMessage itself. Please see the following illustration for my expected result:

Expected result

However, it seems view.frame actually extends underneath the iMessage top bar. As such, the navigation bar of my navigation controller is hidden underneath the top bar of iMessage. The content view of the scroll view itself is however positioned correctly without further customization. Please see the following illustration for the actual result:

Actual result

Obviously, I could set the frame of the navigation controller to begin just below the top bar. However I'm looking for solutions that does not rely too much on hard coded positions. If doing this, the scroll view will also not continue underneath the iMessage top bar for that translucent effect.

Ideally the navigation bar will be displayed just below the iMessage top bar, just like the scroll view does by default.

Does anyone know a solution to this?


Solution

  • After a bit of deliberation, I have found one quite hacky solution. I'm still accepting feedback and other answers.

    One can achieve the above by creating a preliminary layoutView, which is constrained to the layout guides using AutoLayout. Then, by setting the UINavigationController's frame to match that of the layout view, it will in effect only occupy the visible area.

    You can set up a view like mine using Storyboard as follows:

    LayoutView layout

    Then in your container view controller (createStickerViewController in my case) simply assign the frame of your UINavigationController to that of your layout view as soon as the constraints have been satisfied:

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        // Update frame after constraints have been satisfied for layoutView
        pickAssetNavigationController.view.frame = layoutView.frame
    }
    

    The result is that the navigation controller only occupies that actual visible area, and as such the navigation bar is placed just below the iMessage top bar as expected (refer to figure 1 in original question).