Search code examples
iosswiftuibezierpath

How to draw Infinity and move circle on that path?


I want to draw a Infinity Symbol and on that path I have to move a filled circle to cover all the path. And Its should be customize-able, like for some value it should stay on left side and for some value it will go to another side. Is this possible first of all ?

Right now I am able to draw a circle and I am able to move circle on that path using this :

    let circlePath = UIBezierPath(arcCenter: CGPoint(x:self.view.frame.midX, y:self.view.frame.midY), radius: self.view.frame.size.width/3, startAngle: 0, endAngle:CGFloat(Double.pi)*2, clockwise: true)

    circlePathSize = circlePath.bounds.size

    // circleLayer is only used to locate the circle animation path

    circleLayer.path = circlePath.cgPath
    circleLayer.strokeColor = UIColor.white.cgColor
    circleLayer.shadowColor = UIColor.darkGray.cgColor
    circleLayer.shadowOpacity = 7.0
    circleLayer.shadowRadius = 2.0
    circleLayer.shadowOffset = CGSize(width: -1, height: 0)
    circleLayer.lineWidth = 3.0
    circleLayer.fillColor = UIColor.clear.cgColor
    self.view.layer.addSublayer(circleLayer)

    let animation = CAKeyframeAnimation(keyPath: "position");
    animation.duration = 5.0
    animation.repeatCount = MAXFLOAT
    animation.path = circlePath.cgPath

    //whatever the value of origin for squareView will not affect the animation
    squareView.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
    squareView.layer.cornerRadius = 10
    squareView.layer.shadowColor = UIColor.darkGray.cgColor
    squareView.layer.shadowOpacity = 7.0
    squareView.layer.shadowRadius = 3.0
    squareView.layer.shadowOffset = CGSize(width: 2, height: 0)

    squareView.backgroundColor = UIColor.white
    self.view.addSubview(squareView)
    // You can also pass any unique string value for key
    squareView.layer.add(animation, forKey: nil)

And I am able to draw sine curve also using this :

let origin = CGPoint(x: width * (1 - graphWidth) / 2, y: height * 0.50)

        let path = UIBezierPath()
        path.move(to: origin)

        for angle in stride(from: 5.0, through: 360.0, by: 5.0) {
            let x = origin.x + CGFloat(angle/360.0) * width * graphWidth
            let y = origin.y - CGFloat(sin(angle/180.0 * Double.pi)) * height * amplitude
            path.addLine(to: CGPoint(x: x, y: y))
        }

        UIColor.white.setStroke()
        path.stroke()

But I am not able to mix them up to get what I desire. Any help would be appreciated. Thanks.


Solution

  • Finally I was able to achieve it. For someone who wants to do the same, this is my UIView class :

    class InfinitySymbolView: UIView {
    
        var path: UIBezierPath!
    
        let circleLayer = CAShapeLayer()
        var circlePathSize:CGSize?
        let circleView = UIView()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.backgroundColor = UIColor.white
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
        }
    
    
        // Only override draw() if you perform custom drawing.
        // An empty implementation adversely affects performance during animation.
        override func draw(_ rect: CGRect) {
            // Drawing code
    
            let height = self.frame.height
            let width = self.frame.width
            let heightFactor = height/4
            let widthFactor = width/4
    
            path = UIBezierPath()
            path.lineWidth = 3.0
    
            path.move(to: CGPoint(x:widthFactor, y: heightFactor * 3))
            path.addCurve(to: CGPoint(x:widthFactor, y: heightFactor), controlPoint1: CGPoint(x:0, y: heightFactor * 3), controlPoint2: CGPoint(x:0, y: heightFactor))
    
            path.move(to: CGPoint(x:widthFactor, y: heightFactor))
            path.addCurve(to: CGPoint(x:widthFactor * 3, y: heightFactor * 3), controlPoint1: CGPoint(x:widthFactor * 2, y: heightFactor), controlPoint2: CGPoint(x:widthFactor * 2, y: heightFactor * 3))
    
            path.move(to: CGPoint(x:widthFactor * 3, y: heightFactor * 3))
            path.addCurve(to: CGPoint(x:widthFactor * 3, y: heightFactor), controlPoint1: CGPoint(x:widthFactor * 4 + 5, y: heightFactor * 3), controlPoint2: CGPoint(x:widthFactor * 4 + 5, y: heightFactor))
    
            path.move(to: CGPoint(x:widthFactor * 3, y: heightFactor))
            path.addCurve(to: CGPoint(x:widthFactor, y: heightFactor * 3), controlPoint1: CGPoint(x:widthFactor * 2, y: heightFactor), controlPoint2: CGPoint(x:widthFactor * 2, y: heightFactor * 3))
    
            UIColor.purple.setStroke()
            path.stroke()
    
            let shapeLayer = CAShapeLayer()
            shapeLayer.path = path.cgPath
            shapeLayer.fillColor = UIColor.clear.cgColor
            self.layer.addSublayer(shapeLayer)
    
            animateCircleOnPath()
        }
    
        func animateCircleOnPath(){
            let animation = CAKeyframeAnimation(keyPath: "position");
            animation.duration = 5.0
            animation.repeatCount = MAXFLOAT
            animation.path = path.cgPath
            circleView.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
            circleView.layer.cornerRadius = 10
            circleView.layer.shadowColor = UIColor.darkGray.cgColor
            circleView.layer.shadowOpacity = 7.0
            circleView.layer.shadowRadius = 3.0
            circleView.layer.shadowOffset = CGSize(width: 2, height: 0)
            circleView.backgroundColor = UIColor.white
            self.addSubview(circleView)
            circleView.layer.add(animation, forKey: nil)
        }
    }