How to read keyPath value after animation? - ios

I'm animating show and hide of my FAB button, however I want to prevent showing FAB if it is already shown. How to read current layer scale?
func hideFab(){
let materialCurve = MDCAnimationTimingFunction.deceleration
let timingFunction = CAMediaTimingFunction.mdc_function(withType: materialCurve)
let animation = CABasicAnimation(keyPath:"transform.scale.xy")
animation.timingFunction = timingFunction
animation.fromValue = 1
animation.toValue = 0
animation.duration = 0.5
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
fab.layer.playAnimation({ (layer) -> CAAnimation in
animation
}, key: animation.keyPath!)
}
func showFab(){
let materialCurve = MDCAnimationTimingFunction.deceleration
let timingFunction = CAMediaTimingFunction.mdc_function(withType: materialCurve)
let animation = CABasicAnimation(keyPath:"transform.scale.xy")
animation.timingFunction = timingFunction
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.5
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
fab.layer.playAnimation({ (layer) -> CAAnimation in
animation
}, key: animation.keyPath!)
}

With this let currentScale = fab.layer.presentation()?.value(forKeyPath: "transform.scale.xy") ?? 0.0 you should be able to get the presentation scale value.
But I'd try to instead check if I showed or not the FAB with a tag:
let hiddenFABTag = 99
let shownFABTag = 100
func hideFAB(){
if fab.tag == shownFABTag {
...
...
fab.tag = hiddenFABTag
}
}
func showFAB(){
if fab.tag == hiddenFABTag {
...
...
fab.tag = shownFABTag
}
}
The first time you call it, either in viewDidLoad() or somewhere, you might want to give it a default tag value, if you know if it's going to be shown or hidden the first time by saying:
override func viewDidLoad() {
super.viewDidLoad()
fab.tag = hiddenFABTag
}

Related

CABasicAnimation performance before removeFromSuperView()

I have a code-created button. Every time I click, I want it to perform CABasicAnimation before removeFromSuperView(). But if I add removeFromSuperView() after that, it will instantly removeFromSuperView() without any animation.
#objc func bubblePressed(_ bubble:Bubble){
let animation = CABasicAnimation(keyPath:"opacity")
animation.fromValue = 1
animation.toValue = 0
animation.duration = 2.0
bubble.layer.add(animation, forKey:nil)
bubble.removeFromSuperview()
}
Is there any way that I can achieve this?
#objc func bubblePressed(_ bubble:Bubble){
CATransaction.begin()
CATransaction.setCompletionBlock({
// remove from super view
bubble.removeFromSuperview()
})
let animation = CABasicAnimation(keyPath:"opacity")
animation.fromValue = 1
animation.toValue = 0
animation.duration = 2.0
bubble.layer.add(animation, forKey:nil)
CATransaction.commit()
}
Workaround 2
#objc func bubblePressed(_ bubble:Bubble){
let animation = CABasicAnimation(keyPath:"opacity")
animation.fromValue = 1
animation.toValue = 0
animation.duration = 2.0
animatio.delegate = self
bubble.layer.add(animation, forKey:nil)
}
extension ViewController: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
bubble.removeFromSuperview()
// remove from super view in this function .. its Delegate method
}
}

Animating CALayer border alpha

