25

Interface Builder in XCode 4.5 respects the intrinsicContentSize for some views, e.g. NSButton, but I can't convince it to respect it on my own custom subviews. This causes IB to add extra constraints trying to force the layout drawn in IB, which then causes the intrinsic sizes to not be used when the program is run.

For example, consider a button centered in a window, and a custom view centered in a window…

IB constraints for centered NSButton

IB constraints for centered NSView

You can see that the custom view gets four constraints, presumably because IB doesn't know the view's intrinsicContentSize. You can change which extra constraints are added, e.g. you can force it to be width and height instead, but you can't delete them.

I'm coping with this now by searching and deleting the extra constraints in my awakeFromNib, but there must be a better way to do this.

bitmusher
  • 877
  • 1
  • 7
  • 16

3 Answers3

35

Set a placeholder intrinsic content size — a "guess," if you will — in Interface Builder.

  1. Select your custom view.
  2. Show the size inspector (Shift5).
  3. Change the "Intrinsic Size" drop-down from "Default (System Defined)" to "Placeholder."
  4. Enter reasonable guesses at your view's runtime width and height.

These constraints are removed at compile-time, meaning they will have no effect on your running app, and the layout engine will add constraints as appropriate at runtime to respect your view's intrinsicContentSize.

ravron
  • 11,014
  • 2
  • 39
  • 66
  • But not the best way. It would have been awesome if it could run the `intrinsicContentSize` method and use the result directly in Interface Builder, like it does with `IBDesignable`. – Iulian Onofrei May 10 '17 at 12:04
  • **Note for 2019 there is now a solution to this. Answer below**. Yo\u use invalidateIntrinsicContentSize – Fattie Sep 03 '19 at 17:59
15

How to actually do this, 2019

import UIKit

@IBDesignable class TagPerson: ShadowRoundedImageView {

    override var intrinsicContentSize: CGSize {
        var s = super.intrinsicContentSize
        s.height = 40
        s.width = 40
        return s
    }

    override func prepareForInterfaceBuilder() {
        invalidateIntrinsicContentSize()
    }

}

However, there is a problem. Xcode is buggy. You can sometimes reset it by:

The above will of course work flawlessly at runtime. But it randomly fails to work in interface builder (even with 11+).

To make it cycle, try

  1. The usual 'Refresh all views'

  2. Attach and delete a pointless constraint to one of your intrinsic size views. (I've noticed if you have a number of them, doing this to one is usually enough to make Xcode cycle, then they all work.)

  3. And finally:

Xcode has an "intrinsic size placeholder" feature.

enter image description here

Select one or more of your intrinsic-size elements. Toggle the bizarre placeholder thing back and fore a few times. Often that makes it cycle and the view will then work correctly.

At worst, restarting Xcode with the usual clean-everything will, sometimes, get it working.

Fattie
  • 27,874
  • 70
  • 431
  • 719
  • For me adding @IBDesignable was needed to get Xcode to respect the content size. – Balázs Vincze Jan 30 '20 at 18:46
  • 1
    @BalázsVincze , one importanmt point, IBDesignable is basically hopelessly bvroken in current Xcode :/ we find sadly it completely fails on any non-trrivial storybvoards - sad – Fattie Jan 30 '20 at 21:53
  • Ohh that’s a shame... Fortunately the XIB I used my custom view in was indeed trivial. – Balázs Vincze Jan 31 '20 at 20:26
2

Ok, the point here is to make Xcode use the intrinsicContentSize of your custom view in IB.

This can be achieved by adding a placeholder view inside your custom view in IB with a fixed width and height (you can center it horizontally and vertically as well)

enter image description here

Then select your custom view and tap "Size To Fit Content" form the Edit Menu in IB. At this point all extra size defining constraints will be deletable leaving only positioning ones.

enter image description here

That way IB will size your custom view to fit the placeholder view and and Autolayout would depend on your view's override of - (CGSize)intrinsicContentSize in run time to determine your custom view's size.

Last step is to delete the placeholder view to allow your view to display its content and size correctly:

   - (void)viewDidLoad
    {
        [super viewDidLoad];
        [_placeholderView removeFromSuperview];
    }

I know this is a hack but hopefully it helps you.

Mariam K.
  • 600
  • 4
  • 13
  • 1
    @Fattie I completely agree with you, nowadays with the placeholder option and the huge improvement in autolayout/IB, this is totally unnecessary. But please keep in mind I answered this question back in 2013 when none of these existed yet ;) – Mariam K. Sep 04 '19 at 18:42
  • 1
    @MariamK. maybe mark your answer as not up to date (2019) anymore as you wrote in your comment above. – Bruno Bieri Sep 19 '19 at 08:07