Home > Software engineering >  iOS - Spinner to a circular view
iOS - Spinner to a circular view

Time:09-24

I am trying to add spinner effect to a circular view.

    func spin() {
        let circlePathLayer = CAShapeLayer()
        circlePathLayer.fillColor = nil
        circlePathLayer.strokeColor = UIColor.redColor.cgColor
        circlePathLayer.lineWidth = 6
        layer.addSublayer(circlePathLayer)
        setPath(to: circlePathLayer)
        animate(layer: circlePathLayer)
    }
    
    private func setPath(to layer: CAShapeLayer) {
        layer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: layer.lineWidth / 2,
                                                                   dy: layer.lineWidth / 2)).cgPath
    }
    
    private func animate(layer: CAShapeLayer) {
        animateKeyPath(keyPath: "strokeEnd",
                       duration: 1,
                       values: [0, 1],
                       layer: layer)
        animateKeyPath(keyPath: "strokeStart",
                       duration: 1,
                       values: [(0 - 0.1) as CGFloat, (1 - 0.1) as CGFloat],
                       layer: layer)
    }
    
    private func animateKeyPath(keyPath: String, duration: CFTimeInterval, values: [CGFloat], layer: CAShapeLayer) {
        let animation = CAKeyframeAnimation(keyPath: keyPath)
        animation.values = values
        animation.calculationMode = .linear
        animation.duration = duration
        animation.repeatCount = Float.infinity
        layer.add(animation, forKey: animation.keyPath)
    }

But for the above code, at the end of animation duration, a flicker happens every time at 1 position of the strokeEnd. How could I overcome the flicker and regulate the spinner?

Just to notice the effect visually, I have set the duration to 5 seconds. Please notice when the spinner reaches 3'o clock position.

enter image description here

CodePudding user response:

I think strokeStart and strokeEnd are constrained to be between 0 and 1. When you try to set strokeStart to a negative number (like -0.1) the system pins it to zero.

Try this solution:

//: A UIKit based Playground for presenting user interface

import UIKit
import PlaygroundSupport

class SpinnerView : UIView {
    let circlePathLayer = CAShapeLayer()

    override func didMoveToSuperview() {
        layer.addSublayer(circlePathLayer)
    }

    func spin() {
        let circleFrame = bounds.insetBy(dx: -40, dy: -40)
        circlePathLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
        
        circlePathLayer.strokeColor = UIColor.red.cgColor
        circlePathLayer.fillColor = nil
        circlePathLayer.lineWidth = 6

        setPath(to: circlePathLayer)
        animate(layer: circlePathLayer)
    }

    private func setPath(to layer: CAShapeLayer) {
        let path = UIBezierPath()
        path.addArc(withCenter: CGPoint(x: layer.bounds.midX,
                                        y: layer.bounds.midY),
                    radius: (bounds.width / 2.0) - layer.lineWidth / 2,
                    startAngle: 0,
                    endAngle: 2 * .pi / 10.0,
                    clockwise: true)
        layer.path = path.cgPath
    }

    private func animate(layer: CAShapeLayer) {
        let animation = CABasicAnimation(keyPath: "transform.rotation")
        animation.duration = 1
        animation.repeatCount = Float.infinity
        animation.fromValue = 0
        animation.toValue = 2 * CGFloat.pi
        layer.add(animation, forKey: "transform.rotation")
    }
}

class MyViewController : UIViewController {
    let spinner = SpinnerView()

    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white

        spinner.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(spinner)

        view.addConstraints([
            NSLayoutConstraint(item: spinner,
                               attribute: .centerX,
                               relatedBy: .equal,
                               toItem: view,
                               attribute: .centerX,
                               multiplier: 1.0,
                               constant: 0),
            NSLayoutConstraint(item: spinner,
                               attribute: .centerY,
                               relatedBy: .equal,
                               toItem: view,
                               attribute: .centerY,
                               multiplier: 1.0,
                               constant: 0),
            NSLayoutConstraint(item: spinner,
                               attribute: .width,
                               relatedBy: .equal,
                               toItem: view,
                               attribute: .width,
                               multiplier: 1.0,
                               constant: -40)
        ])

        spinner.addConstraints([
            NSLayoutConstraint(item: spinner,
                               attribute: .width,
                               relatedBy: .equal,
                               toItem: spinner,
                               attribute: .height,
                               multiplier: 1.0,
                               constant: 0)
        ])

        self.view = view
    }

    override func viewDidLayoutSubviews() {
        spinner.spin()
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
  • Related