43

I was wondering if anyone knows why when you set the frame of a subview in viewDidLoad and viewWillAppear the changes do not take affect on the screen, but if you set it in viewDidAppear they do?

In my case I am loading a custom xib with two tableviews then attempting to shift them down in viewDidLoad to allow space for another view which is added in viewDidLoad as it is not always necessary to display it.

The problem is when i set frame in viewDidLoad or viewWillAppear it is set on the object, i can see by printing it out, but it is not reflected on screen. Moving my set frame calls to viewDidAppear will cause everything to work as expected.

Is it wrong to think I should be able to set the frame in viewDidLoad?

- (id)init {
    if ( self = [super initWithNibName:@"MyView" bundle:nil] ) {}

    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.descriptionWebView = [[[UIWebView alloc] initWithFrame:CGRectMake( 0, 0, self.view.frame.size.width, 200 )] autorelease];

    [self.view addSubview:self.descriptionWebView];

    self.tableView1.autoresizingMask = UIViewAutoresizingNone;
    self.tableView2.autoresizingMask = UIViewAutoresizingNone;

    [self.descriptionWebView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"...." withExtension:@"html"]]];

    table1LocationWithHeader = CGRectMake( self.tableView1.frame.origin.x, 200, self.tableView1.frame.size.width, self.tableView1.frame.size.height - 200 );
    table2LocationWithHeader = CGRectMake( self.tableView2.frame.origin.x, 200, self.tableView2.frame.size.width, self.tableView2.frame.size.height - 200 );

    //this will NOT work
    self.tableView1.frame = table1LocationWithHeader;
    self.tableView2.frame = table2LocationWithHeader;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    //this will NOT work
    self.tableView1.frame = table1LocationWithHeader;
    self.tableView2.frame = table2LocationWithHeader;
}


- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    //this WILL work
    self.tableView1.frame = table1LocationWithHeader;
    self.tableView2.frame = table2LocationWithHeader;
}


//I added these after comments for stack overflow users, it seems like viewDidLayoutSubviews is the best place to set the frame

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    //this will NOT work
    self.tableView1.frame = table1LocationWithHeader;
    self.tableView2.frame = table2LocationWithHeader;
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    //this WILL work
    self.tableView1.frame = table1LocationWithHeader;
    self.tableView2.frame = table2LocationWithHeader;
}
nacross
  • 2,013
  • 2
  • 25
  • 37

3 Answers3

100

viewDidLoad is called when the class is loaded however no ui elements have been initialised and therefore any attempt to reference them will be overwritten or unavaliable during the initialisation process which happens between the viewDidLoad and viewDidAppear calls. Once all ui element have been initalised and drawn viewDidAppear is called.

viewDidLoad - Called after the controller's view is loaded into memory

At this point the view isn't within the view hierarchy.

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

Again, the view is yet to be added to the view hierarchy.

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

Only then is the view added to the view hierarchy.

Update

The viewDidLayoutSubviews is the most appropriate place to modify the UI before it actually appears on the screen.

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

Jeff
  • 13,943
  • 11
  • 55
  • 103
Leon Storey
  • 3,274
  • 2
  • 25
  • 40
  • Okay so viewDidLoad and viewWillAppear are quite simply the wrong place to attempt to modify the frame. Maybe the correct place to set the frame is actually in viewWill/DidLayoutSubviews which i just saw in the question linked to by Rob, as viewDidAppear seems to be too late to modifying the frame. I will try this and see if it works. – nacross Oct 26 '12 at 00:52
  • 8
    I just tried it out and viewDidLayoutSubviews seems like the best place to set the frame. Thanks for helping me clear that up. – nacross Oct 26 '12 at 01:01
  • This doesn't really explain why setting frame values in `viewDidLoad` doesn't work. I am actually setting the frames in `viewDidLoad` very often an never had a problem with it. – Sulthan Apr 02 '13 at 19:32
  • 3
    @Sulthan I think this problem occurs when you have contraints set in interface builder that conflict with what ever frame you are setting. This must happen between viewWillLayoutSubviews and viewDidLayoutSubviews. – nacross May 01 '13 at 00:48
  • @Sulthan - I don't usually have problems, but if there is anything that requires the view.frame.size it always gives me the non-iPhone5 (shorter) size, rather than the fullsize one. didlayoutsubviews is the best solution – Peter Nov 16 '13 at 12:08
  • 2
    `viewDidLayoutSubviews` is fantastic! I've been developing for iOS for going on two years now and this is the first I've heard of it. Great answer! +1 – ctlockey Dec 03 '13 at 20:23
  • 5
    I keep seeing this answer where one big issue is being ignored. `viewDidLayoutSubviews` gets called multiple times (for one, on every added subviews as @DienBell explained). If you do many frame calculations there, it will do them multiple times. Far from neat. One can put a flag, but the first call would probably have an incomplete frame. So you really want the _last_ call. But I can't think of a way to accomplish that. Checking that `self.view.frame` is not `CGRectZero` can work, but again, far from neat. – bauerMusic Nov 26 '15 at 14:34
  • @bauerMusic - Have you found any better ideas for this? Thanks for sharing that insight. – Noitidart Jun 22 '17 at 08:15
  • 1
    @Noitidart No.. I was 'hoping' that Apple will address this one day. I guess that since some frames are dependent on others, `viewDidLayoutSubviews` is the right place, but I mostly use the `UIScreen.main.bounds` to get the screen size (which will alway set correctly and does not change). Check my post: https://stackoverflow.com/questions/33942215/frame-calculations-in-viewdidlayoutsubviews – bauerMusic Jun 22 '17 at 09:54
  • 1
    Thanks very much @bauerMusic for sharing your update! +1 – Noitidart Jun 23 '17 at 04:02
2

See this thread When is layoutSubviews called?
When use autolayout, framework do not call layoutSubviews automatically. That is very important. From ref:

  • 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. ...


If you add subview in viewDidLoad, layoutSubviews called before viewDidAppear, and you can get the correct size of subviews. But if you do nothing, layoutSubviews will be called after viewDidAppear. It's up to your code.

Community
  • 1
  • 1
BollMose
  • 3,002
  • 4
  • 32
  • 41
0

in viewWillAppear()

just call layoutIfNeeded() once for the view whose frame you want to get.

if tableView then tableView.layoutIfNeeded() for exmple

Mohshin Shah
  • 298
  • 2
  • 13