Why I Say ‘NO’ to Autoresizing
Cocoa does so much so well, but like any framework, it has its quirks. It can be difficult to decide when it’s time to put your foot down and say “No. Thanks. I’ll take over from here.” For me, one of those moments came when I had finally had it with NSView’s autoresizing behavior. Like a self-doubting girlfriend, convinced that I must be doing something wrong, I let it go for a long time. Eventually I had to face the truth. My views were not where I expected them to be and it wasn’t because my expectations were unreasonable. Autoresizing just wasn’t working.
Autoresizing
NSView has an “autoresizing” mechanism. It’s a layout tool that lets the developer configure how a subview will resize and reposition itself when its superview’s frame rectangle changes. For example, let’s say that we have a window with a button in the bottom right corner and we want it to stay there when the user resizes the window.

The button is a subview of the window’s “content view”, so we’ll configure the button’s autoresizing behavior. The button is fine being positioned at the bottom of the window, but we want it to move along the x-axis as the window’s width changes. To do this, we’ll use the autoresizing mask constant NSViewMinXMargin. According to the docs,
If set, the view’s left edge is repositioned proportionally to the change in the superview’s width. Otherwise, the view’s left edge remains in the same position relative to the superview’s left edge.
If we want to configure the view programmatically, we’ll first let the superview know that it needs to trigger the autoresizing, then we’ll configure the subview’s autoresizing mask.
[oWindowContentView setAutoresizesSubviews:YES]; [oButton setAutoresizingMask: NSViewMinXMargin];
Or we can use the nifty visual editor in Interface Builder to set “springs and struts”:

