ios - Animate CAShapeLayer with circle UIBezierPath and CABasicAnimation -
i'd animate circle angle 0 360 degrees in 15 sec.
the animation weird. know start/end angle issue, faced kind of problem circle animations, don't know how solve one.
var circle_layer=cashapelayer() var circle_anim=cabasicanimation(keypath: "path") func init_circle_layer(){ let w=circle_view.bounds.width let center=cgpoint(x: w/2, y: w/2) //initial path let start_angle:cgfloat = -0.25*360*cgfloat.pi/180 let initial_path=uibezierpath(arccenter: center, radius: w/2, startangle: start_angle, endangle: start_angle, clockwise: true) initial_path.addline(to: center) //final path let end_angle:cgfloat=start_angle+360*cgfloat(cgfloat.pi/180) let final_path=uibezierpath(arccenter: center, radius: w/2, startangle: start_angle, endangle: end_angle, clockwise: true) final_path.addline(to: center) //init layer circle_layer.path=initial_path.cgpath circle_layer.fillcolor=uicolor(hex_code: "ea535d").cgcolor circle_view.layer.addsublayer(circle_layer) //init anim circle_anim.duration=15 circle_anim.fromvalue=initial_path.cgpath circle_anim.tovalue=final_path.cgpath circle_anim.isremovedoncompletion=false circle_anim.fillmode=kcafillmodeforwards circle_anim.delegate=self } func start_circle_animation(){ circle_layer.add(circle_anim, forkey: "circle_anim") }
i want start on top @ 0 degrees , finish on top after full tour:
you can't animate fill of uibezierpath
(or @ least without introducing weird artifacts except in nicely controlled situations). can animate strokeend
of path
of cashapelayer
. , if make line width of stroked path wide (i.e. radius of final circle), , set radius of path half of of circle, you're looking for.
private var circlelayer = cashapelayer() private func configurecirclelayer() { let radius = min(circleview.bounds.width, circleview.bounds.height) / 2 circlelayer.strokecolor = uicolor(hexcode: "ea535d").cgcolor circlelayer.fillcolor = uicolor.clear.cgcolor circlelayer.linewidth = radius circleview.layer.addsublayer(circlelayer) let center = cgpoint(x: circleview.bounds.width/2, y: circleview.bounds.height/2) let startangle: cgfloat = -0.25 * 2 * .pi let endangle: cgfloat = startangle + 2 * .pi circlelayer.path = uibezierpath(arccenter: center, radius: radius / 2, startangle: startangle, endangle: endangle, clockwise: true).cgpath circlelayer.strokeend = 0 } private func startcircleanimation() { circlelayer.strokeend = 1 let animation = cabasicanimation(keypath: "strokeend") animation.fromvalue = 0 animation.tovalue = 1 animation.duration = 15 circlelayer.add(animation, forkey: nil) }
for ultimate control, when doing complex uibezierpath
animations, can use cadisplaylink
, avoiding artifacts can result when using cabasicanimation
of path
:
private var circlelayer = cashapelayer() private weak var displaylink: cadisplaylink? private var starttime: cftimeinterval! private func configurecirclelayer() { circlelayer.fillcolor = uicolor(hexcode: "ea535d").cgcolor circleview.layer.addsublayer(circlelayer) updatepath(percent: 0) } private func startcircleanimation() { starttime = cacurrentmediatime() displaylink = { let _displaylink = cadisplaylink(target: self, selector: #selector(handledisplaylink(_:))) _displaylink.add(to: .current, formode: .commonmodes) return _displaylink }() } @objc func handledisplaylink(_ displaylink: cadisplaylink) { // @objc qualifier needed swift 4 @objc inference let percent = cgfloat(cacurrentmediatime() - starttime) / 15.0 updatepath(percent: min(percent, 1.0)) if percent > 1.0 { displaylink.invalidate() } } private func updatepath(percent: cgfloat) { let w = circleview.bounds.width let center = cgpoint(x: w/2, y: w/2) let startangle: cgfloat = -0.25 * 2 * .pi let endangle: cgfloat = startangle + percent * 2 * .pi let path = uibezierpath() path.move(to: center) path.addarc(withcenter: center, radius: w/2, startangle: startangle, endangle: endangle, clockwise: true) path.close() circlelayer.path = path.cgpath }
then can do:
override func viewdidappear(_ animated: bool) { super.viewdidappear(animated) configurecirclelayer() startcircleanimation() } override func viewdiddisappear(_ animated: bool) { super.viewdiddisappear(animated) displaylink?.invalidate() // avoid displaylink keeping reference dismissed view during animation }
that yields:
Comments
Post a Comment