87

I have discovered a strange behavior in my application, where a connected IBOutlet has its connected view's frame between the calls in my view controller to viewWillAppear: and viewDidAppear:. Here is the relevant code in my UIViewController subclass:

-(void)viewWillAppear:(BOOL)animated {
    NSLog(@"%@", self.scrollView);
}

-(void)viewDidAppear:(BOOL)animated {
    NSLog(@"%@", self.scrollView);
}

and the resulting log output:

MyApp[61880:c07] <UIScrollView: 0x1057eff0; frame = (0 0; 0 0); clipsToBounds = YES; autoresize = TM+BM; gestureRecognizers = <NSArray: 0x10580100>; layer = <CALayer: 0x1057f210>; contentOffset: {0, 0}>
MyApp[61880:c07] <UIScrollView: 0x1057eff0; frame = (0 44; 320 416); clipsToBounds = YES; autoresize = TM+BM; gestureRecognizers = <NSArray: 0x10580100>; layer = <CALayer: 0x1057f210>; contentOffset: {0, 0}>

Which clearly shows that the frame is changing between the two calls. I wanted to do setup with the view in the viewDidLoad method, but if the content is not available for me to change until it is on the screen, that seems pretty useless. What could be happening?

Jumhyn
  • 6,687
  • 9
  • 48
  • 76
  • 2
    Are you using autolayout? are you adding this view in Interface builder or programmatically? – Andrea Jul 14 '13 at 07:57
  • Autolayout is enabled, and this view is created in IB from a storyboard. – Jumhyn Jul 14 '13 at 08:00
  • 1
    I never used storyboard, but most proably it is correct. Using Autolayout frame of your views are set when the autolayout engine starts its calculation. Try to ask the same thing right after super of - (void)viewDidLayoutSubviews mpethod of your view controller. – Andrea Jul 14 '13 at 08:04
  • That successfully triggers my event at the right time, but that method is also called whenever I perform any animation on the view. – Jumhyn Jul 14 '13 at 08:11
  • 1
    `viewDidLayoutSubviews` was the correct way to go. I just had to put all my content in a subview so that the method wasn't re-called whenever I changed the frame of of the main view. – Jumhyn Jul 14 '13 at 08:22
  • Post an answer tonight or tomorrow and I will accept ASAP. – Jumhyn Jul 14 '13 at 08:22
  • As of iOS 10, the bounds are correct in viewWillAppear. – Kartick Vaddadi Mar 07 '17 at 11:18
  • Awesome info, thanks! – Jumhyn Mar 08 '17 at 04:49

4 Answers4

158

From the documentation:

viewWillAppear:

Notifies the view controller that its view is about to be added to a view hierarchy.

viewDidAppear:

Notifies the view controller that its view was added to a view hierarchy.

As a result the frames of the subviews aren't yet set in the viewWillAppear:

The appropriate method to modify your UI before the view is presented to the screen is:

viewDidLayoutSubviews

Notifies the view controller that its view just laid out its subviews.

Cœur
  • 37,241
  • 25
  • 195
  • 267
