Embedded UIPageController child VC has wrong frame origin

531 Views Asked by At

I am trying to create a layout element like below in the middle of a page and leverage UIPageViewController to do it.

Desired layout

I embedded a UIPageViewController in a container view. The contents of the card element have a height determined by AutoLayout, and stretch horizontally towards the margins, with the card having a max width after which it stays centered. However I'm running into a strange problem. When I set the VC for the card element to be the contents of my UIPageViewController, the individual page gets mostly sized correctly, but its origin is outside the bounds of the pageVC's content view.

I recreated the problem in a very small test project. Here is the storyboard and relevant constraints:

enter image description here

Here is my code:

class ViewController: UIViewController {

    var pageController: UIPageViewController!

    override func viewDidLoad() {
        super.viewDidLoad()
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "PageView") else { return }
        pageController.setViewControllers([vc], direction: .forward, animated: true, completion: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let dest = segue.destination as? UIPageViewController {
            pageController = dest
        }
    }

}

class SinglePageViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.translatesAutoresizingMaskIntoConstraints = false
    }

}

And here is what I am actually getting:

enter image description here

I tried setting view.autoresizingMask = [.flexibleWidth, .flexibleLeftMargin, .flexibleRightMargin] instead of view.translatesAutoresizingMaskIntoConstraints = false in SinglePageViewController, and that gives it correct leading, trailing, and width constraints, but then I can't get the UIView-Encapsulated-Layout-Height: self.height = 0 constraint to go away. There are no system constraints that account for the origin misplacement, so I have no idea how to debug further. All the views higher up in the hierarchy have height 0, and all the views farther down are placed correctly. If I embed the SinglePageViewController directly in the container view, it lays out perfectly, but if I put the UIPageViewController in between, it breaks.

Why doesn't the UIPageViewController constrain the pages inside it to match its view's bounds? Is there any way to get this to work?

1

There are 1 best solutions below

5
DonMag On

First, you should not do this:

override func viewDidLoad() {
    super.viewDidLoad()

    // don't do this
    //view.translatesAutoresizingMaskIntoConstraints = false

}

When views are instantiated from Storyboards they are loaded with appropriate resizing masks... the "Root" view of a view controller needs translatesAutoresizingMaskIntoConstraints set to true in order for the layout to work properly.

Removing that line may get you where you need to be, but I think you have a couple constraints setup incorrectly.

Here's how the Storyboard I set up looks:

enter image description here

Then, using this code:

//
//  EmbeddedPageViewViewController.swift
//  Created by Don Mag on 5/21/20.
//

import UIKit

class EmbeddedPageViewViewController: UIViewController {

    var pageController: UIPageViewController!

    override func viewDidLoad() {
        super.viewDidLoad()
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "PageView") else { return }
        pageController.setViewControllers([vc], direction: .forward, animated: true, completion: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let dest = segue.destination as? UIPageViewController {
            pageController = dest
        }
    }

}

class SinglePageViewController: UIViewController {

    @IBOutlet var bkgView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        bkgView.backgroundColor = .white
        bkgView.layer.cornerRadius = 8.0
        bkgView.layer.shadowColor = UIColor.black.cgColor
        bkgView.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
        bkgView.layer.shadowRadius = 2.0
        bkgView.layer.shadowOpacity = 0.35

    }

}

I get this result:

enter image description here

The main view's background color is set to "cantaloupe" to make it easy to see the frame of the UIContainerView.

Rotated to landscape orientation:

enter image description here

The width maxes out at 325 and it stays centered.

For reference, here's how it looks without setting the view's background to white:

enter image description here

