Reputation: 11
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"];
Upvotes: 0
Views: 1063
Reputation: 4373
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
Upvotes: 2