5
votes

Trying to break down a problem that I had for a while now

Background

Let's assume I try to calculate a UIImage that should fit perfectly into a UIImageView, that is layouted with constraints in the storyboard.

The content of the imageView should not be filled / aspect fitted ... but fit perfectly into the frame. Therefore i have a method in my viewController

- (UIImage *)calculatedImageForRect:(CGRect)imageRect

Problem

I want to make sure, that the imageView already contains the correct image as soon as the view becomes visible, so also during a simple push transition. Additionally, I don't want to generate the UIImage multiple times, since it is relatively expensive.

This becomes especially interesting with devices like the iPhone6 plus, that have a different screen resolution

Obviously at viewDidLoad the frames are not set correctly. At that point the frames are set as in the preview of the storyboard - e.g. setup for representing IPhone5 resolution. Same is true for viewWillAppear. Here the frame would e.g. be (10,10,300,100) <-- let's call it wrong-frame

On viewDidAppear the frame is correct (10,10,394,100) = the correct-frame <-- instead of 320 points width, the iPhone6Plus has 414 points. Nevertheless, here it is too late, since the view is already visible and setting the image would result in a flickering (empty image > new generated image).

I know the right time to work with the width is the viewDidLayoutSubviews, which is first called with the wrong-frame at least once, then at one point with the correct-frame. But as i said, i don't want to generate the image multiple times, so here's the dilemma:

How can I determine the right-frame before it is visible?

Of course I could calculate the right frame from the [UIScreen mainScreen] resolution by working with the trailing & leading constraints of the UIImageView, but it feels dirty.

1
So the essence of your question is that you want to figure out how to be notified when a particular view in your view hierarchy (in this case a UIImageView) is finally set to its correct frame, but before it is rendered onscreen?algal
Yes that is exactly what I want to knowMarkHim

1 Answers

9
votes

The reason you cannot use viewDidLayoutSubviews is that, as you say, it may be called more than once. This is also true of layoutSubviews.

Why is this? One reason is that it is because constraint-based layout is an iterative process, which may involve multiple passes. For instance, say I force layout of the view V, which contains in its view hierarchy a subview W, which contains a subview X. The first Auto Layout calculation of the layout of X may alter constraints that then requires a re-calculation of W, which then requires a recalculation of X. This is basically unlike layout with auto resizing masks, which is top down, so that the outer view always dictates the layout of its subview.

However, I believe (but am not 100% sure) that, if all your layout code is implemented correctly, then these multiple layout passes should all take place within one turn of the run loop. That is, when you start this process by calling layoutIfNeeded to force layout on V, then you can be sure that all Auto Layout layout passes on W and X have completed, once layoutIfNeeded returns. No "needs layout" or "needs update constraint" flags will be left dirty waiting for the next turn of the run loop to finish the work.

This may point the way to your solution. If you force an early layout of the top-level view in your hierarchy (such as the root view of the view controller you are transitioning to), then when that layout pass completes the UIImageView will have a correct frame, and you can use that frame to generate your image.

Essentially, instead of doing layout at the last possible moment, you are forcing it to be done earlier. Then you can know when it is finished, since you initiated it and can see the end of the process by waiting for layoutIfNeeded to exit. And then you can observe the resulting value which you care about.