Cheok Yan Cheng
Cheok Yan Cheng

Reputation: 42690

What is a good way to add UIPageViewController to parent UIViewController without considering status bar height?

Currently, I have a UIViewController, with its top component consists of a horizontal UICollectionView (MenuTabsView.swift)

enter image description here

Now, I would like to add a UIPageViewController, just below the MenuTabsView.

I have tried the following few approaches.


Programatically without taking status bar height into consideration

func presentPageVCOnView() {
    
    self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
    self.pageController.view.frame = CGRect.init(x: 0, y: menuBarView.frame.maxY, width: self.view.frame.width, height: self.view.frame.height - menuBarView.frame.maxY)
    self.addChildViewController(self.pageController)
    self.view.addSubview(self.pageController.view)
    self.pageController.didMove(toParentViewController: self)
    
}

Here's the outcome.

enter image description here

From 1st glance, it seems that UIPageViewController's view need to offset by Y status bar distance. (But why?)


Programatically by taking status bar height into consideration

func presentPageVCOnView() {
    let statusBarHeight = CGFloat(20.0)
    
    self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
    self.pageController.view.frame = CGRect.init(x: 0, y: menuBarView.frame.maxY + statusBarHeight, width: self.view.frame.width, height: self.view.frame.height - menuBarView.frame.maxY - statusBarHeight)
    self.addChildViewController(self.pageController)
    self.view.addSubview(self.pageController.view)
    self.pageController.didMove(toParentViewController: self)
}

Now, it looks way better.

enter image description here


Use container view without status bar offset

But, I don't feel comfortable, on why we need to manually consider status bar height, during programatically way. I was thinking, maybe I can add a ContainerView to UIViewController, and "attach" the UIPageViewController's view to it?

(I am not sure why during adding Container View to storyboard, an additional UIViewController will be added along. Anyhow, I just manually delete the additional UIViewController)

enter image description here

enter image description here

Then, I use the following code to "attach" the UIPageViewController's view to new container view.

func presentPageVCOnView() {

    self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
    self.pageController.view.frame = containerView.frame
    self.addChildViewController(self.pageController)
    self.view.addSubview(self.pageController.view)
    self.pageController.didMove(toParentViewController: self)
}

But, the outcome is not what as expected. Y offset still happen!!!

enter image description here


Use container view with status bar offset

I try to make sure, there are space of 20, between the top component MenuTabsViews and UIPageViewController's view.

enter image description here

enter image description here


I was wondering, is there any good practice/ solution, to ensure we can add UIPageViewController's view below another component, without affecting by status bar height?

Upvotes: 0

Views: 398

Answers (2)

DonMag
DonMag

Reputation: 77442

You can do this all without any code -- it just takes an understanding of how UIContainerView works.

There's no real UIContainerView class... it is an automated way of adding a child view controller via Storyboard / Interface Builder. When you add a UIContainerView, IB automatically creates a "default" view controller connected to the container view with an Embed segue. You can change that default controller.

Here's step-by-step (images are large, so you'll probably want to click them to see the details)...

Start with a fresh UIViewController:

enter image description here

Add your "Menu Bar View" - I have it constrained Top/Leading/Trailing to safe-area, Height of 60:

enter image description here

Drag a UIContainerView onto the view - note that it creates a default view controller at the current size of the container view. Also note that it shows a segue. If you inspect that segue, you'll see it is an Embed segue:

enter image description here

Constrain the Top of the container view to the Bottom of your Menu Bar View, and Leading/Trailing/Bottom to safe-area. Notice that the size of the embedded view controller automatically takes the new size of the container view:

enter image description here

Select that default controller... and delete it:

enter image description here

Drag a new UIPageViewController onto your Storyboard and set its Custom Class to PageControllerVC:

enter image description here

Now, Ctrl-Click-Drag from the Container view to the newly added page view controller. When you release the mouse button, select Embed from the popup:

enter image description here

You now have an Embed segue from the container view to your page view controller. Notice that it automatically adjusted its size to match the container view size:

enter image description here

Since the Menu Bar View top is constrained to the safe-area, it will behave as expected.

Since the container view top is constrained to the bottom of the Menu Bar View, it will stay there, and should give you what you want.

No Code Needed :)


Edit

The most likely reason you ran into trouble with loading via code is with you frame setting.

If you try to set frames in viewDidLoad(), for example, auto-layout has not configured the rest of the view hierarchy... so framing will not be what you expect.

You're much better off using auto-layout / constraints, rather than setting explicit frames anyway.

Here is how I would do it from code (assumes you have your "Menu Bar View" connected via @IBOutlet):

class ViewController: UIViewController {
    
    @IBOutlet var menuBarView: UIView!
    
    var pageControllerVC: PageControllerVC?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as? PageControllerVC else {
            fatalError("Could not instantiate PageControllerVC!!!")
        }
        guard let v = vc.view else {
            fatalError("loaded PageControllerVC had no view ????")
        }
        addChild(vc)
        view.addSubview(v)

        v.translatesAutoresizingMaskIntoConstraints = false
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            v.topAnchor.constraint(equalTo: menuBarView.bottomAnchor),
            v.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            v.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            v.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ])

        vc.didMove(toParent: self)
        
        self.pageControllerVC = vc

    }
    
}

Upvotes: 1

Vitalii Shvetsov
Vitalii Shvetsov

Reputation: 422

You should remove safeArea pinning for pageVC. Safe area includes status bar and iPhone 11+ top space

tabBar.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor)
// to this
tabBar.topAnchor.constraint(equalTo: self.view.topAnchor)

And in storyboards change Safe Area to SuperView

Upvotes: 0

Related Questions