Search code examples
iosswiftuiscrollviewautolayoutuicontainerview

UIScrollView paging layout on iPad and iPhone


I am making an universal demo xcode project which is a UIScrollView contains two pages, scroll left and right to go back and forth.

My questions is iPad and iPhone's layout is not the same.

  1. I created 3 view controllers in storyboard as below:

enter image description here

ViewController has the UIScrollView.

AViewController contains an UILabel in the center("Good morning...").

BViewController contains an UILabel in the center("Good night...").

  1. The constraints of the UILabels are:

enter image description here

  1. Here is the code of ViewController:

    class ViewController: UIViewController {
    
        @IBOutlet weak var scrollView: UIScrollView!
        override func viewDidLoad() {
            super.viewDidLoad()
            let story = UIStoryboard(name: "Main", bundle: nil)
            let vc1 = story.instantiateViewController(withIdentifier: "AViewController")
            let vc2 = story.instantiateViewController(withIdentifier: "BViewController")
            vc1.view.backgroundColor = UIColor.green
            vc2.view.backgroundColor = UIColor.red
            addContentView([vc1, vc2])
            scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width * 2, height: scrollView.frame.height)
        }
    
        func addContentView(_ viewControllers: [UIViewController]) {
            viewControllers.enumerated().forEach {
                addChildViewController($1)
                $1.view.frame = CGRect(x: UIScreen.main.bounds.width * CGFloat($0), y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
                scrollView.addSubview($1.view)
                didMove(toParentViewController: $1)
        }
    }
    

    }

  2. If I choose View as iPhone 8 then the layout works correct in iPhone 8 like this: (Swipe then from green to red)

enter image description here

But for iPad:

enter image description here

The labels are not centered!!.

If I choose View as iPad ... ,then for iPhone it doesn't layout correct.

Here is the view hierarchy, it's obvious the layout is correct but for some reason the green view is larger than screen size and covered by the red one.

enter image description here

Thanks for any help.

The code is here: https://github.com/williamhqs/TestLayout


Solution

  • The embedded view controller views have translatesAutoresizingMaskIntoConstraints = true by default which introduces bogus constraints: AutoresizingMask Constraints Setting it to false will not let you just set the frame manually, so you will have to write the full layout. It's slightly more verbose, but autolayout will work for the embedded view controllers and you get rotation support as well:

    @IBOutlet weak var scrollView: UIScrollView!
    override func viewDidLoad() {
        super.viewDidLoad()
        let story = UIStoryboard(name: "Main", bundle: nil)
        let vc1 = story.instantiateViewController(withIdentifier: "AViewController")
        let vc2 = story.instantiateViewController(withIdentifier: "BViewController")
        vc1.view.backgroundColor = UIColor.green
        vc2.view.backgroundColor = UIColor.red
        addContentView([vc1, vc2])
    }
    
    func addContentView(_ viewControllers: [UIViewController]) {
        var previousController: UIViewController? = nil
        viewControllers.forEach {
            addChildViewController($0)
            $0.view.translatesAutoresizingMaskIntoConstraints = false
            scrollView.addSubview($0.view)
    
            $0.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
            $0.view.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
            if let trailing = previousController?.view.trailingAnchor {
                $0.view.leadingAnchor.constraint(equalTo: trailing).isActive = true
            } else {
                $0.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
            }
            $0.view.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
            $0.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
            didMove(toParentViewController: self)
            previousController = $0
        }
        previousController?.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
    }
    

    Scroll view works as expected for all devices, regardless of the Xcode View as setting: enter image description here enter image description here