292

I have a custom view that's not getting layoutSubview messages during animation.

I have a view that fills the screen. It has a custom subview at the bottom of the screen that correctly resizes in Interface Builder if I change the height of the nav bar. layoutSubviews is called when the view is created, but never again. My subviews are correctly laid out. If I toggle the in-call status bar off, the subview's layoutSubviews is not called at all, even though the main view does animate its resize.

Under what circumstances is layoutSubviews actually called?

I have autoresizesSubviews set to NO for my custom view. And in Interface Builder I have the top and bottom struts and the vertical arrow set.


Another part of the puzzle is that the window must be made key:

[window makeKeyAndVisible];

of else the subviews are not automatically resized.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Steve Weller
  • 4,599
  • 4
  • 23
  • 30

8 Answers8

530

I had a similar question, but wasn't satisfied with the answer (or any I could find on the net), so I tried it in practice and here is what I got:

  • init does not cause layoutSubviews to be called (duh)
  • addSubview: causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target
  • view setFrame intelligently calls layoutSubviews on the view having its frame set only if the size parameter of the frame is different
  • scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and its superview
  • rotating a device only calls layoutSubview on the parent view (the responding viewControllers primary view)
  • Resizing a view will call layoutSubviews on its superview (Important: views with an intrinsic content size will re-size if the content that determines their size changes; for example, updating the text on a UILabel will cause the intrinsic content size to be updated and thus call layoutSubviews on its superview)

My results - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/

Derek Lee
  • 3,452
  • 3
  • 30
  • 39
