IntrinsicContentSize on a custom UIView streches the content

232 views Asked by At

I would like to create a custom UIView which uses/offers a IntrinsicContentSize, since its height depends on its content (just like a label where the height depends on the text).

While I found a lot of information on how to work with IntrinsicContentSize offered by existing Views, I found just a few bits on how to use IntrinsicContentSize on a custom UIView:

@IBDesignable class MyIntrinsicView: UIView {
    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        context?.setFillColor(UIColor.gray.cgColor)
        context?.fill(CGRect(x: 0, y: 0, width: frame.width, height: 25))
        
        height = 300
        invalidateIntrinsicContentSize()
    }
    
    
    @IBInspectable var height: CGFloat = 50
    override var intrinsicContentSize: CGSize {
        return CGSize(width: super.intrinsicContentSize.width, height: height)
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        invalidateIntrinsicContentSize()
    }
}
  • The initial high is set to 50

  • The draw methods draws a gray rect with a size 25

  • height is changed to 300 and invalidateIntrinsicContentSize() is called

  • Placing a MyView instance in InterfaceBuilder works without any problem. The view does not need a height constraint

However, in IB the initial height of 50 is used. Why? IB draws the gray rect, thus the draw method is called. So why is the height not changed to 300?

What is also strange: When setting a background color it is drawn as well, also super.draw(...) is not called. Is this intended?

I would expect a view with height of 300 and a gray rect at the top with a height of 25. However, when running the project in simulator the result is different:

  • height of the view = 300 - OK
  • height of content (= gray rect) = 150 (half view height) - NOT OK

It seems that the content was stretched from its original height of 25 to keep its relative height to the view. Why is this?

enter image description here

1

There are 1 answers

0
DonMag On BEST ANSWER

Trying to change the view's height from inside draw() is probably a really bad idea.

First, as you've seen, changing the intrinsic content size does not trigger a redraw. Second, if it did, your code would go into an infinite recursion loop.

Take a look at this edit to your class:

@IBDesignable class MyIntrinsicView: UIView {
    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        context?.setFillColor(UIColor.gray.cgColor)
        context?.fill(CGRect(x: 0, y: 0, width: frame.width, height: 25))

        // probably a really bad idea to do this inside draw()
        //height = 300
        //invalidateIntrinsicContentSize()
    }
    
    
    @IBInspectable var height: CGFloat = 50 {
        didSet {
            // call when height var is set
            invalidateIntrinsicContentSize()
            // we need to trigger draw()
            setNeedsDisplay()
        }
    }
    
    override var intrinsicContentSize: CGSize {
        return CGSize(width: super.intrinsicContentSize.width, height: height)
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        // not needed
        //invalidateIntrinsicContentSize()
    }
}

Now, when you change the intrinsic height in IB via the IBDesignable property, it will update in your Storyboard properly.

Here's a quick look at using it at run-time. Each tap (anywhere) will increase the height property by 50 (until we get over 300, when it will be reset to 50), which then invalidates the intrinsic content size and forces a call to draw():

class QuickTestVC: UIViewController {
    
    @IBOutlet var testView: MyIntrinsicView!
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        var h: CGFloat = testView.intrinsicContentSize.height
        h += 50
        if h > 300 {
            h = 50
        }
        testView.height = h
    }
}