How to draw an animated path with multiple colors in ios?
I need to draw something like the following image in an iOS app, except that the arc can contain more colors:
I know how to draw, but I am looking for a way to animate the drawing of a path.
There is a similar question here , except that the circle is not animated. This is another question that explains how to animate the circle, and it works fine in my case, except it doesn't handle multiple colors in the path.
How to do it?
source to share
I found a general solution that works really well. Since there is no way to draw a unique different color path, I just draw, without animation, all the different color paths that make up the path I want. After that, I drew a unique backward path that spans all of these paths and applied animation to that path.
For example, in the above example, I draw both arcs with the following code:
class CircleView: UIView {
let borderWidth: CGFloat = 20
let startAngle = CGFloat(M_PI)
let middleAngle = CGFloat(M_PI + M_PI / 2)
let endAngle = CGFloat(2 * M_PI)
var primaryColor = UIColor.redColor()
var secondaryColor = UIColor.blueColor()
var currentStrokeValue = CGFloat(0)
override func drawRect(rect: CGRect) {
var center = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
var radius = CGFloat(self.frame.width / 2 - borderWidth)
var path1 = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: middleAngle, clockwise: true)
var path2 = UIBezierPath(arcCenter: center, radius: radius, startAngle: middleAngle, endAngle: endAngle, clockwise: true)
path1.lineWidth = borderWidth
primaryColor.setStroke()
path1.stroke()
path2.lineWidth = borderWidth
path1.stroke()
secondaryColor.setStroke()
path2.stroke()
}
}
After that, I get the path path3
and then add it to the layer that will be added to the view:
var path3 = UIBezierPath(arcCenter: center, radius: radius, startAngle: endAngle, endAngle: startAngle, clockwise: true)
Note that this path spans the two previous paths in reverse order, since its startAngle is equal to the value endAngle
, its endAngle is equal startAngle
, and the clockwise property is set to.This true
path is the one I'm going to animate.
For example, if I want to show 40% of the entire (imaginary) path (the one made up of different color paths), I will outweigh that to show 60% of my coverage path path3
. The way we can animate path3
can be found in the link given in the question.
source to share
import UIKit
import QuartzCore
import CoreGraphics
class ViewController: UIViewController,UIGestureRecognizerDelegate {
var btnview : UIButton!
var buttonCenter = CGPoint.zero
var firstlayerpoint = CGPoint.zero
var firstLayer = CAShapeLayer()
var secondLayer = CAShapeLayer()
var thirdLayer = CAShapeLayer()
var initialPosition = CGRect()
let label = UILabel()
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var blueLabel: UILabel!
@IBOutlet weak var greenLabel: UILabel!
@IBOutlet weak var redLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
firstLayer = self.createCircleWithBounds(bounds: CGRect(x:0, y:0, width:100,height:100), Position: self.view.center, StrokeColor: UIColor.blue, LineWidth: 20.0)
firstLayer.strokeStart = 0.00
firstLayer.strokeEnd = 0.33
self.view.layer.addSublayer(firstLayer)
secondLayer = self.createCircleWithBounds(bounds: CGRect(x:0, y:0, width:100,height:100), Position: self.view.center, StrokeColor: UIColor.red, LineWidth: 20.0)
secondLayer.strokeStart = 0.33
secondLayer.strokeEnd = 0.66
self.view.layer.addSublayer(secondLayer)
thirdLayer = self.createCircleWithBounds(bounds: CGRect(x:0, y:0, width:100,height:100), Position: self.view.center, StrokeColor: UIColor.green, LineWidth: 20.0)
thirdLayer.strokeStart = 0.66
thirdLayer.strokeEnd = 1.00
self.view.layer.addSublayer(thirdLayer)
btnview = UIButton(frame: CGRect(x: self.view.center.x - 20 , y: self.view.center.y - 20 , width: 40, height: 40))
btnview.backgroundColor = UIColor.gray
btnview.isUserInteractionEnabled = true
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panButton(panGesture:)))
// panGesture.minimumNumberOfTouches = 1
btnview.addGestureRecognizer(panGesture)
self.view.addSubview(btnview)
// Do any additional setup after loading the view, typically from a nib.
nameLabel.isHidden = true
blueLabel.isHidden = true
greenLabel.isHidden = true
redLabel.isHidden = true
}
func panButton(panGesture: UIPanGestureRecognizer) {
//let translation = panGesture.translation(in: self.btnview)
panGesture.view!.center = btnview.center
panGesture.setTranslation(CGPoint.zero, in: self.view)
// var point = CGPoint.zero
// point = firstLayer.frame.size.center
if panGesture.state == .began {
label.isHidden = false
buttonCenter = btnview.center // store old button center
}
else if panGesture.state == .ended || panGesture.state == .failed || panGesture.state == .cancelled {
print(btnview.frame.origin.x)
print(greenLabel.frame.origin.x)
if btnview.frame.origin.x > greenLabel.frame.origin.x
{
// lblflayer.isHidden = false
// lblsecondlayer.isHidden = true
// lblthirdlayer.isHidden = true
nameLabel.isHidden = false
nameLabel.text = "Blue"
nameLabel.backgroundColor = UIColor.blue
}
else if btnview.frame.origin.x > blueLabel.frame.origin.x
{
// print(btnview.frame.origin.x)
// print(lblsecondlayer.frame.origin.x)
// lblsecondlayer.isHidden = false
// lblflayer.isHidden = true
// lblthirdlayer.isHidden = true
nameLabel.isHidden = false
nameLabel.text = "Red"
nameLabel.backgroundColor = UIColor.red
}
else if btnview.frame.origin.x > redLabel.frame.origin.x
{
print(btnview.frame.origin.x)
print(redLabel.frame.origin.x)
greenLabel.isHidden = true
// lblsecondlayer.isHidden = true
// lblthirdlayer.isHidden = false
nameLabel.isHidden = false
nameLabel.text = "Green"
nameLabel.backgroundColor = UIColor.green
}
else
{
nameLabel.isHidden = true
// lblflayer.isHidden = true
// lblsecondlayer.isHidden = true
// lblthirdlayer.isHidden = true
}
btnview.center = buttonCenter // restore button center
}
else
{
let location = panGesture.location(in: view) // get pan location
btnview.center = location // set button to where finger is
}
}
func createCircleWithBounds(bounds: CGRect, Position position: CGPoint, StrokeColor color: UIColor, LineWidth lineWidth: CGFloat) -> CAShapeLayer {
//let shapelayer = CAShapeLayer.layer
let shapelayer = CAShapeLayer()
shapelayer.strokeColor = color.cgColor
shapelayer.fillColor = UIColor.clear.cgColor
shapelayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: bounds.width / 2).cgPath
shapelayer.bounds = bounds
shapelayer.position = position
shapelayer.lineCap = kCALineCapButt
shapelayer.lineWidth = lineWidth
return shapelayer
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
source to share