BadPirate
  • 25,802
  • 10
  • 92
  • 123
  • 1
    Great answer. I have always wondered about `layoutSubviews`. Does `initWithFrame:` cause `layoutSubviews` to be called? – Robert Jul 05 '11 at 10:01
  • 2
    @Robert - I was using initWithFrame... so no. – BadPirate Jul 05 '11 at 17:06
  • I have run into cases where setFrame changes the size but does not cause layoutSubviews to be called. I have no idea why. See this question: http://stackoverflow.com/questions/7921610/why-might-layoutsubviews-not-automatically-be-called-on-a-view-when-its-frame-is – William Jockusch Oct 27 '11 at 20:55
  • from the comments in your blog post: "I find that resizing (not moving, only resizing) any subview causes the superview to get -layoutSubviews sent to it, which I found unexpected. From your table it doesn’t look like you tested that case." please add this to your points. – João Portela Feb 13 '12 at 14:24
  • Joao - Are you able to verify that this is the case? I'd prefer to have a second source :) – BadPirate Feb 13 '12 at 20:02
  • 8
    @BadPirate: **yes**. According to my experiments, if you resize `view1.1` it calls `layoutSubviews` of `view1` and then `layoutSubviews` of `view1.1`. This call does not propagate indefinitely to the superviews, calling it on `view1.1.1` only calls `layoutSubviews` on `view1.1` and `view1.1.1`. Just moving without changing it's size does not call `layoutSubviews` on any of them. – João Portela Feb 17 '12 at 16:25
  • relevant testing code can be found in [this gist](https://gist.github.com/1854249) (so that you can check if I have made some mistake) – João Portela Feb 17 '12 at 16:32
  • If init doesn't call layoutSubviews, maybe viewDidLoad does ? –  May 18 '13 at 09:20
  • 1
    viewDidLoad isn't called on UIView (but UIViewController). View Did load gets called after the UIView is init'd. – BadPirate May 21 '13 at 21:43
  • Also, important to point out is that none of those events are called when the view has not yet been added to the view stack. That is why it is not called by init of any type. Specifically it is called in the next available cycle of the application's main thread when it has been marked as needing layout by one of those events. – user1122069 Feb 07 '16 at 01:49
  • Another important point to specify: rotating a device only calls `layoutSubview` when the layout actually changes. E.g. if the device is in `UIInterfaceOrientationLandscapeLeft` and changes right to `UIInterfaceOrientationLandscapeRight`, `layoutSubview` is not called. – awph Mar 20 '17 at 20:47
  • 2
    Based on my experiment, the second rule may be not accurate: when I add `view1.2` into `view1`, `layoutSubviews` of `view1.2` and `view1` are called, but `layoutSubviews` of `view1.1` is not called. (`view1.1` and `view1.2` are subviews of `view1`). **That is, not all the subviews of the target view are called `layoutSubviews` method**. – HongchaoZhang Mar 27 '17 at 10:17
  • It also get called in applicationDidEnterBackground on 64Bit iPads. – Alex Black Jul 19 '17 at 15:59
  • In my case, I load some data in `viewDidLoad` on background queue and reload tableView on main queue. And I apply some gradient color on uiview in `viewDidLayoutSubviews`. Then I found `viewDidLayoutSubviews` is called twice when I open this viewController. – Zhou Haibo Jun 25 '21 at 10:21
  • Oh, and I found a visualisation on [this post](https://www.appcoda.com/view-controller-lifecycle/). – Zhou Haibo Jun 25 '21 at 10:29
115

Building on the previous answer by @BadPirate, I experimented a bit further and came up with some clarifications/corrections. I found that layoutSubviews: will be called on a view if and only if:

  • Its own bounds (not frame) changed.
  • The bounds of one of its direct subviews changed.
  • A subview is added to the view or removed from the view.

Some relevant details:

  • The bounds are considered changed only if the new value is different, including a different origin. Note specifically that is why layoutSubviews: is called whenever a UIScrollView scrolls, as it performs the scrolling by changing its bounds' origin.
  • Changing the frame will only change the bounds if the size has changed, as this is the only thing propagated to the bounds property.
  • A change in bounds of a view that is not yet in a view hierarchy will result in a call to layoutSubviews: when the view is eventually added to a view hierarchy.
  • And just for completeness: these triggers do not directly call layoutSubviews, but rather call setNeedsLayout, which sets/raises a flag. Each iteration of the run loop, for all views in the view hierarchy, this flag is checked. For each view where the flag is found raised, layoutSubviews: is called on it and the flag is reset. Views higher up the hierarchy will be checked/called first.
Patrick Pijnappel
  • 7,317
  • 3
  • 39
  • 39
  • 5
    can't upvote this answer enough. it should be the top answer. the three rules given is all you need. i've never come across any layoutSubview behaviour that these rules didn't describe perfectly. – quad16 Jul 12 '16 at 15:53
  • 2
    I don't think layoutSubviews is called when the bounds of a direct subview is changed. I think the calling of layoutSubviews is just a side effect of your testing. In some cases layoutSubviews will not get called when a subviews bounds change. Please check the answer of frogcjn, because his anwser is based on documentation by Apple instead of just experiments. – Simon Backx Oct 08 '18 at 12:05
20

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

Layout changes can occur whenever any of the following events happens in a view:

a. The size of a view’s bounds rectangle changes.
b. An interface orientation change occurs, which usually triggers a change in the root view’s bounds rectangle.
c. The set of Core Animation sublayers associated with the view’s layer changes and requires layout.
d. Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of a view.
e. Your application forces layout by calling the setNeedsLayout method of the view’s underlying layer object.

jscs
  • 63,694
  • 13
  • 151
  • 195
frogcjn
  • 819
  • 5
  • 21
  • Also, important to point out is that none of those events are called when the view has not yet been added to the view stack. You include this with the word "can", but specifically it is called in the next available cycle of the application's main thread when it has been marked as needing layout by one of those events. – user1122069 Feb 07 '16 at 01:48
14

Some of the points in BadPirate's answer are only partially true:

  1. For addSubView point

    addSubview causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target.

    It depends on the view's (target view) autoresize mask. If it has autoresize mask ON, layoutSubview will be called on each addSubview. If it has no autoresize mask then layoutSubview will be called only when the view's (target View) frame size changes.

    Example: if you created UIView programmatically (it has no autoresize mask by default), LayoutSubview will be called only when UIView frame changes not on every addSubview.

    It is through this technique that the performance of the application also increases.

  2. For the device rotation point

    Rotating a device only calls layoutSubview on the parent view (the responding viewController's primary view)

    This can be true only when your VC is in the VC hierarchy (root at window.rootViewController), well this is most common case. In iOS 5, if you create a VC, but it is not added into any another VC, then this VC would not get any noticed when device rotate. Therefore its view would not get noticed by calling layoutSubviews.

Community
  • 1
  • 1
Mohit Nigam
  • 454
  • 5
  • 10
9

calling [self.view setNeedsLayout]; in viewController makes it to call viewDidLayoutSubviews

bademi
  • 421
  • 5
  • 8
9

I tracked the solution down to Interface Builder's insistence that springs cannot be changed on a view that has the simulated screen elements turned on (status bar, etc.). Since the springs were off for the main view, that view could not change size and hence was scrolled down in its entirety when the in-call bar appeared.

Turning the simulated features off, then resizing the view and setting the springs correctly caused the animation to occur and my method to be called.

An extra problem in debugging this is that the simulator quits the app when the in-call status is toggled via the menu. Quit app = no debugger.

Steve Weller
  • 4,599
  • 4
  • 23
  • 30
  • Are you saying that layoutSubviews is called when the view is resized? I always assumed it is not... – Andrey Tarantsov May 09 '09 at 14:21
  • It is. When a view is resized it needs to do something with its subviews. If you don't provide it then it moves them automatically using springs, struts etc. – Steve Weller May 10 '09 at 04:45
6

have you looked at layoutIfNeeded?

The documentation snippet is below. Does the animation work if you call this method explicitly during the animation?

layoutIfNeeded Lays out the subviews if needed.

- (void)layoutIfNeeded

Discussion Use this method to force the layout of subviews before drawing.

Availability Available in iPhone OS 2.0 and later.

Willi Ballenthin
  • 6,444
  • 6
  • 38
  • 52
2

When migrating an OpenGL app from SDK 3 to 4, layoutSubviews was not called anymore. After a lot of trial and error I finally opened MainWindow.xib, selected the Window object, in the inspector chose Window Attributes tab (leftmost) and checked "Visible at launch". It seems that in SDK 3 it still used to cause a layoutSubViews call, but not in 4.

6 hours of frustration put to an end.

Tal Yaniv
  • 493
  • 6
  • 13