And here's the source to my Storyboard:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="hya-IX-JpL">
    <device id="retina4_7" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Embedded Page View View Controller-->
        <scene sceneID="TRC-Gf-9JR">
            <objects>
                <viewController id="hya-IX-JpL" customClass="EmbeddedPageViewViewController" customModule="PassBackNavController" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="gkJ-Gz-CPw">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IRh-01-Go2">
                                <rect key="frame" x="8" y="270" width="359" height="128"/>
                                <constraints>
                                    <constraint firstAttribute="height" constant="128" id="GT3-zg-F9u"/>
                                </constraints>
                                <connections>
                                    <segue destination="tRW-tL-JaM" kind="embed" id="DRB-mh-M2m"/>
                                </connections>
                            </containerView>
                        </subviews>
                        <color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="IRh-01-Go2" firstAttribute="leading" secondItem="qBw-JJ-Fx5" secondAttribute="leading" constant="8" id="Aqh-1J-Wli"/>
                            <constraint firstItem="IRh-01-Go2" firstAttribute="centerY" secondItem="gkJ-Gz-CPw" secondAttribute="centerY" id="NLG-5N-8Lb"/>
                            <constraint firstItem="qBw-JJ-Fx5" firstAttribute="trailing" secondItem="IRh-01-Go2" secondAttribute="trailing" constant="8" id="oDL-2p-roM"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="qBw-JJ-Fx5"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dnU-Gs-r2b" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="119" y="131"/>
        </scene>
        <!--Single Page View Controller-->
        <scene sceneID="L0K-o3-YNb">
            <objects>
                <viewController storyboardIdentifier="PageView" id="cfk-9g-Gkj" customClass="SinglePageViewController" customModule="PassBackNavController" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="7CL-c5-Z9a">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="200"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nC5-Ym-QUo">
                                <rect key="frame" x="25" y="12" width="325" height="176"/>
                                <subviews>
                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="umo-Tg-dpR">
                                        <rect key="frame" x="20" y="78" width="285" height="20.5"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                        <nil key="textColor"/>
                                        <nil key="highlightedColor"/>
                                    </label>
                                </subviews>
                                <color key="backgroundColor" red="0.075549371539999993" green="0.79593962429999998" blue="0.99987417460000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <constraints>
                                    <constraint firstItem="umo-Tg-dpR" firstAttribute="leading" secondItem="nC5-Ym-QUo" secondAttribute="leading" constant="20" id="HXw-12-L1h"/>
                                    <constraint firstAttribute="width" priority="750" constant="325" id="Tfm-wt-KFB"/>
                                    <constraint firstAttribute="trailing" secondItem="umo-Tg-dpR" secondAttribute="trailing" constant="20" id="by3-Y5-AqR"/>
                                    <constraint firstItem="umo-Tg-dpR" firstAttribute="centerY" secondItem="nC5-Ym-QUo" secondAttribute="centerY" id="fIW-R2-QXc"/>
                                </constraints>
                            </view>
                        </subviews>
                        <color key="backgroundColor" red="0.97619122270000003" green="0.97633117439999995" blue="0.97616070509999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="nC5-Ym-QUo" firstAttribute="top" secondItem="7CL-c5-Z9a" secondAttribute="top" constant="12" id="ASL-q5-MnJ"/>
                            <constraint firstItem="nC5-Ym-QUo" firstAttribute="centerX" secondItem="7CL-c5-Z9a" secondAttribute="centerX" id="O2h-f7-lBG"/>
                            <constraint firstItem="nC5-Ym-QUo" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="7CL-c5-Z9a" secondAttribute="leading" constant="16" id="RWx-xc-AnB"/>
                            <constraint firstAttribute="bottom" secondItem="nC5-Ym-QUo" secondAttribute="bottom" constant="12" id="gJ0-QG-ApQ"/>
                            <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nC5-Ym-QUo" secondAttribute="trailing" constant="16" id="mEx-VN-uJ4"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="QrT-IP-grh"/>
                    </view>
                    <size key="freeformSize" width="375" height="200"/>
                    <connections>
                        <outlet property="bkgView" destination="nC5-Ym-QUo" id="dx9-da-bUg"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="Lfo-H7-oVB" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="884" y="275"/>
        </scene>
        <!--Page View Controller-->
        <scene sceneID="jMx-mM-wG7">
            <objects>
                <pageViewController autoresizesArchivedViewToFullSize="NO" transitionStyle="pageCurl" navigationOrientation="horizontal" spineLocation="min" id="tRW-tL-JaM" sceneMemberID="viewController"/>
                <placeholder placeholderIdentifier="IBFirstResponder" id="xla-ir-fR9" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="897" y="38"/>
        </scene>
    </scenes>
</document>