Animate View with multiple arguments - ios

I am trying to animate the opacity of a CALayer but the values are more than one value to begin with and one to end with.
For example: I want it to animate throw these values: 0.0, 0.7, 0.3, 1.0, 0.5, 0.0
I also want the animation to repeat with auto reverse.
This is what I have for now:
let redLineAnimation = CABasicAnimation(keyPath: "opacity")
redLineAnimation.duration = 0.4
redLineAnimation.autoreverses = true
redLineAnimation.fromValue = 0.0
redLineAnimation.toValue = 1.0
redLineAnimation.repeatCount = Float.infinity
movingRedLineLayer.addAnimation(redLineAnimation, forKey: nil)
I am new to iOS development. What can I do? Thanks!

Take a look at CAKeyframeAnimation. That will give you what you want.
And if what you are animating is a view's layer, you could use the easier-to-use UIView keyframe based animation methods. (Core Animation is pretty tricky, and the docs are spotty)

Related

Animating Views and subviews in sequence

Im struck with Animation. I would like to animate in below sequence as shown in picture.
Please click here for Image
All are views i.e., outerView, dot1, dot2, dot3 . I've implemented code to animate dots but need your help to animate outerview and adding everything in sequence
let transition = CATransition()
transition.duration = 2;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
transition.speed = 1.0
dot3?.layer.add(transition, forKey: nil)
transition.beginTime = CACurrentMediaTime() + 0.11
dot2?.layer.add(transition, forKey: nil)
transition.beginTime = CACurrentMediaTime() + 0.22
dot1?.layer.add(transition, forKey: nil)
Please help me animating in sequence - outerView starting, dots and closing outerView like shown
You're on the right path, except obviously there will be a lot more animations than the few that you've shown in the snippet. There's no reason why you can't continue building this animation using the CAAnimation classes, but I suspect that using the newer UIViewPropertyAnimator classes (will need to target iOS10) will be useful because they allow you to 'scrub' the steps in the animation which will be useful debugging. Here's a good intro: dzone.com/articles/ios-10-day-by-day-uiviewpropertyanimator
Expanding on this comment to a proper answer...
Using animateWithKeyframes is a pretty decent solution to create this animation in code. Here's a snippet of what this could look like:
let capsule: UIView // ... the container view
let capsuleDots [UIView] //... the three dots
let capsuleFrameWide, capsuleFrameNarrow: CGRect //.. frames for the capsule
let offstageLeft, offstageRight: CGAffineTransform // transforms to move dots to left or right
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeIn)
// the actual animation occurs in 4 steps
animator.addAnimations {
UIView.animateKeyframes(withDuration: 2, delay: 0, options: [.calculationModeLinear], animations: {
// step 1: make the capsule grow to large size
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.1) {
capsule.bounds = capsuleFrameWide
}
// step 2: move the dots to their default positions, and fade in
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.1) {
capsuleDots.forEach({ dot in
dot.transform = .identity
dot.alpha = 1.0
})
}
// step 3: fade out dots and translate to the right
UIView.addKeyframe(withRelativeStartTime: 0.8, relativeDuration: 0.1) {
capsuleDots.forEach({ dot in
dot.alpha = 0.0
dot.transform = offstageRight
})
}
// step4: make capsure move to narrow width
UIView.addKeyframe(withRelativeStartTime: 0.9, relativeDuration: 0.1) {
capsule.bounds = capsuleFrameNarrow
}
})
}
Wrapping the keyframes in a UIViewPropertyAnimator makes it easy to scrub the animation (among other things).
In case it's useful for anyone, I've pushed a small project to GitHub that allows you to jump in an explore/refine/debug animations with UIViewPropertyAnimator. It includes boilerplate for connecting the UISlider to the animation so all you have to focus on is the animation itself.
This is all for debugging the animation, for production of course you'll probably want to remove hard coded sizes so it can be potentially reused at different scales etc.
It very easy to implement
animatedImage(with:duration:)
or
var animationImages: [UIImage]?
example:
UIImageView.animationImages = [image1, image2, image3, image4,...]
UIImageView.animationDuration = 5
UIImageView.startAnimating()
You will get ordered animation with couple of lines only

CATransaction: a view flashes on completion

