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.
viewWillAppear(_:)Always called after viewDidLoad (for obvious reasons, if you think about it), and just before the view appears on the screen to the user, viewWillAppear is called.
If you need to be told when a particular subview changes size, and know its exact final size, you should generally override the layoutSubviews method of that particular UIView subclass.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With