I have a UIView with a border (color: Green, width: 10).
I'm trying to animate the border's alpha (in a loop) from the value of 1.0 to the value of 0.2 - then back to 1.0 - then back to 0.2 etc...
But CALayer doesn't have a property of borderAlpha so I'm not sure how can I do that.
I've tried this code, but it didn't work:
UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .autoreverse], animations: {
self.layer.borderColor = UIColor(cgColor: self.layer.borderColor!).withAlphaComponent(0.2).cgColor
}, completion: nil)
Does anybody know how can I do that?
Thank you!
Try using this
class animateStack: UIViewController {
#IBOutlet weak var animateView: UIView!{
didSet{
animateView.layer.borderColor = UIColor.black.cgColor
animateView.layer.borderWidth = 10
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
animateBorderAlpha()
}
private func animateBorderAlpha(){
/// First Animation
let animation = CABasicAnimation(keyPath: "borderColor")
animation.beginTime = 0
animation.toValue = UIColor.black.withAlphaComponent(0.1).cgColor
animation.fromValue = UIColor.black.cgColor
animation.duration = 2
/// Second Animation
let animation1 = CABasicAnimation(keyPath: "borderColor")
animation1.toValue = UIColor.black.cgColor
animation1.fromValue = UIColor.black.withAlphaComponent(0.1).cgColor
animation1.beginTime = animation.beginTime + animation.duration
animation.duration = 4
/// Animation Group
let borderColorAnimation: CAAnimationGroup = CAAnimationGroup()
borderColorAnimation.animations = [animation, animation1]
borderColorAnimation.duration = animation.duration + animation1.duration
borderColorAnimation.repeatCount = Float.greatestFiniteMagnitude
self.animateView.layer.add(borderColorAnimation, forKey: "borderColor")
}
}
Update
class animateViewClass: NSObject {
class func animateBorderAlpha(_ view: UIView){
/// First Animation
let animation = CABasicAnimation(keyPath: "borderColor")
animation.beginTime = 0
animation.toValue = UIColor.black.withAlphaComponent(0.1).cgColor
animation.fromValue = UIColor.black.cgColor
animation.duration = 2
/// Second Animation
let animation1 = CABasicAnimation(keyPath: "borderColor")
animation1.toValue = UIColor.black.cgColor
animation1.fromValue = UIColor.black.withAlphaComponent(0.1).cgColor
animation1.beginTime = animation.beginTime + animation.duration
animation.duration = 4
/// Animation Group
let borderColorAnimation: CAAnimationGroup = CAAnimationGroup()
borderColorAnimation.animations = [animation, animation1]
borderColorAnimation.duration = animation.duration + animation1.duration
borderColorAnimation.repeatCount = Float.greatestFiniteMagnitude
view.layer.add(borderColorAnimation, forKey: "borderColor")
}
}
Usage
animateViewClass.animateBorderAlpha(viewName)
/// Case of Subclass UIView
animateViewClass.animateBorderAlpha(self)
Updated and simplified. Use CALayer to create the border layer, then use CABasicAnimation to achieve fade-in-out effect:
class BorderView: UIView {
private var boarderLayer:CALayer?
private let animationKey = "opacityAnimation"
override public var frame: CGRect {
didSet{
self.updateBorder()
}
}
func updateBorder() {
if boarderLayer == nil {
boarderLayer = CALayer()
boarderLayer?.borderColor = UIColor.red.cgColor
boarderLayer?.borderWidth = 5.0
self.layer.addSublayer(boarderLayer!)
}
boarderLayer?.frame = self.bounds
if (boarderLayer?.animation(forKey: animationKey) == nil) {
self.addAnimiation(layer: boarderLayer!,increasing:true)
}
}
func addAnimiation(layer:CALayer,increasing:Bool) {
CATransaction.begin()
CATransaction.setCompletionBlock{ [weak self] in
self?.addAnimiation(layer: layer,increasing:!increasing)
}
let animation = CABasicAnimation(keyPath: "opacity")
if increasing {
layer.opacity = 0.2
animation.fromValue = 1.0
animation.toValue = 0.2
}
else{
layer.opacity = 1.0
animation.fromValue = 0.2
animation.toValue = 1.0
}
animation.duration = 1.0
layer.add(animation, forKey: animationKey)
CATransaction.commit()
}
}
And the result:

Adding spring to pageCurl CATransition

