How to set a custom view's intrinsic content size in Swift?
Asked Answered
D

2

53

Background

I am making a vertical label to use with traditional Mongolian script. Before I was just rotating a UILabel but there were some performance issues and other complications with this. Now I am working on making a label from scratch. However, I need the vertical label to tell auto layout when its height adjusts (based on string length).

What I have read

I read the Intrinsic Content Size and Views with Intrinsic Content Size documentation. These were more about how to use it, though, and not how to define it in a custom view.

Searching for "ios intrinsic content size for a custom view" only gives me

in Stack Overflow. This particular question didn't even need intrinsic content size because their view was just an assembly of standard views.

What I am trying

What I am trying is my answer below. I am adding this Q&A pair so that it won't take other people as long to find the answer as it took me with the search keywords that I used.

Dumps answered 29/3, 2016 at 9:44 Comment(0)
D
102

Setting the intrinsic content size of a custom view lets auto layout know how big that view would like to be. In order to set it, you need to override intrinsicContentSize.

override var intrinsicContentSize: CGSize {
   return CGSize(width: x, height: y)
}

Then call

invalidateIntrinsicContentSize()

Whenever your custom view's intrinsic content size changes and the frame should be updated.

Notes

Dumps answered 29/3, 2016 at 9:44 Comment(1)
For me adding @IBDesignable was needed to get Xcode to respect the content size.Tonry
H
17

Example of a "view with intrinsic height" ...

@IBDesignable class HView: UIView {

    @IBInspectable var height: CGFloat = 100.0

    override var intrinsicContentSize: CGSize {
        return CGSize(width: 99, height: height)
        // if using in, say, a vertical stack view, the width is ignored
    }

    override func prepareForInterfaceBuilder() {
         invalidateIntrinsicContentSize()
    }
}

enter image description here

which you can set as an inspectable

enter image description here

Since it has an intrinsic height, it can (for example) be immediately inserted in a stack view in code:

stack?.insertArrangedSubview(HView(), at: 3)

In contrast, if it was a normal view with no intrinsic height, you'd have to add a height anchor or it would crash:

let v:UIView = HView()
v.heightAnchor.constraint(equalToConstant: 100).isActive = true
stack?.insertArrangedSubview(v, at: 3)

Note that in ...

the important special case of a stack view:

  • you set only ONE anchor (for vertical stack view, the height; for horizontal the width)

so, setting the intrinsic height works perfectly, since:

  • the intrinsic height indeed means that the height anchor specifically will be set automatically if needed.

Remembering that in all normal cases of a subview, many other anchors are needed.

Hexyl answered 14/1, 2017 at 13:14 Comment(2)
I'd recommend returning super.intrinsicContentSize.width for the width in the override rather than returning a constant like you suggest. This way when the view is used outside of a stack view and has a width constraint that constraint is respected.Erbium
hi @VojtaBöhm - sure thing, but the pedagogy here is to explain that, in fact, the width value is ignored when used in a layout case where the width value is ignored (stack view in the example)Hexyl

© 2022 - 2024 — McMap. All rights reserved.