import UIKit
class ViewController: UIViewController {
// dummy y values
let yValue: [CGFloat] = [3.0, 2, 5, 6, 1, 3]
// bezierPath to use as the fromValue for animation
var oldDotPath = UIBezierPath()
var oldLinePath = UIBezierPath()
// basic computed properties
var topInset: CGFloat {
viewHeight/2 - appAreaHeight/2
}
var xOffset: CGFloat {
appAreaWidth/CGFloat(yValue.count-1)
}
var dotX: CGFloat {
viewWidth/2 - appAreaWidth/2
}
var rate: CGFloat {
appAreaHeight/yValue.max()!
}
var viewHeight: CGFloat {
view.bounds.height
}
var viewWidth: CGFloat {
view.bounds.width
}
var appAreaHeight: CGFloat {
viewHeight*0.8
}
var appAreaWidth: CGFloat {
viewWidth*0.9
}
let dotLayer = CAShapeLayer()
let lineLayer = CAShapeLayer()
lazy var button : UIButton = {
let button = UIButton()
button.setTitle("tap", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
addToView()
drawInitialLines()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
layoutButton()
}
@objc func buttonTapped() {
updatePathValues()
}
private func addToView() {
view.addSubview(button)
}
private func layoutButton() {
let buttonWidth: CGFloat = 100
let buttonHeight: CGFloat = 50
let buttonX = viewWidth/2 - buttonWidth/2
let buttonY = viewHeight/2 + appAreaHeight/2 - buttonHeight
button.frame = CGRect(x: buttonX, y: buttonY, width: buttonWidth, height: buttonHeight)
}
private func updatePathValues() {
let linePath = UIBezierPath()
// for dots
let dotPath = UIBezierPath()
let dotRadius: CGFloat = 5
for i in 0..<yValue.count {
let point = CGPoint(x: dotX + xOffset*CGFloat(i), y: topInset+appAreaHeight-rate*yValue[i])
if i == 0 {
dotPath.move(to: point)
dotPath.addArc(withCenter: point, radius: dotRadius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
linePath.move(to: point)
} else {
dotPath.move(to: point)
dotPath.addArc(withCenter: point, radius: dotRadius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
linePath.addLine(to: point)
}
}
let dotAnimation = CABasicAnimation(keyPath: "path")
dotAnimation.fromValue = oldDotPath.cgPath
dotAnimation.toValue = dotPath.cgPath
dotAnimation.duration = 0.9
let lineAnimation = CABasicAnimation(keyPath: "path")
lineAnimation.fromValue = oldLinePath.cgPath
lineAnimation.toValue = linePath.cgPath
lineAnimation.duration = 0.9
// add animation to the layers
dotLayer.add(dotAnimation, forKey: "path")
lineLayer.add(lineAnimation, forKey: "path")
// persisting path changes by assigning the info
dotLayer.path = dotPath.cgPath
lineLayer.path = linePath.cgPath
}
private func drawInitialLines() {
// for lines
let linePath = UIBezierPath()
// for dots
let dotPath = UIBezierPath()
let dotRadius: CGFloat = 5
// drawing lines and dots that fit dummy y values
for i in 0..<yValue.count {
let point = CGPoint(x: dotX + xOffset*CGFloat(i), y: topInset+appAreaHeight/2)
if i == 0 {
dotPath.move(to: point)
dotPath.addArc(withCenter: point, radius: dotRadius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
linePath.move(to: point)
} else {
dotPath.move(to: point)
dotPath.addArc(withCenter: point, radius: dotRadius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
linePath.addLine(to: point)
}
}
// retaining the paths for the later use in animation
oldDotPath = dotPath
oldLinePath = linePath
dotLayer.fillColor = UIColor.systemPink.cgColor
dotLayer.lineWidth = 3
// setting the path here. by changing the path, can animate the dots.
dotLayer.path = dotPath.cgPath
lineLayer.strokeColor = UIColor.systemCyan.cgColor
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.lineWidth = 3
lineLayer.lineJoin = .round
lineLayer.lineCap = .round
// setting the path here...
lineLayer.path = linePath.cgPath
// adding these layers to the view's backing layer
view.layer.addSublayer(lineLayer)
view.layer.addSublayer(dotLayer)
}
}