I have managed (through the help of StackOverflow) to add a partial page curl programatically to my UIPageViewController. This is used a hint to the user that they have completed that page/screen and can proceed.
The code looks like this:
let animation = CATransition()
animation.duration = 0.3
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
animation.endProgress = 0.2
animation.type = "pageCurl"
myPageViewController.view.layer.add(animation, forKey: "MyTransition")
And here's what it looks like (apologies for poor quality - mp4 to gif not so good)
What i'd really like to do now is add some spring to that animation, so it looks a little nicer. But i'm finding it really difficult to get any information off Google (may not even be possible?).
This is what i've tried:
// Can't see an animation type on here, maybe keyPath is used as the type?
let x = CASpringAnimation(keyPath: "pageCurl")
// No endProgress either, just trying some random values now
x.fromValue = 0
x.toValue = 1
// No idea - found on Google - can play with the settings later
x.damping = 5
x.duration = x.settlingDuration
// These look familiar from my working animation, so i'll set these!
x.fillMode = kCAFillModeForwards
x.isRemovedOnCompletion = false
myPageViewController.view.layer.add(x, forKey: "MyNewTransition")
... and it does absolutely nothing. Not even a crash. I expect it's because you cant use pageCurl as the keyPath, iOS doesn't know what to do with it, so it's ignored.
Here's a video showing roughly what I want from this...
So does anyone have any idea if it's possible to do this pageCurl animation, but with some spring?
Edit for new gifs:
class ViewController: UIViewController {
var animated:Bool = false
var counter: Int = 0
var transition = CATransition()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.backgroundColor = UIColor.white.cgColor
}
#IBAction func animate(_ sender: Any) {
animated = false
counter = 0
animateCurlPage(start: 0.0, end: 0.35, duration: 0.4)
}
func animateCurlPage(start: Float, end: Float, duration: CFTimeInterval) {
if (counter == 3) { return }
UIView.animate(withDuration: duration, animations: {
if (self.animated) { return }
self.transition.type = "pageCurl"
self.transition.subtype = kCATransitionFromBottom
self.transition.duration = duration
self.transition.startProgress = start
self.transition.endProgress = end
self.transition.delegate = self
self.transition.fillMode = kCAFillModeForwards
self.transition.isRemovedOnCompletion = false
self.view.layer.add(self.transition, forKey: "transition1")
})
if animated {
animateRepeatCurlPage()
}
}
func animateRepeatCurlPage() {
UIView.animate(withDuration: 0.15, animations: {
self.transition.type = "pageCurl"
self.transition.subtype = kCATransitionFromBottom
self.transition.duration = 0.15
self.transition.startProgress = 0.32
self.transition.endProgress = 0.31
self.transition.delegate = self
self.transition.fillMode = kCAFillModeForwards
self.transition.isRemovedOnCompletion = (self.counter == 2) ? true : false
self.view.layer.add(self.transition, forKey: "transition2")
})
counter += 1
}
}
extension ViewController: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
animateCurlPage(start: 0.35, end: 0.32, duration: 0.2)
animated = true
}
}
Here is the result of the code:
I hope you will not add a new gif again :D ! Cheers !
Edit: You should add the gifs before my answer, now it looks like I added a wrong solution for your good question.
For page curl animation and spring animation you need to execute two animations in the same time, so you need an animation group to be able to handle them and also one of the most important thing is to have different key for each animation. In the current example if you use the same key: transition for both animations, only a single animation will be executed and usually, the last one.
You can play a bit the animation parameters, I just added some arbitrary values here.
#IBAction func animate(_ sender: Any) {
let animationGroup = CAAnimationGroup()
animationGroup.duration = 5
animationGroup.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionLinear)
animationGroup.animations = [animateCurlPage(), animateSpringTransition()]
view.layer.add(animationGroup, forKey: "animation")
}
func animateCurlPage() -> CAAnimation {
let transition = CATransition()
transition.type = "pageCurl"
transition.subtype = kCATransitionFromRight
transition.duration = 1
view.layer.add(transition, forKey: "transition")
return transition
}
func animateSpringTransition() -> CAAnimation{
let transitionX = CASpringAnimation(keyPath: "position.y")
transitionX.duration = 0.5
transitionX.fromValue = view.layer.position.y
transitionX.toValue = view.layer.position.y - 60
transitionX.damping = 5.0
transitionX.initialVelocity = 1.0
transitionX.stiffness = 20.0
transitionX.beginTime = CACurrentMediaTime()
view.layer.add(transitionX, forKey: "transitionX")
return transitionX
}
Here is the result:

Animate label count in with CABasicAnimation in swift?

I have added a CABasicAnimation on CAShapeLayer.Now in this animation my CAShapeLayer create an arc on circle with animation.
Now what I want when this animation proceed count of label should increase with a sync to animation of CAShapeLayer. PLease how can I do it?
let animation = CABasicAnimation(keyPath: "strokeEnd")
//animation.delegate = self
animation.duration = 2
animation.fromValue = 0
animation.toValue = 0.7 // changed here
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.isRemovedOnCompletion = false
progressCircle.add(animation, forKey: "ani")
Here ProgressCircle is the CAShapeLayer.
I used below code for increement label count with animation but it does not match with the animation.
func incrementLabel(to endValue: Int) {
let duration: Double = 1.0 //seconds
DispatchQueue.global().async {
for i in 0 ..< (endValue + 1) {
let sleepTime = UInt32(duration/Double(endValue) * 1000000.0)
usleep(sleepTime)
DispatchQueue.main.async {
self.label.text = "\(i)"
}
}
}
}

Re-Animate Gradient Background with different color in swift in ios