I'm writing a little bit complex animation, which goes in 2 steps:
Change opacity to 0 of UIViews that are not need to be visible and move a UIImageView (which has alpha = 1) to another CGPoint (position).
Change opacity of another UIView to 1 and the opacity of the UIImageView from the previous step to 0, and then after the animation of this step is finished, remove UIImageView from superview.
I've done it this way:
The first step is done without an explicit CATransaction. These 2 animations just have beginTime set to CACurrentMediaTime(). And I'm applying changes to the views right after layer.addAnimation(...) call.
Everything works fine here.
In the second step implementation I call CATransaction.begin() at the beginning.
Inside begin/commit calls to CATransaction I create and add 2 CABasicAnimations to 2 different layers: one for changing the opacity from 0 to 1 (for UIView), and one for changing the opacity from 1 to 0 (for UIImageView). Both animations have beginTime set to CACurrentMediaTime() + durationOfThePreviousStep.
And right after CATransaction.begin() I call CATransaction.setCompletionBlock({...}), and in this completion block I apply changes to these two views: set their new alphas and remove UIImageView from superview.
The problem is, at the end of this whole animation the UIView that has alpha animated to 1 flashes, which means its alpha sets back to 0 (though I've set its alpha to 1 in the completion block) and right after this the completion block executes and its alpha goes up to 1 again.
Well, the question is, how to get rid of this flashing? Maybe this animation can be done in better way?
P.S. I'm not using UIView animations because I'm interested in custom timing functions for these animations.
EDIT 1:
Here's the code. I've deleted UIImageView alpha animation because it's not really necessary.
var totalDuration: CFTimeInterval = 0.0
// Alpha animations.
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 1
alphaAnimation.toValue = 0
alphaAnimation.beginTime = CACurrentMediaTime()
alphaAnimation.duration = 0.15
let alphaAnimationName = "viewsFadeOut"
view1.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view1.alpha = 0
view2.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view2.alpha = 0
view3.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view3.alpha = 0
view4.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view4.alpha = 0
// Image View moving animation.
// Add to total duration.
let rect = /* getting rect */
let newImagePosition = view.convertPoint(CGPoint(x: CGRectGetMidX(rect), y: CGRectGetMidY(rect)), fromView: timeView)
let imageAnimation = CABasicAnimation()
imageAnimation.keyPath = "position"
imageAnimation.fromValue = NSValue(CGPoint: imageView!.layer.position)
imageAnimation.toValue = NSValue(CGPoint: newImagePosition)
imageAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
imageAnimation.beginTime = CACurrentMediaTime()
imageAnimation.duration = 0.3
imageView!.layer.addAnimation(imageAnimation, forKey: "moveImage")
imageView!.center = newImagePosition
totalDuration += imageAnimation.duration
// Time View alpha.
CATransaction.begin()
CATransaction.setCompletionBlock {
self.timeView.alpha = 1
self.imageView!.removeFromSuperview()
self.imageView = nil
}
let beginTime = CACurrentMediaTime() + totalDuration
let duration = 0.3
alphaAnimation.fromValue = 0
alphaAnimation.toValue = 1
alphaAnimation.beginTime = beginTime
alphaAnimation.duration = duration
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
/* imageView alpha animation is not necessary, so I removed it */
CATransaction.commit()
EDIT 2: Piece of code that cause the problem:
CATransaction.begin()
CATransaction.setCompletionBlock {
self.timeView.alpha = 1
}
let duration = 0.3
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 0.0
alphaAnimation.toValue = 1.0
alphaAnimation.duration = duration
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
CATransaction.commit()
Maybe the problem is because the timeView has a UITextField and a UIScrollView with 4 subviews. I've tried to replace timeView with a snapshot of timeView (UIImageView), but that didn't help.
EDIT 3:
New code. With this code, timeView always has alpha = 1, and it also animates from 0 to 1.
CATransaction.begin()
CATransaction.setCompletionBlock {
self.imageView!.removeFromSuperview()
self.imageView = nil
}
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 0.0
alphaAnimation.toValue = 1.0
alphaAnimation.duration = 0.3
alphaAnimation.beginTime = beginTime
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
timeView.alpha = 1.0
CATransaction.commit()
Just looking at your code, I would expect it to flash. Why? Because you have animated timeView's layer's opacity from 0 to 1, but you have not set it to 1 (except in the completion handler, which will happen later). Thus, we animate the presentation layer from 0 to 1 and then the animation ends and it is revealed that the opacity of the real layer was 0 all along.
So, set timeView's layer's opacity to 1 before your animation gets going. Also, since you are using a delayed beginTime, you will need to set your animation's fillMode to "backwards".
I was able to get good results by modifying your test code to be self-contained and to look like this; there is a delay, the view fades in, and there is no flash at the end:
CATransaction.begin()
let beginTime = CACurrentMediaTime() + 1.0 // arbitrary, just testing
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 0.0
alphaAnimation.toValue = 1.0
alphaAnimation.duration = 1.0 // arbitrary, just testing
alphaAnimation.fillMode = "backwards"
alphaAnimation.beginTime = beginTime
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
timeView.layer.opacity = 1.0
CATransaction.commit()
There are some other things about your code that I find rather odd. It is somewhat risky using a transaction completion block in this way; I don't see why you don't give your animation a delegate. Also, you are reusing alphaAnimation multiple times; I can't recommend that. I would create a new CABasicAnimation for each animation, if I were you.

Save CoreAnimation sequence frame by frame

How can I save each frame of a CoreAnimation based animation (as image files)?
Here's my little playground scene. The animation lasts 2.4 seconds.
let stage = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 300, height: 300))
stage.backgroundColor = UIColor.blueColor();
var dot = UIView(frame: CGRectMake(0, 0, 10, 10))
dot.backgroundColor = UIColor.redColor()
dot.center = stage.center
UIView.animateWithDuration(2.4, animations: { () -> Void in
dot.center.y = dot.center.y + 50
})
I know how to save a static UIView frame as PDF but I am not sure how to hook into the animation sequence while it happens and capture/save the view frame by frame.
Option 1
As far as I see it I need to hook into the animation block and save the stage view for each frame (at a given frame rate?). How can I do this? Is there some sort of callback I can use?
Option 2
While looking for a solution I came across another option that looks even more promising (based on this question and this awesome blog post). Using a CBAnimation, setting a timingFunction and then setting the timeOffset to the progress states I want to render.
Here's an example for the frame at 50% progress (using a different example here).
var drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
…
drawAnimation.fromValue = NSNumber(float: 0.0)
drawAnimation.toValue = NSNumber(float: 1.0)
drawAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
circle.addAnimation(drawAnimation, forKey: "drawCircleAnimation")
circle.speed = 0.0;
circle.timeOffset = 0.5
The problem is that I cannot capture this timeOffset based animation state. When I render the view I only get the final state instead of the frozen animation frame at 0.5.
Any help to resolve this is appreciated.

