问题
To illustrate this question, I gisted a very small Xcode project on Github (two classes, 11kb download). Have a look at the gist here or use git clone git@gist.github.com:93982af3b65d2151672e.git.
Please consider the following scenario. A custom view of mine, called 'container view', contains two little squares. Illustrated in this screenshot:
The blue square is a 22x22 pt UIView
instance and the red square is a 22x22 pt CALayer
instance. For purposes of this question I want the two squares to 'stick' to the bottom right corner of the outer view, while I animate the frame of that outer view.
I change the container view's frame within a UIView
's class method animateWithDuration:delay:options:animations:completion:
, with a non-default easing parameter of type UIViewAnimationCurveEaseOut
.
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationCurveEaseOut
animations:^{ _containerView.frame = randomFrame; }
completion:nil];
Note: Both the blue UIView's and the red CALayer's frame are set in the overridden setFrame:
method of the container view, but results would have been the same if I had set the blue UIView's autoresizingMask
property to UIView flexible left and top margins.
In the resulting animation, the blue square 'sticks' to the corner just the way I intended it, but the red square completely disregards both timing and easing of the animation. I assume this is because of the implicit animation feature of Core Animation, a feature that has helped me in many occasions before.
Here are a few screenshots in the animation sequence that illustrate the asynchronicity of the two squares:
On to the question: Is there a way to synchronize the two frame changes so that the red CALayer and the blue UIView both move with the same animation duration and easing curve, sticking to each other as if they were one view?
P.S. Of course the required visual result of the two squares sticking together could be achieved in any number of ways, for example by having both layers become either CALayer
s or UIView
s, but the project that the real issue is in has a very legit cause for the one to be a CALayer and the other to be a UIView.
回答1:
I'm assuming that you got the end position correct and that after the animation that the view and the layer are aligned. That has nothing to do with the animation, just geometry.
When you change the property of a CALayer
that isn't the layer of a view (like in your case) it will implicitly animate to its new value. To customize this animation you could use an explicit animation, like a basic animation. Changing the frame in a basic animation would look something like this.
CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[myAnimation setToValue:[NSValue valueWithCGRect:myNewFrame]];
[myAnimation setFromValue:[NSValue valueWithCGRect:[myLayer frame]]];
[myAnimation setDuration:1.0];
[myAnimation setTimingFunction:[CAMediaTimingFuntion functionWithName: kCAMediaTimingFunctionEaseOut]];
[myLayer setFrame:myNewFrame];
[myLayer addAnimation:myAnimation forKey:@"someKeyForMyAnimation"];
If the timing function and the duration of both animations are the same then they should stay aligned.
Also note that explicit animations doesn't change the value and hat you have to both add the animation and set the new value (like in the sample above)
Yes, there are a number of ways to achieve the same effect. One example is having the view and the layer be subviews of the same subview (that in turn is a subview of the outer frame).
Edit
You can't easily group the UIView-animation with an explicit animation. Neither can you use an animation group (since you are applying them to different layers) but yo can use a CATransaction:
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFuntion functionWithName: kCAMediaTimingFunctionEaseOut]];
// Layer animation
CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[myAnimation setToValue:[NSValue valueWithCGRect:myNewFrame]];
[myAnimation setFromValue:[NSValue valueWithCGRect:[myLayer frame]]];
[myLayer setFrame:myNewFrame];
[myLayer addAnimation:myAnimation forKey:@"someKeyForMyAnimation"];
// Outer animation
CABasicAnimation *outerAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[outerAnimation setToValue:[NSValue valueWithCGRect:myNewOuterFrame]];
[outerAnimation setFromValue:[NSValue valueWithCGRect:[outerView frame]]];
[[outerView layer] setFrame:myNewOuterFrame];
[[outerView layer] addAnimation:outerAnimation forKey:@"someKeyForMyOuterAnimation"];
[CATransaction commit];
回答2:
If you want David Rönnqvist's snippet in Swift 3.0, here you have it:
CATransaction.begin()
CATransaction.setAnimationDuration(1.0)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut))
// Layer animation
let myAnimation = CABasicAnimation(keyPath: "frame");
myAnimation.toValue = NSValue(cgRect: myNewFrame)
myAnimation.fromValue = NSValue(cgRect: myLayer.frame)
myLayer.frame = myNewFrame
myLayer.add(myAnimation, forKey: "someKeyForMyAnimation")
// Outer animation
let outerAnimation = CABasicAnimation(keyPath: "frame")
outerAnimation.toValue = NSValue(cgRect: myNewOuterFrame)
outerAnimation.fromValue = NSValue(cgRect: outerView.frame)
outerView.layer.frame = myNewOuterFrame
outerView.layer.add(outerAnimation, forKey: "someKeyForMyOuterAnimation")
CATransaction.commit()
来源:https://stackoverflow.com/questions/10801414/how-to-synchronously-animate-a-uiview-and-a-calayer