The Problem
The example above works great, but it doesn’t take much to get autoreszing to misbehave. I’m going to make a view and give it one subview. I want the outer view to adjust its width and height with the window and the inner view to stay its original size, but to remain centered in its superview. To achieve this layout, I’ll use the following code:
// turn on autoresizing [oWindowContentView setAutoresizesSubviews:YES]; [oOuterView setAutoresizesSubviews:YES]; // set autoresizing mask [oOuterView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; [oInnerView setAutoresizingMask:(NSViewMinXMargin | NSViewMinYMargin | NSViewMaxXMargin | NSViewMaxYMargin)];
This is what happens:
Everything is fine for a little while, the inner view stays centered. But at one point, after the outer view’s height becomes smaller than the inner view’s height and I expand the window back out, the inner view’s Y margin is lost and its original setting is broken. The inner view is stuck to the top of the outer view’s frame.
This is a very simple example. In real life, view hierarchies are much more complex. We have views inside split views inside other split views next to other views containing buttons, text fields and sliders and they all have to know what to do when the user resizes the window or drags a divider. Behavioral quirks like the one in the video become harder and harder to live with as our interfaces grow.
Taking Control
I sometimes think that designing a dynamic layout is a bit like choreographing a dance routine. What should I do when the dancers just can’t get it right, no matter how many times we’ve been over it? I could instruct them to just stay still when they forget what to do and to jump back in when they’re ready. I could hire someone to stand on the sidelines during the performance, reminding them of their next move when they look lost. Another option is to just replace them with dancers who listen and remember.
I spoke with an Apple engineer at the last WWDC about the problems I was having with autoresizing. I can still see the look on his face when he realized what I was talking about. It was as if he was thinking, “Oh yeahhhhh THAT…” Yeah. That. I asked if the NSView people had fixed That in Leopard. He confirmed that no, they had not and suggested that I set a minimum size for the superviews to prevent the situation. He also asked me what I thought they could do to anticipate resizing past certain sizes.
The idea of setting a minimum size for the superview is the common fix for this. You see it most often with split views, where superviews are very likely to be sized smaller than their subviews. It seems to be an accepted practice to restrict split view divider dragging past some arbitrary point when there is no real usability or layout-related reason to do so. The behavior I see time and again is that just before the split view divider reaches the subview’s amnesia-inducing point, it’ll automatically jump closed and when it’s dragged back out, it’ll jump back. This is bad interaction design. It’s a work-around that has nothing to do with what is good or bad in terms of “user experience”. In fact, it creates a bad user experience. Things shouldn’t be jumping around under the mouse, especially in the middle of a split view divider drag when the user is expecting some measure of control over the layout.
So what could NSView do to prevent this behavior? It could cache margins or at least the proportions we’re trying to preserve. That way, it will always have the correct value to use for its calculations. Seems kind of easy to me.
For the time being, we’re stuck with this behavior and we have to either set minimum sizes for superviews, pollute our application code with controllers to monitor and adjust the layout, or subclass NSView and all of its subclasses and define our own resizing behavior. None of these situations is ideal. The first results in less dynamic layouts and less enjoyable interactions, but it’s simple. The second is just dirty and the last is a pretty big undertaking considering how many subclasses NSView has. I’m sure there are other creative solutions we could consider. You can probably guess what I do from the title of this post. I subclass and bypass the whole autoresizing mechanism. The only time I turn on autoresizing is for the view that’s at the very top of the hierarchy so that it resizes with the window. My solution has its own issues, but they’re pretty easy to deal with and I get an amazing dance troupe that can handle even my most avant-garde choreographic efforts.
A zip file containing the Xcode project I use in the video can be downloaded here.
UPDATE (02-16-08)
I was just thinking about what the bug really is. I don’t think that it’s an issue of simply cacheing a value anymore. There’s something else going on.
If we look at NSViewMinYMargin. Again from the docs:
If set and the superview is not flipped, the view’s top edge is repositioned proportionally to the change in the superview’s height.
I think it has to do with the change calculation. You’ll notice in the video, it didn’t happen each time i shrank the superview. I think that when you’re resizing fast enough, it’s possible that the notification of the frame change is a little behind and when it goes to make the calculation, the change is 0. That doesn’t explain why it stays “stuck”, but it is possible to “unstick” it if you keep resizing over and over. When it unsticks, the proportions are off, so that it’s no longer centered, so there’s something weird there, too. Very very weird.
Tags: Autoresizing, Cocoa, Layout, NSView
February 14th, 2008 at 7:52 am
Hi! Great first post on your new blog
I keep on wondering why Apple does not fix the default autoResizing behaviour. I remember from talking about this that your layout code offers some additional more advanced behaviours next to the ones that are provided in normal NSView autoResizing settings.
With those extra options left out, shouldn’t it be relatively easy for Apple to implement good autoResizing (using cached initial coordinates or something similar for its calculations instead of real-time coordinates after each setFrame call) while leaving the API unchanged?
One potential problem with using cached coordinates could be the handling of situations where a new subview is added to a superView after that superView has been resized, next to some views that were in the superView before. Is this something that limits you when implementing UI using your custom layout code or do you circumvent the problem someway, eg. by storing relative locations instead?
If that potential problem is actually a non-issue, the only two reasons I see for Apple’s Cocoa engineers not fixing autoResizing are: #1. breaking expected (broken) behavior in 3rd party apps and #2. having items with a higher priority on their to-do lists. I can’t really see why #1 would be a big issue, so maybe we should all just start filing bugs on broken autoResizing to move it up that to-do list.
February 14th, 2008 at 9:52 am
Hi, thanks
With the current Autoresizing model, you would have to explicitly position the subview you’re adding, taking into account the current location of the first subview, then set its autoresizing mask.
So, in the example in the video, if I had a button that added a subview next to the first one, when the button was pressed, I’d add the subview, get the first subview’s position and size, set the new subview’s origin so that it would sit right next to the first one and give it the same autoresizing mask. If I want to center them both, i’d have to get the width and height of the superview, position the both of the subviews accordingly, then set their autoresizing masks again. It’s sort of long-winded, but if they cached some information about the layout, it would work.
Obviously Autoresizing wasn’t meant to handle super dynamic layout schemes. With a CALayer, you can choose to forgo Autoresizing. They offer much more sophisticated layout tools that have nifty features like “constraints” and “gravity”, all of which work by creating “relationships” bewetween layers, their sibling layers, and their superlayers. That’s definately the right way to go. With dynamic layouts you don’t want to HAVE to explicitly set a position or even a size the way you are expected to with Autoresizing.
Anyway, my point is that even when you’re being completely reasonable with how you use Autoresizing, it’s very easy to get unexpected results (especially when a split view is in the picture) and that the mechanism could work as expected without a huge change in the codebase and no change to the API. Almost everyone I know that works with GUIs in Cocoa has to write extra code in their app just to handle this problem and that kinda sux.
I filed a bug report
February 16th, 2008 at 2:21 am
The biggest problem I have with autoresizing is the hassle of configuring every single view to have the correct springs and struts. On the other hand, that hassle is much less when you do it while you’re laying out the views. It’s the washing-dishes problem.
The other problem with autoresizing is the one you describe: It’s buggy when the view’s frame shrinks below a reasonable size. I agree that this should never fail, but for the app developer’s part, there is a point where you can say to the user “no, shrinking the window below this size makes no sense”. So, find that size, then set it as the minimum size of the window.
As you say, this takes some control away from the user. On the other hand, turning off resizing outright takes away even more control. I like to give the user as much control as I can, which means allowing (if restricting) autoresizing.
(I take the same approach with split views. Any time I create a master-detail interface with two table views or a table and a text view, I put them in split views so that the user can adjust the proportion.)
So… the views just stay pinned to the lower-left within an ever-growing (or shrinking) window? If you’re not turning resizing off outright, I’m confused about what you’re doing instead.
February 16th, 2008 at 2:48 am
Sorry if I wasn’t clear, but I subclassed NSView and gave it a layout manager class I wrote to replace Autoresizing. So all of the views I use are my subclass instead of NSView. Of course I run into the problem that controls are also NSViews and if I want them to use my layout code, I have to subclass those as well and give them the same layout class. I do that for the ones I use often. It’s a pain to have to do it, but I did it once and use them over and over.
I would have never have thought to do that if Autoresizing had just worked, but now that I’ve done it, it’s nice because I can enhance the standard layout options - give them the types of relationship-based layout we see now in CALayer and its layout manager.
I’m kind of obsessive about not restricting the layout unless its absolutely necessary, like if important controls can’t be accessed. Doing it because of a bug in NSView’s layout code seems wrong to me.
February 16th, 2008 at 3:37 am
Good to know that you’re not simply abandoning resizing entirely. The autoresizing code sounds like something that would be good to post for others’ use, if that’s possible.
By the way, whenever you mention that you’ve filed a Radar bug, you should link to it for the benefit of any Apple employees who should run across your post. The syntax is rdar://problem/######.
February 16th, 2008 at 3:43 am
Yes, thank you for the tip.
rdar://problem/5742732
February 16th, 2008 at 4:47 am
Funny, I’ve never thought of the splitters snapping shut as a design flaw. I actually really like this mechanism: it’s similar to design programs that snap the object you’re dragging to the edge or center of the page. If I’m dragging a splitter way over to the side, it’s because I either want to make that pane “as narrow as possible” (but still useable), or I want to hide it entirely. I don’t want a uselessly narrow little pane that isn’t even wide enough to show its UI components. So the splitter gives me one or the other. I can quickly drag the slider over and know that I’ll hide the pane entirely, not be left with a 10-pixel-wide pane or something like that.
(Springs-and-struts can be a pain, but every other layout mechanism I’ve experienced is much worse. Have you ever used Java’s LayoutManagers? I vividly remember building a simple inspector window in Swing that took me two whole days of messing with the dreaded GridBagLayoutManager, which I could have built in ten minutes with IB.)
February 16th, 2008 at 5:07 am
I think springs-and-struts are awesome. It’s one of my favorite interfaces ever, just wish the effect would not be so buggy.
As far as the split view thing goes, my opinion certainly has alot to do with my personal preferences. I absolutely hate it when i can’t resize a window as much as i want, even though I completely understand that for some layouts, there absoultely needs to be a min size of the window or else buttons and things will be drawn ontop of each other or they’ll be out of the view. Same for split views. I want to drag back and forth as much as I want. I’m fidgety that way, I like to play with the interfaces. When I can’t and there’s no obvious layout reason why, first I think it’s broken, then when it finally snaps, I get annoyed. But that’s Just me.
One problem is the way people do it. Dirk Stoop pointed this out to me and I think it makes a difference. If you’re going to stop people from dragging any further, change the cursur icon from the left/right or up/down icons to ones that show only one direction. Then it’s more obvious that you can’t go any further and you’ll stop trying to pull it. If you’re going to snap it, change the cursur back to the arrow right away, don’t wait for the mouse up. Details like that help the user understand what’s going on. It’s nice to let them know they’re no longer in control.
These situations might also be the right time for a little animation to help clear things up. The snapping is so jarring.
March 25th, 2008 at 1:33 am
In the “View Programming Guide” you quoted is written:
Important: Several cautions apply to autoresizing. For autoresizing to work correctly, the subview being autoresized must lie completely within its superview’s frame.
March 25th, 2008 at 1:35 am
To address you problem, the “View programming guide” you quoted says:
A subclass can override resizeSubviewsWithOldSize: or resizeWithOldSuperviewSize: to customize the autoresizing behavior for a view.
March 25th, 2008 at 2:55 am
twobyte:
once a split view is in the picture, views don’t lie completely within their super view’s frame. most modern apps are based around a single window with a split view design. and usually there are more split views inside those split views. that kind of restriction is compeltely not realistic! : )
April 20th, 2008 at 5:10 pm
Hi Kati,
Like Jense I’ve never thought of the splitters snapping shut as a design flaw. Comming from a non-cocoa environment where we didn’t have this problem I put time into getting our splitters to snap - the issue is that if you have, say, a list of icons with labels there is not that much point in having a splitter 4px running the width of the window. It would be less than 1/2 an icon. As a user I find this messy and not useful. As I remember when implementing the snapping behavior I was working on 10.3 & 10.4 and looked quite carefully at the Finder. As I remember they implemented the snapping at the point where you would otherwise get less than an icon. I think they got it right - when you move the splitter want to see more, or less. To me the extremes are as much as possible, nothing or the minimum that is useful.
For the avoidance of doubt, I think that the Auto Resizing behaviour *should* behave properly.
April 30th, 2008 at 9:44 pm
Cathy, I think you are right in your update that the problem is in how the changes are computed, but I don’t think it’s a notification lag. I think it’s simply what you referred to earlier — that views don’t cache their margins and proportions — along with roundoff error.
It looks like views compute their new size by trying to preserve the margins and/or proportions from their *previous* size and position rather than their *original* size and position — what Dirk referred to as the use of real-time coordinates.
I think the problem is that when a margin gets close to 0, something causes it to be rounded to 0. I’m not sure if floating point rounding errors would be large enough to make this happen. I’ve wondered whether autoresizing explicitly rounds some coordinates to integers; I think I even tested this theory once with some NSLogs, but if so it was long ago. In any case, once a margin becomes 0, it tends to stay that way, because 0 always scales to 0. The only way to force it to become non-zero again is (in your example) to make the outer view smaller than the inner view. The inner view’s fixed size will be preserved, because struts apparently have higher precedence than springs. This would explain the lopsidedness that follows. The views have totally forgotten that one was centered inside the other; they are preserving whatever margin proportions you created at the moment you forced the stuck edges to separate.
As for the “sticking” only happening sometimes — I think in your experiments when you’re dragging to resize, it’s a matter of chance whether you hit the exact size that’s going to make a margin 0 at the exact time that a resizing event occurs. I think the “sticking” is more likely to happen if you’re dragging slowly enough for resize events to happen at almost every pixel that you move. For example, imagine you have the inner view with just a one-pixel margin all around, and then ever so carefully shrink the window by exactly two pixels.
Anyway, I’m not sure I’m saying a whole lot that’s new — Dirk was getting at some of the same points — but I’ve been meaning to comment on this post for ages, because it’s an interesting problem. One of these days I’ll check out the more sophisticated capabilities of CALayer.
April 30th, 2008 at 9:58 pm
twobyte wrote:
>>>
In the “View Programming Guide” you quoted is written:
Important: Several cautions apply to autoresizing. For autoresizing to work correctly, the subview being autoresized must lie completely within its superview’s frame.
<<<
I could be imagining things, but I *think* the “drifting subview” problem has been observed even when a generous minimum size has been specified for the enclosing view. The only way to avoid the problem completely is never to have more than one spring in a given direction (horizontal or vertical) for a given view. Roundoff errors occur when autoresizing tries to distribute a given number of pixels between two “springy” areas, based on some ratio. If there is only one spring, there is no ratio to compute; you just take the number of pixels left over after subtracting the sizes of the struts.
At least that’s my theory; I haven’t experimented with this stuff in a while.