gsach
  • 5,715
  • 7
  • 27
  • 42
  • 2
    Ugh, that is annoying. Even the wording of the documentation makes it seem as if viewWillApepar: and viewDidAppear: should happen directly after one another. – Jumhyn Jul 14 '13 at 17:10
  • 2 months waiting for this answer... thanks I found it !! THANK YOU VERY MUCH! – Rafael Ruiz Muñoz Jul 18 '15 at 14:44
  • 6
    Should be noted that `viewDidLayoutSubviews` will be called multiple times and not always with the same frame, (I think it's called with CGRectZero on first call sometimes). It gets called for every added subview and other changes in the view. – bauerMusic Nov 26 '15 at 14:13
  • I have been butting in to this all day (viewDidLayoutSubviews being called multiple times, including when dismissing the view), and just said the ef with it, and put the logic in an if statement and made a bool that gets set to true after the code has run once. I think there has to be a cleaner way, but too much time sunk in to it already. – solenoid Feb 09 '16 at 18:37
  • we have to get to the bottom of this! viewDidLayoutSubviews is awful, seNeedDisplay does not work – Yaro Jun 20 '16 at 08:46
  • should be the checked answer! – Martin Mlostek Jun 23 '16 at 11:24
  • @gsach if you're still around on SO, Apple added a `viewIsAppearing(_:)` lifecycle method that captures the moment where layout has happened but before the view is drawn to the screen. Would be good to add to your answer! – Jumhyn Jun 07 '23 at 18:26
112

Autolayout made a huge change in how we design and develop the GUI of our views. One of the main differences is that autolayout doesn't change our view sizes immediately, but only when is triggered, that means at a specific time, but we can force it to recalculate our constraints immediately or mark them as "in need" of layout. It works like -setNeedDisplay.
The big challenge for me was to understand and accept that, we do not need to use autoresizing masks anymore, and frame has become a useless property in placing our views. We do not need to think about view position anymore, but we should think how we want to see them in a space related to each other.
When we want to mix old autoresizing mask and autolayout is when problems arise. We should think about autolayout implementation really soon and try to avoid to mix the old approach in a view hierarchy based on autolayout.
Is fine to have a container view that uses only autoresizing masks, such as a main view of a view controller, but is better if we do not try to mix.
I never used storyboard, but most proably it is correct. Using Autolayout, frame of your views are set when the autolayout engine starts its calculation. Try to ask the same thing right after super of - (void)viewDidLayoutSubviews method of your view controller.
This method is called when the autolayout engine has finished to calculate your views' frames. [UPDATE]
As @Jumhyn posted in a comment Apple added a new method in the view controller lifecycle func viewIsAppearing(_ animated: Bool) from iOS 13.
According to Apple documentation:

The system calls this method once each time a view controller’s view appears after the viewWillAppear(:) call. In contrast to viewWillAppear(:), the system calls this method after it adds the view controller’s view to the view hierarchy, and the superview lays out the view controller’s view. By the time the system calls this method, both the view controller and its view have received updated trait collections and the view has accurate geometry.

Andrea
  • 26,120
  • 10
  • 85
  • 131
  • 6
    - (void)viewDidLayoutSubviews is the answer for me! Thanks a lot! – FrizzTheSnail Jul 07 '15 at 12:56
  • This answer is really not correct. Yes of course, obviously, for (5?) years now you have to use autolayout. But there are any number of situations (*using autolayout*) where you need to, say, add something on a screen, "just before it appears to the user". (If you do it in viewDidAppear you'll get a flicker. If you do it in viewWillAppear - the positions will be wrong.) The actual answer is indeed to use viewDidLayoutSubviews. – Fattie Feb 20 '17 at 15:33
  • `add something on a screen, "just before it appears to the user". The actual answer is indeed to use viewDidLayoutSubviews.`... you are going to show that view a lot of times – Andrea Feb 20 '17 at 15:48
  • 1
    @Andrea if you're still around on SO, Apple added a `viewIsAppearing(_:)` lifecycle method that captures the moment where layout has happened but before the view is drawn to the screen. Would be good to add to your answer! – Jumhyn Jun 07 '23 at 18:26
9

call

self.scrollView.layoutIfNeeded()

in your viewWillAppear method. Afterwards you can access it's frame and it will have the same value as you print in viewDidAppear

andrei
  • 1,353
  • 15
  • 24
  • 1
    No, this is not correct. Example: you have a navigation bar on the screen (from your nav controller). Even after layoutIfNeeded(), the height of the navbar isn't included, so your frame size will change. – xaphod May 11 '17 at 01:03
  • This *IS* a good way to force a re-calculation of a scrollview frame dimension so that it is consistent throughout the view layout call hierarchy if the scrollView frame is tied to constraints within its superview. – smakus Mar 18 '20 at 18:33
4

In my case, moving all frame related methods to

override func viewWillLayoutSubviews()

worked perfectly(I was trying to modify constraints from storyboard).

Siddhesh Mahadeshwar
  • 1,591
  • 14
  • 16