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:
Related
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
}
how to make a line dash animation like ig story?i try to make dynamic line dash ,but failed ;
( CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotateAnimation.fromValue = #0;
rotateAnimation.toValue = #(M_PI_2*3);
CABasicAnimation * strokeStartAnimation = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
strokeStartAnimation.fromValue = #(0);
strokeStartAnimation.toValue = #0.6;
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = #[rotateAnimation,strokeStartAnimation];
animationGroup.duration = 1.5;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
animationGroup.delegate = self;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.circlarShapeLayer addAnimation:animationGroup forKey:#"animationGroup"];
I would probably look into CAReplicatorLayer. You can do some interesting things with it. I took a quick stab in Playgrounds. You can play with the the timing and instance delay to see if you can get it to your liking. I also did not put a lot of thought into the instance color change. You could also set a gradient image to a layer and mask it with the CAShapeLayer and add that to the replicator. The only other way would be to add the layers manually which you could do using the same code that the CAReplicatorLayer uses in the below code and you would then have more control. You would just have a for loop and keep rotating the layer around or using a different portion of the stroke. Then keep and array of those and animate when needed.
import Foundation
import UIKit
import PlaygroundSupport
class InstagramProfileSpinner : UIView{
var circlePiece : CAShapeLayer = CAShapeLayer()
var replicator : CAReplicatorLayer = CAReplicatorLayer()
lazy var imageView : UIImageView = {
let img = UIImageView(frame: self.bounds)
img.layer.cornerRadius = self.bounds.width/2
img.layer.masksToBounds = true
img.contentMode = .scaleAspectFill
return img
}()
var avatarURL : URL?{
didSet{
configureAvatar()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpShapeLayer()
self.addSubview(imageView)
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpShapeLayer()
self.addSubview(imageView)
}
func setUpShapeLayer(){
if let sublayers = self.layer.sublayers,sublayers.contains(replicator){} else{
circlePiece = CAShapeLayer()
circlePiece.frame = self.bounds
circlePiece.path = self.pathForCircle()
circlePiece.strokeColor = UIColor(red: 254/255, green: 136/255, blue: 7/255, alpha: 1).cgColor
circlePiece.lineWidth = 2.5
circlePiece.lineJoin = .round
circlePiece.lineCap = .round
circlePiece.fillColor = UIColor.clear.cgColor
circlePiece.strokeEnd = 0.025
let count = 1/circlePiece.strokeEnd
//set up replicator
replicator.instanceCount = Int(count)
let angle = (2.0*Double.pi)/Double(count)
replicator.instanceTransform = CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)
replicator.instanceRedOffset = -0.002
replicator.instanceGreenOffset = -0.04
replicator.instanceBlueOffset = -0.02
replicator.addSublayer(circlePiece)
self.layer.addSublayer(replicator)
}
}
func pathForCircle()->CGPath{
let path = UIBezierPath(ovalIn: self.bounds.insetBy(dx: 5, dy: 5))
return path.cgPath
}
func animateSpinner(){
let basic = CABasicAnimation(keyPath: "strokeEnd")
basic.fromValue = circlePiece.strokeEnd
basic.toValue = circlePiece.strokeEnd/5
basic.duration = 1
basic.autoreverses = true
basic.repeatCount = .infinity
replicator.instanceDelay = 0.25
circlePiece.add(basic, forKey: "littleStrokes")
}
func removeAnimation(){
if let animation = circlePiece.presentation(),
let stroke = animation.value(forKeyPath: "strokeEnd") as? CGFloat{
let final = CABasicAnimation(keyPath: "strokeEnd")
final.toValue = circlePiece.strokeEnd
final.duration = 1
replicator.instanceDelay = 0
circlePiece.add(final, forKey: "littleStrokes")
}
}
func configureAvatar(){
guard let url = self.avatarURL else{return}
self.imageView.image = nil
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let dt = data{
DispatchQueue.main.async {
self.imageView.image = UIImage(data: dt)
}
}
}.resume()
}
override func layoutSubviews() {
super.layoutSubviews()
replicator.frame = self.bounds
let inset = self.bounds.width * 0.08
imageView.frame = self.bounds.insetBy(dx: inset, dy:inset)
imageView.layer.cornerRadius = imageView.frame.width/2
}
}
class ViewController:UIViewController{
var check = true
var circle = InstagramProfileSpinner(frame: CGRect(x: 30, y: 30, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(circle)
let url = URL(string: "https://images.pexels.com/photos/450271/pexels-photo-450271.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260")
circle.avatarURL = url
circle.animateSpinner()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 10) {
self.circle.removeAnimation()
}
}
}
let viewController = ViewController()
PlaygroundPage.current.liveView = viewController
PlaygroundPage.current.needsIndefiniteExecution
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)"
}
}
}
}
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()
}
}
I would like to have some kind of pulse animation (infinite loop "scale in - scale out") on a UIButton so it gets users' attention immediately.
I saw this link How to create a pulse effect using -webkit-animation - outward rings but I was wondering if there was any way to do this only using native framework?
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"opacity"];
theAnimation.duration=1.0;
theAnimation.repeatCount=HUGE_VALF;
theAnimation.autoreverses=YES;
theAnimation.fromValue=[NSNumber numberWithFloat:1.0];
theAnimation.toValue=[NSNumber numberWithFloat:0.0];
[theLayer addAnimation:theAnimation forKey:#"animateOpacity"]; //myButton.layer instead of
Swift
let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
pulseAnimation.duration = 1
pulseAnimation.fromValue = 0
pulseAnimation.toValue = 1
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
pulseAnimation.autoreverses = true
pulseAnimation.repeatCount = .greatestFiniteMagnitude
view.layer.add(pulseAnimation, forKey: "animateOpacity")
See the article "Animating Layer Content"
Here is the swift code for it ;)
let pulseAnimation = CABasicAnimation(keyPath: "transform.scale")
pulseAnimation.duration = 1.0
pulseAnimation.fromValue = NSNumber(value: 0.0)
pulseAnimation.toValue = NSNumber(value: 1.0)
pulseAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
pulseAnimation.autoreverses = true
pulseAnimation.repeatCount = .greatestFiniteMagnitude
self.view.layer.add(pulseAnimation, forKey: nil)
The swift code is missing a fromValue, I had to add it in order to get it working.
pulseAnimation.fromValue = NSNumber(value: 0.0)
Also forKey should be set, otherwise removeAnimation doesn't work.
self.view.layer.addAnimation(pulseAnimation, forKey: "layerAnimation")
func animationScaleEffect(view:UIView,animationTime:Float)
{
UIView.animateWithDuration(NSTimeInterval(animationTime), animations: {
view.transform = CGAffineTransformMakeScale(0.6, 0.6)
},completion:{completion in
UIView.animateWithDuration(NSTimeInterval(animationTime), animations: { () -> Void in
view.transform = CGAffineTransformMakeScale(1, 1)
})
})
}
#IBOutlet weak var perform: UIButton!
#IBAction func prefo(sender: AnyObject) {
self.animationScaleEffect(perform, animationTime: 0.7)
}