I'm animating an ImageView using CABasicAnimation.
I move its layer to left, right, up and down and sometimes I'd scale it bigger then reset it to its original size etc.
I'm doing all this to its layer so I thought I might have to move & scale the real thing along with its layer as well but when I tested it with tap gesture to see if it really was just staying where it started, it wasn't. Therefore I no longer need to change its view's frames as far as I'm concerned.
Is changing a view's layer's values also change its view's values?
A UIView is no more than a fancy wrapper for a CALayer – bringing UIResponder events & animation conveniences among many other things.
Many UIView properties are actually just forwarded versions of the underlying CALayer properties, defined purely for convenience.
A view's frame & bounds properties should always reflect the layer equivalents.
transform is slightly more complex, as for the view it's of type CGAffineTransform – whereas on the layer it's CATransform3D. If the layer's transform can be represented as a CGAffineTransform, then you'll be able to access it from the view after setting it on the layer. If it can't be represented, then its value is undefined.
Therefore yes, you are right in saying you don't need to update the frame or transform on the UIView when changing it on its CALayer. Although note that these properties won't reflect the 'in-flight' values of the animation – you'll need to access the layer's presentationLayer for that.
Also note that as #par & #jrturton mention, if a layer's transform is not the identity transform, then the frame is undefined and you therefore shouldn't use it.
Related
Here is the iPad Simulator with four nested UIViews, drawing a custom background, and an inner UILabel. I am rotating the top UIView's CALayer, by getting it's layer and setting transform with a rotateY CATransform3D, animated on a separate thread (but the changes to transform are being sent on the main thread, naturally):
Note- this animation does not loop correctly, hence it appears to bounce.
The layers do animate as a whole, but curiously, the first child and it's descendants appear to be floating above the UIView with the transform applied!
The UIViews themselves are children in another UIView, which has a red background. There are no other transformations being applied anywhere else.
The positions for each UIView were set using setFrame initially at the start.
What is causing this strange behaviour, and how can I ensure the child UIViews transform with their parent, giving a flat appearance to the surface as a whole?
Well. Perhaps unsurprisingly I was doing something silly, but since I'd not used CALayer transforms before, I didn't know if things were acting up. I had overridden layoutSubviews on the UIViews I was creating, and the act of rotating the CALayer was triggering this call and then pushing the child components frame around, due to a bug.
The problem is that CALayers don't actually do 3D perspective by default. In order to do that you need to make a minor change to the layer's transform (which is of type CATransform3D)
You want to change the .m34 field of the transform to a small negative value. Try -1/200 to -1/500 as a starting range. If I remember correctly it should be the negative of 1 over the image height/width.
Change the .m34 property of the layer that you want to appear to "come off the page" and rotate in 3D. When you do that the Z setting of the layer does matter, and will both make closer layers bigger and also make layers that are further away disappear behind other things.
I suggest you do a Google search on "CATransform3D m34" for more information. There is a fair amount of information on the net about it.
I have an view inside a UIScrollView. On that view I set a scale transform, so it compensates for the zooming and stays the same size.
While the transform seems to work on all subviews, filling the bounds in the view's own drawRect seems to fill the whole frame, as if there was no scale transform applied.
Why can this be?
I was aware that I could not use the frame property in a transformed view, but actually setting it yourself on a transformed view is also a no-no.
if the transform property contains a non-identity transform,
the value of the frame property is undefined and should not be modified
I removed all code that uses self.frame in any way and used self.center instead. Now everything displays correctly.
I have a UIView to which various scale and rotation CGAffineTransforms have been applied, in response to touch screen gestures.
When a scale operation completes, I want to adjust the bounds of the view to the new size, then have view to re-draw itself at a scale of 1.0, while keeping all other components of the transform the same.
To adjust the bounds of the view, I'm using:
self.myView.bounds = CGRectApplyAffineTransform(self.myView.bounds, self.myView.transform);
To "undo" the scale transform, I'm trying this:
self.myView.transform = CGAffineTransformConcat(self.myView.transform, CGAffineTransformMakeScale(1, 1));
then calling [myView setNeedsDisplay] to get the view to redraw itself.
However this does not produce the desired results and when a rotate transform is applied, the above code seems to cause what looks like a sideways translation transform to be applied too.
What's the cleanest way to "undo" just a scale transform and have the view redraw at 1:1 with all other transforms remaining intact?
I am handling something similar, and what I do is store the separate components in another struct (scale, rotation, translation). That way you can easily recreate the transform again (be careful about the order of your operations though).
One thing though: I suggest that you don't change the bounds and instead just apple a scale operation to the transform. This will avoid some potential unneeded layouts. All of this stuff can be handled purely with the transform property. The way you are doing it now is not going to change anything since applying a scale of 1 is a no-op. If you scaled up the view by 2, you would need to scale by 0.5 to get back to the original. Or, as I said above, store all the components and recreate it from the identity matrix (matrix math is fast, don't worry about that).
Save your scale values in a variable and then, try changing CGAffineTransformMakeScale(1, 1) into
CGAffineTransformMakeScale(1/totalScaleWidth, 1/totalScaleHeight)
In CALayer's API, 'position' is used for setting the position of the layer.
By my own testing, setting bounds.origin does not do anything. Am I missing something?
The bounds.origin controls where the origin of the layer's coordinate system is, relative to the layer's frame in its superlayer. Changing it has two visible effects:
The position of sublayers of the layer. For example, when you scroll a UIScrollView, the scroll view doesn't change its subview's frames. It simply changes its bounds.origin. I suggest setting up a toy app with a scroll view and doing NSLog("scroll view bounds = %#", NSStringFromCGRect(scrollView.bounds)); from a timer or some other trigger to get a sense of what's happening.
The origin of the graphics context coordinate system in drawInContext:. Mostly commonly you would see this effect in a view's drawRect: method. Your CGContext inside drawRect: will have been translated by the self.bounds.origin.
You may find it helpful to read about “View Geometry and Coordinate Systems” in the View Programming Guide for iOS and “Layer Objects Define Their Own Geometry” in the Core Animation Programming Guide, although really neither of them have a good discussion of the bounds origin.
Changing the bounds rectangle changes the position and size of the content in the coordinate system of the layer itself. Changing the frame (or position) changes the position of the layer in the coordinate system of its super layer. Usually you only want to change the frame, not the bounds.
Can somebody point me to a good primer on the above, and what happens to one when you mess with the others? It seems as though no matter what I do, once I start messing with either the status bar orientation or the view transform (even if all I'm doing is 90-degree rotations), I can count on my views ending up sideways, upside down and backwards, and on a frustrating afternoon of trial and error trying to get them straightened out. I'm sure it all makes sense once you know the logic and what order everything's applied in, but so far, empirically, I haven't been able to figure it out.
I don't know of a good single document primer on the subject, but the following is what I've learned from experience and reading the docs.
center, bounds, and frame
If you set frame then center and bounds will be updated. If you set center or bounds then frame will be updated. Frame is a convenience method for manipulating center and bounds using the superview's coordinate system.
From UIView Class Reference:
The geometry of a view is defined by its frame, bounds, and center properties. The frame defines the origin and dimensions of the view in the coordinate system of its superview and is commonly used during layout to adjust the size or position of the view. The center property can be used to adjust the position of the view without changing its size. The bounds defines the internal dimensions of the view as it sees them and is used almost exclusively in custom drawing code. The size portion of the frame and bounds rectangles are coupled together so that changing the size of either rectangle updates the size of both.
See The Relationship of the Frame, Bounds, and Center Properties for more details.
transform
If you set the transform property to something besides the identity transform, frame is undefined. If you set the transform to something else, you should only manipulate the view geometry using center (to position the view in it's superview) and bounds (to adjust the size of the view). Here's the relevant info from UIView Class Reference:
The origin of the transform is the value of the center property, or the layer’s anchorPoint property if it was changed. (Use the layer property to get the underlying Core Animation layer object.) The default value is CGAffineTransformIdentity.
...
Warning If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
See Coordinate System Transforms for more details.
UIInterfaceOrientation
UIInterfaceOrientation doesn't affect the transform, bounds, center, or frame properties directly. However, when the device orientation changes, the view controller will automatically resize its subview (which will in-turn resize it's subviews and so on).
See Responding to Device Orientation Changes and View Controller View Resizing for more details.