Tometoyou
Tometoyou

Reputation: 8376

How to make a container view size itself by the components inside it in storyboard

I have added a container view to a UIViewController in my storyboard that is managed by a separate UIViewController. This container view needs to have a height associated with it in order to lay out the items correctly. However, the height of this container view is dynamic, because it contains a UITextView among other items. Before, when I didn't use a container view, auto layout calculated the height of the items for me. Now I've moved everything to a container view it seems like this isn't possible. Am I going to have to manually calculate the height of my container view and apply it to a constraint, or is there a way to let auto layout deal with it?

Upvotes: 24

Views: 11358

Answers (2)

Dave Levy
Dave Levy

Reputation: 1172

You might also look into Stack View to see if that suits your needs.

https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html

Upvotes: 1

DonMag
DonMag

Reputation: 77462

You can do this, but you need a little bit of code.

When a UIViewController is embedded in a container view, its "root view" has its .translatesAutoresizingMaskIntoConstraints set to true, and its frame is automatically set to the bounds of the container view.

If your embedded VC has constraints set to properly resize itself, you can disable .translatesAutoresizingMaskIntoConstraints on its root view when it is loaded.

As an example, suppose we have this layout, and the label in the "content VC" will have a variable number of lines:

enter image description here

We want the container view to automatically change its height so all the text will fit. But, if we add 10 lines of text, the "default" result will be:

enter image description here

So, we give the container view Top/Leading/Trailing constraints of 8 (so we have a little padding on the sides), and a Height constraint of 128... but we change the Priority of the Height constraint to 250 (Low). This satisfies IB, but gives us flexibility for its height at run-time.

In our "content VC" we add a label and constrain it Top/Leading/Trailing/Bottom all to 8 (again, just for some padding) and, of course, Number of Lines set to 0.

Now, in our main VC, we implement prepare(for segue...) and disable .translatesAutoresizingMaskIntoConstraints on the embedded VC's root view:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)

    if let childViewController = segue.destination as? MyContentViewController {
        childViewController.view.translatesAutoresizingMaskIntoConstraints = false
    }
}

Now, at run-time, the intrinsic height of our label will increase the height of its superview (the root view), and that will override the container view's Low Priority Height constant, resulting in:

enter image description here

Here is the complete code for both VCs:

import UIKit

class MyContentViewController: UIViewController {

    @IBOutlet var theLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // add 10 lines of text to the label
        theLabel.text = (1...10).map({ "Line \($0)" }).joined(separator: "\n")

    }

}

class ContainerContentViewController: UIViewController {

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        super.prepare(for: segue, sender: sender)

        if let childViewController = segue.destination as? MyContentViewController {
            childViewController.view.translatesAutoresizingMaskIntoConstraints = false
        }
    }

}

Upvotes: 46

Related Questions