Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Frame calculations in `viewDidLayoutSubviews`

Tags:

ios

ios9

First, I should mention, this is mostly an efficiency issue.

There are many discussions as to where to do frame calculations where viewWillAppear is too early and viewDidAppear is too late (view is already visible).

The common answer is to do frame calculations in viewDidLayoutSubviews. Problem is, it gets called multiple times. Worse, the most accurate call, the one where all frames have their final size is the last one. To my knowledge there is no way to know which call is that final one.

We use to have a 'framesAreSet' flag (initialized false) and a check, where if frame is not zero (something like self.view.frame.size.width != 0) and 'framesAreSet' is false, it goes in, turn the flag and calculate only once. Something like:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    if (self.view.frame.size.width != 0 && !framesAreSet)
    {
        framesAreSet = true;

        //Calculate frames here
    }
} 

This looks ok, but in truth, a check for something like self.view.frame.size.width != 0 does not guarantee that frames are indeed set. The fact that viewDidLayoutSubviews will get called after suggests that some frames were not set to their final state.

Would be great to have a viewCompleteLayoutSubviews. Any ideas of what's the best way to accomplish a 'one time' frame calculations when all frames are set and view is not yet visible?

(This is probably an issue with views not using NSConstraints)

like image 676
bauerMusic Avatar asked Nov 26 '15 15:11

bauerMusic


2 Answers

The app gets only one didLayoutSubviews per draw (up to that point, view state changes are just noted with setNeedsLayout). In that sense, didLayoutSubviews is your idea of didCompleteLayoutOfSubviews. After the draw, if another view state change happens, the layout is incomplete again.

In other words, number of didLayout calls doesn't depend on the number of subview adds or frame changes, it depends on the number of draws (not to be confused with the run loop). Before a draw, if the needsLayout flag has been set, layoutSubviews and then didLayoutSubviews will be called exactly once, no matter how much the view hierarchy has been rearranged.

like image 108
danh Avatar answered Sep 29 '22 04:09

danh


You should always re-layout for the dimension your view currently has.

You set the frames, then rotate the device. Do you need to recompute the frames? Sure you do, the bounds size has changed! You are making dangerous assumptions with your framesAreSet.

If performance really is key for you, you may want to cache the computed frames an some dictionary in invalidate those if the bounds size changes.

But you are certainly not going to get away with performing layout only once.

An alternative would be to make the view controller's view an instance of a custom class and override -layoutSubviews in there.

like image 35
Christian Schnorr Avatar answered Sep 29 '22 02:09

Christian Schnorr