I want to re-animate gradient background with different color when animating.
I can successfully animate gradient color with this code.
let dayTopColor = CommonUtils.colorWithHexString("955EAC")
let dayBottomColor = CommonUtils.colorWithHexString("9F3050")
let dayToTopColor = CommonUtils.colorWithHexString("D15B52")
let dayToBottomColor = CommonUtils.colorWithHexString("CC4645")
let nightTopColor = CommonUtils.colorWithHexString("2D5E7C")
let nightBottomColor = CommonUtils.colorWithHexString("19337D")
let nightToTopColor = CommonUtils.colorWithHexString("21334E")
let nightToBottomColor = CommonUtils.colorWithHexString("101A55")
var isInSaudiArabia = false
var gradient : CAGradientLayer?
var toColors : AnyObject?
var fromColors : AnyObject?
func animateBackground(){
var layerToRemove: CAGradientLayer?
for layer in self.view.layer.sublayers!{
if layer.isKindOfClass(CAGradientLayer) {
layerToRemove = layer as? CAGradientLayer
}
}
layerToRemove?.removeFromSuperlayer()
self.gradient!.colors = [nightTopColor.CGColor, nightBottomColor.CGColor]
self.toColors = [nightToTopColor.CGColor, nightToBottomColor.CGColor]
self.view.layer.insertSublayer(self.gradient!, atIndex: 0)
animateLayer()
}
func animateLayer(){
self.fromColors = self.gradient!.colors!
self.gradient!.colors = self.toColors as? [AnyObject]
let animation : CABasicAnimation = CABasicAnimation(keyPath: "colors")
animation.delegate = self
animation.fromValue = fromColors
animation.toValue = toColors
animation.duration = 3.50
animation.removedOnCompletion = true
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.delegate = self
self.gradient!.addAnimation(animation, forKey:"animateGradient")
}
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
self.toColors = self.fromColors;
self.fromColors = self.gradient!.colors!
animateLayer()
}
CommonUtils.colorWithHexString() is a function which converts hex color to UIColor.
Btw when i try to change background color to day color while animating, background gradient color got flickered.
Is there anybody who knows solution.
The problem is that when you remove the layer, it stops the animation. But when the animation stops, animationDidStop is still getting called, which is starting a new animation, itself. So, you're removing the layer, which stops the animation, immediately starts another, but you're then starting yet another animation. You have dueling animations.
You can check flag to see if the animation finished properly before animationDidStop should call animateLayer.
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if flag {
toColors = fromColors;
fromColors = gradient!.colors!
animateLayer()
}
}
Personally, I'm not sure why you're removing and adding and removing the layer. And if you were, I'm not sure why you don't just gradient?.removeFromSuperlayer() rather than iterating through the layers.
Regardless, I'd just keep the gradient layer there, just check its presentationLayer and start the animation from there:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fromColors = [dayTopColor.CGColor, dayBottomColor.CGColor]
toColors = [dayToTopColor.CGColor, dayToBottomColor.CGColor]
gradient = CAGradientLayer()
gradient!.colors = fromColors!
gradient!.frame = view.bounds
view.layer.addSublayer(gradient!)
animateLayer()
}
let dayTopColor = CommonUtils.colorWithHexString("955EAC")
let dayBottomColor = CommonUtils.colorWithHexString("9F3050")
let dayToTopColor = CommonUtils.colorWithHexString("D15B52")
let dayToBottomColor = CommonUtils.colorWithHexString("CC4645")
let nightTopColor = CommonUtils.colorWithHexString("2D5E7C")
let nightBottomColor = CommonUtils.colorWithHexString("19337D")
let nightToTopColor = CommonUtils.colorWithHexString("21334E")
let nightToBottomColor = CommonUtils.colorWithHexString("101A55")
var gradient : CAGradientLayer?
var toColors : [CGColor]?
var fromColors : [CGColor]?
var day = true
func toggleFromDayToNight() {
day = !day
if day {
fromColors = [dayTopColor.CGColor, dayBottomColor.CGColor]
toColors = [dayToTopColor.CGColor, dayToBottomColor.CGColor]
} else {
fromColors = [nightTopColor.CGColor, nightBottomColor.CGColor]
toColors = [nightToTopColor.CGColor, nightToBottomColor.CGColor]
}
let colors = (gradient!.presentationLayer() as! CAGradientLayer).colors // save the in-flight current colors
gradient!.removeAnimationForKey("animateGradient") // cancel the animation
gradient!.colors = colors // restore the colors to in-flight values
animateLayer() // start animation
}
func animateLayer() {
let animation : CABasicAnimation = CABasicAnimation(keyPath: "colors")
animation.fromValue = gradient!.colors
animation.toValue = toColors
animation.duration = 3.50
animation.removedOnCompletion = true
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.delegate = self
gradient!.colors = toColors
gradient!.addAnimation(animation, forKey:"animateGradient")
}
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if flag {
swap(&toColors, &fromColors)
animateLayer()
}
}
#IBAction func didTapButton() {
toggleFromDayToNight()
}
}

Resources