SCNTransaction AnimationOptions

I have an animation in my SceneKit project. It animates the node that is pressed. Now, the animation works with this code:
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.5)
SCNTransaction.setCompletionBlock {
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.5)
result.node!.position.y -= 1
SCNTransaction.commit()
}
result.node!.position.y += 1
SCNTransaction.commit()
}
I want to make the node seem like it is jumping, therefore I would like to use some animation options, such as you can use with a UIView: CurveEaseIn etc.. (I want it to start slowly, end abrupt. The second should be abrupt first, then slowly.)
Is there a way to use these for a SCNTransaction? Or is there maybe a better way to make it 'bounce'?
Thanks in advance :)
Changing the timing function
Yes, you can change the timing function with SCNTransaction.
You do that by creating a CAMediaTimingFunction object and assigning it using setAnimationTimingFunction(). There are a couple of named timing functions (for example "ease in"), or you can create one using two set of control points.
let easeIn = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
SCNTransaction.setAnimationTimingFunction(easeIn)
Using a key-frame animation
Another alternative is to create a more custom key-frame animation for the node's y-position. I did a bounce looking key-frame animation in the sample code for Chapter 5 of my book about Scene Kit:
var jump = CAKeyframeAnimation(keyPath: "position.y")
let easeIn = CAMediaTimingFunction(controlPoints: 0.35, 0.0, 1.0, 1.0)
let easeOut = CAMediaTimingFunction(controlPoints: 0.0, 1.0, 0.65, 1.0)
jump.values = [0.000000, 0.433333, 0.000000, 0.124444, 0.000000, 0.035111, 0.000000];
jump.keyTimes = [0.000000, 0.255319, 0.531915, 0.680851, 0.829788, 0.914894, 1.000000];
jump.timingFunctions = [easeOut, easeIn, easeOut, easeIn, easeOut, easeIn ];
jump.duration = 0.783333;
yourNodeThatWasClicked.addAnimation(jump, forKey: "jump and bounce")
If you go down this path, I would recommend finding somewhere that you can experiment, tweak, and play with the key times and values of the key-frame animation. Perhaps a playground or a small custom app with sliders where you can get fast iterations.
you can use a CABasicAnimation with byValue of 1 and set autoreverses to YES. Finally set its timingFunction to kCAMediaTimingFunctionEaseIn (or, more specifically, a timing function initialized with this curve name).

CATransform3D breaks layer order?

Basically I want to build the scrollView like the one in iOS7 safari tab switcher. I use CATransform3D to get the feeling of oblique. However when I employ transform the layers just don't display in proper order.(They are in correct order before transform, seems like a total reversal in order.Back layer jumps to the front). How can I fix this thorny problem?
By the way in my case superView.bringSubViewToFront doesn't work.
My code:
//Create imageView
...
scroller.addSubview(imageView)
let layer = imageView.layer
//Set shadow
layer.shadowOpacity = 0.2
layer.shadowRadius = 2
layer.shadowOffset = CGSizeMake(6, -10)
//Transform, if without following code the layer order is correct
var transform = CATransform3DIdentity
transform.m34 = 0.0009
let radiants = 0.11*M_PI
transform = CATransform3DRotate(transform, CGFloat(radiants), 1.0, 0.0, 0.0)
scroller.bringSubviewToFront(imageView)//It doesn't work.
I just found that if you set the z axis to 1.0 in CATransform3DTranslate the layer become in front of other layers, so just make it 0.

Resources