View frame changes between viewWillAppear: and viewDidAppear:

43,154

Solution 1

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.

Solution 2

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.

Solution 3

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

Solution 4

In my case, moving all frame related methods to

override func viewWillLayoutSubviews()

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

Share:
43,154

Related videos on Youtube

Jumhyn
Author by

Jumhyn

Updated on July 08, 2022

Comments

  • Jumhyn
    Jumhyn almost 2 years

    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?

    • Andrea
      Andrea almost 11 years
      Are you using autolayout? are you adding this view in Interface builder or programmatically?
    • Jumhyn
      Jumhyn almost 11 years
      Autolayout is enabled, and this view is created in IB from a storyboard.
    • Andrea
      Andrea almost 11 years
      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.
    • Jumhyn
      Jumhyn almost 11 years
      That successfully triggers my event at the right time, but that method is also called whenever I perform any animation on the view.
    • Jumhyn
      Jumhyn almost 11 years
      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
      Jumhyn almost 11 years
      Post an answer tonight or tomorrow and I will accept ASAP.
    • Kartick Vaddadi
      Kartick Vaddadi about 7 years
      As of iOS 10, the bounds are correct in viewWillAppear.
    • Jumhyn
      Jumhyn about 7 years
      Awesome info, thanks!
  • Jumhyn
    Jumhyn almost 11 years
    Ugh, that is annoying. Even the wording of the documentation makes it seem as if viewWillApepar: and viewDidAppear: should happen directly after one another.
  • FrizzTheSnail
    FrizzTheSnail almost 9 years
    - (void)viewDidLayoutSubviews is the answer for me! Thanks a lot!
  • Rafael Ruiz Muñoz
    Rafael Ruiz Muñoz almost 9 years
    2 months waiting for this answer... thanks I found it !! THANK YOU VERY MUCH!
  • bauerMusic
    bauerMusic over 8 years
    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.
  • solenoid
    solenoid about 8 years
    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.
  • Yaro
    Yaro almost 8 years
    we have to get to the bottom of this! viewDidLayoutSubviews is awful, seNeedDisplay does not work
  • Martin Mlostek
    Martin Mlostek almost 8 years
    should be the checked answer!
  • Fattie
    Fattie about 7 years
    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.
  • Andrea
    Andrea about 7 years
    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
  • xaphod
    xaphod almost 7 years
    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.
  • smakus
    smakus about 4 years
    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.