4

my question is related to

UIView/CALayer: Transform triggers layoutSubviews in superview

Where is reported that, from a TSI reply from apple:

Generally, changing a geometric property of a view (or layer) will trigger a cascade of layout invalidations up the view hierarchy because parent views may have Auto Layout constraints involving the modified child. Note that Auto Layout is active in some form regardless of whether you have explicitly enabled it.

In my case i have a very simple view hierarchy where view A contains view B

In the layoutSubviews of A i'm setting the frame of B.

When I perform animations (transform and opacity) on the backing layer of B, sometimes, layoutSubviews of A is called. This is obviously a problem because i'm setting the frame of B in the layoutSubviews of A and this break the animation.

How can I avoid this conflict between the layout of B and the animation?

Community
  • 1
  • 1
Luca Bartoletti
  • 2,435
  • 1
  • 24
  • 46
  • From that page you linked to:how can I avoid superview to layoutSubviews every time I'm changing transform? There is no way to bypass this behavior. It's part of UIKit's internal bookkeeping that is required to keep the view hierarchy consistent. – qqq Aug 05 '14 at 16:34
  • (seems to imply that you can't bypass this when animating transform) – qqq Aug 05 '14 at 16:34
  • Yes, you are right. It is clear that is not possible to bypass this behaviour. But this means that is not possible to divide the hierarchy in views affected by autolayout and views not affected. – Luca Bartoletti Aug 05 '14 at 16:46
  • 1
    From the [UIView documentation](https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/uiview/uiview.html#//apple_ref/occ/instm/UIView/layoutSubviews): "You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly." I suppose one thing you could do is have a manual conditional check for animations; i.e., have this method skip B if B is animating, using a BOOL flag that is set/unset with animation calls – qqq Aug 05 '14 at 16:51
  • As I said I can't use constraints because i need to change the transform. The autoresizing mask is not suitable, i need to setup the frame manually when the frame of A change. I have implemented a fix where i'm checking if there are animations running on B. But i don't think that is ideal, is not generic and can cause problem if the layoutSubviews is due to a an update of the frame of A – Luca Bartoletti Aug 05 '14 at 16:58
  • In this article solution 3/4 should not work following what Apple says in the TSI. Because the layoutSubviews could be called http://stackoverflow.com/questions/12943107/how-do-i-adjust-the-anchor-point-of-a-calayer-when-auto-layout-is-being-used – Luca Bartoletti Aug 06 '14 at 10:47
  • I suspect that the reason it will work is because in the layoutSubviews, either the constraints or the custom code will adjust the frame of what this author calls the "host view" -- but NOT the thing you're transforming. The transformed object is adjusted only by the transforms, but all those changes are by definition relative to the frame of the host view -- the superview -- so there isn't a conflict. I haven't built a trial project yet though so I'm not absolutely sure – qqq Aug 06 '14 at 15:46
  • 1
    I added an answer with the solutions i found around this topic – Luca Bartoletti Aug 08 '14 at 18:27

1 Answers1

16

I discovered that is possible to solve this problem in many ways:

1) Temporary save the transform, change the frame and than reapply the transform:

[super layoutSubviews];

//backup the transform value before the layout
CATransform3D transform = self.internalView.layer.transform;

//Set identity and update all the necessary frames
self.internalView.layer.transform = CATransform3DIdentity;
self.internalView.frame = self.bounds;

//set back the transform
self.internalView.layer.transform = transform;

2) Change the bounds and the center of the view without touch the frame. Bounds and center seems to not affect the transform. Make sense because the frame is calculated internally reading layer's bounds, position, transform, anchor point.

self.internalView.bounds = self.bounds;
self.internalView.center = CGPointMake(floorf(CGRectGetWidth(self.bounds)/2.0f),        floorf(CGRectGetHeight(self.bounds)/2.0f));

3) Apple in a TSI (reported here UIView/CALayer: Transform triggers layoutSubviews in superview) suggests to

"Wrap your cell contents in an intermediate view. When you modify the transform of this intermediate view, only the cell's layout should be invalidated, so your expensive layout method won't be called.

If that doesn't work, create some mechanism to signal to your expensive layout method when it actually does (or does not) have to do work. This could be a property you set when the only changes you make are to the transforms."

4) I also experimented turning off completely autolayout for the subviews of my custom view. You can read this really good article from obj.io where is explained that if you remove the call to [super layoutSubviews] from the layoutSubviews implementation you are opting out autolayout for your custom view and all its subviews.

The tread off is that you must be aware that in the case you add as subview a view with constrains the autolayout system will rise an exception like this:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. MyView's implementation of -layoutSubviews needs to call super.'

The solution 1 and 2 are good if you have a simple layout logic. If your layout logic is expansive you can implement 3 or 4 and referee to this other question UIView/CALayer: Transform triggers layoutSubviews in superview

Community
  • 1
  • 1
Luca Bartoletti
  • 2,435
  • 1
  • 24
  • 46
  • Solution 1 works great in iOS 8, but behaves differently in iOS 7. Any idea why? UPDATE - turns out I only need to apply this for iOS 8; iOS 7 works fine without it. – Erich Oct 21 '14 at 22:20
  • Creating additional internalView definitely helped me! Thanks! – Nadzeya Jul 29 '21 at 14:53