Unable to set frame correctly before viewDidAppear

28,049

Solution 1

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.

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

Share:
28,049

Related videos on Youtube

nacross
Author by

nacross

Updated on July 09, 2022

Comments

  • nacross
    nacross almost 2 years

    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;
    }
    
    • Nekto
      Nekto over 11 years
      May be you could show your code?
    • rob mayoff
      rob mayoff over 11 years
    • Maxim Kholyavkin
      Maxim Kholyavkin almost 10 years
      NB! Your viewWillAppear call [super viewDidAppear]; It looks like copy-paste mistake.
  • nacross
    nacross over 11 years
    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
    nacross over 11 years
    I just tried it out and viewDidLayoutSubviews seems like the best place to set the frame. Thanks for helping me clear that up.
  • Sulthan
    Sulthan about 11 years
    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.
  • nacross
    nacross about 11 years
    @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.
  • Peter
    Peter over 10 years
    @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
  • ctlockey
    ctlockey over 10 years
    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
  • bauerMusic
    bauerMusic over 8 years
    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.
  • Noitidart
    Noitidart about 7 years
    @bauerMusic - Have you found any better ideas for this? Thanks for sharing that insight.
  • bauerMusic
    bauerMusic about 7 years
    @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: stackoverflow.com/questions/33942215/…
  • Noitidart
    Noitidart about 7 years
    Thanks very much @bauerMusic for sharing your update! +1