Search code examples
iospaintcode

Make clockwise disappearing circle with PaintCode


I'm trying to make a clockwise disappearing circle on top of my logo with Paintcode. So far I made variables for start and end angles and I don't know how to make the step by step process for clockwise moving.

This is what I made in PaintCode:

PaintCode

And this is the function from the program:

func drawCanvas1(frame: CGRect = CGRect(x: 0, y: 0, width: 502, height: 480), startAngle: CGFloat = 360, endAngle: CGFloat = -360) {
    //// General Declarations
    let context = UIGraphicsGetCurrentContext()!

    //// Color Declarations
    let fillColor = UIColor(red: 0.093, green: 0.382, blue: 0.372, alpha: 1.000)
    let fillColor2 = UIColor(red: 0.967, green: 0.968, blue: 0.960, alpha: 1.000)

    //// Group 2
    //// Group 3
    context.saveGState()
    context.beginTransparencyLayer(auxiliaryInfo: nil)

    //// Clip Clip
    let clipPath = UIBezierPath(rect: CGRect(x: frame.minX + 129.03, y: frame.minY + 113.85, width: 238.25, height: 202.1))
    clipPath.addClip()


    //// Bezier Drawing
    let bezierPath = UIBezierPath()
    bezierPath.move(to: CGPoint(x: frame.minX + 257.2, y: frame.minY + 261.51))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 212.54, y: frame.minY + 216.85), controlPoint1: CGPoint(x: frame.minX + 232.53, y: frame.minY + 261.51), controlPoint2: CGPoint(x: frame.minX + 212.54, y: frame.minY + 241.52))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 257.2, y: frame.minY + 172.19), controlPoint1: CGPoint(x: frame.minX + 212.54, y: frame.minY + 192.18), controlPoint2: CGPoint(x: frame.minX + 232.53, y: frame.minY + 172.19))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 301.87, y: frame.minY + 216.85), controlPoint1: CGPoint(x: frame.minX + 281.87, y: frame.minY + 172.19), controlPoint2: CGPoint(x: frame.minX + 301.87, y: frame.minY + 192.18))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 257.2, y: frame.minY + 261.51), controlPoint1: CGPoint(x: frame.minX + 301.87, y: frame.minY + 241.52), controlPoint2: CGPoint(x: frame.minX + 281.87, y: frame.minY + 261.51))
    bezierPath.close()
    bezierPath.move(to: CGPoint(x: frame.minX + 332.37, y: frame.minY + 180.76))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 300.63, y: frame.minY + 170.19), controlPoint1: CGPoint(x: frame.minX + 326.52, y: frame.minY + 169.08), controlPoint2: CGPoint(x: frame.minX + 312.31, y: frame.minY + 164.34))
    bezierPath.addLine(to: CGPoint(x: frame.minX + 296.53, y: frame.minY + 172.1))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 257.32, y: frame.minY + 157.34), controlPoint1: CGPoint(x: frame.minX + 286.06, y: frame.minY + 162.92), controlPoint2: CGPoint(x: frame.minX + 272.35, y: frame.minY + 157.34))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 235.67, y: frame.minY + 161.42), controlPoint1: CGPoint(x: frame.minX + 249.68, y: frame.minY + 157.34), controlPoint2: CGPoint(x: frame.minX + 242.39, y: frame.minY + 158.8))
    bezierPath.addLine(to: CGPoint(x: frame.minX + 235.68, y: frame.minY + 152.59))
    bezierPath.addLine(to: CGPoint(x: frame.minX + 225.81, y: frame.minY + 150.61))
    bezierPath.addLine(to: CGPoint(x: frame.minX + 220.89, y: frame.minY + 159.38))
    bezierPath.addLine(to: CGPoint(x: frame.minX + 227.76, y: frame.minY + 165.21))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 197.8, y: frame.minY + 216.87), controlPoint1: CGPoint(x: frame.minX + 209.86, y: frame.minY + 175.48), controlPoint2: CGPoint(x: frame.minX + 197.8, y: frame.minY + 194.76))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 198.56, y: frame.minY + 226.38), controlPoint1: CGPoint(x: frame.minX + 197.8, y: frame.minY + 220.11), controlPoint2: CGPoint(x: frame.minX + 198.06, y: frame.minY + 223.28))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 198.57, y: frame.minY + 226.49), controlPoint1: CGPoint(x: frame.minX + 198.57, y: frame.minY + 226.42), controlPoint2: CGPoint(x: frame.minX + 198.57, y: frame.minY + 226.45))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 157.51, y: frame.minY + 215.5), controlPoint1: CGPoint(x: frame.minX + 171.6, y: frame.minY + 217.72), controlPoint2: CGPoint(x: frame.minX + 166.18, y: frame.minY + 216.58))
    bezierPath.addLine(to: CGPoint(x: frame.minX + 152.17, y: frame.minY + 223.39))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 230.93, y: frame.minY + 270.22), controlPoint1: CGPoint(x: frame.minX + 179.07, y: frame.minY + 232.4), controlPoint2: CGPoint(x: frame.minX + 208.29, y: frame.minY + 259.24))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 234.66, y: frame.minY + 271.92), controlPoint1: CGPoint(x: frame.minX + 232.15, y: frame.minY + 270.83), controlPoint2: CGPoint(x: frame.minX + 233.39, y: frame.minY + 271.39))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 234.9, y: frame.minY + 272.03), controlPoint1: CGPoint(x: frame.minX + 234.74, y: frame.minY + 271.95), controlPoint2: CGPoint(x: frame.minX + 234.83, y: frame.minY + 271.99))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 239.12, y: frame.minY + 273.55), controlPoint1: CGPoint(x: frame.minX + 236.42, y: frame.minY + 272.65), controlPoint2: CGPoint(x: frame.minX + 237.81, y: frame.minY + 273.26))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 257.32, y: frame.minY + 276.39), controlPoint1: CGPoint(x: frame.minX + 244.86, y: frame.minY + 275.39), controlPoint2: CGPoint(x: frame.minX + 250.97, y: frame.minY + 276.39))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 316.85, y: frame.minY + 216.87), controlPoint1: CGPoint(x: frame.minX + 290.2, y: frame.minY + 276.39), controlPoint2: CGPoint(x: frame.minX + 316.85, y: frame.minY + 249.74))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 316.82, y: frame.minY + 215.74), controlPoint1: CGPoint(x: frame.minX + 316.85, y: frame.minY + 216.49), controlPoint2: CGPoint(x: frame.minX + 316.83, y: frame.minY + 216.12))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 322.77, y: frame.minY + 211.99), controlPoint1: CGPoint(x: frame.minX + 319.45, y: frame.minY + 214.08), controlPoint2: CGPoint(x: frame.minX + 322.58, y: frame.minY + 212.09))
    bezierPath.addCurve(to: CGPoint(x: frame.minX + 332.37, y: frame.minY + 180.76), controlPoint1: CGPoint(x: frame.minX + 333.73, y: frame.minY + 205.85), controlPoint2: CGPoint(x: frame.minX + 338.04, y: frame.minY + 192.11))
    bezierPath.close()
    fillColor.setFill()
    bezierPath.fill()


    //// Bezier 2 Drawing
    let bezier2Path = UIBezierPath()
    bezier2Path.move(to: CGPoint(x: frame.minX + 352.29, y: frame.minY + 128.81))
    bezier2Path.addLine(to: CGPoint(x: frame.minX + 352.29, y: frame.minY + 236.94))
    bezier2Path.addCurve(to: CGPoint(x: frame.minX + 288.22, y: frame.minY + 301), controlPoint1: CGPoint(x: frame.minX + 352.29, y: frame.minY + 272.26), controlPoint2: CGPoint(x: frame.minX + 323.54, y: frame.minY + 301))
    bezier2Path.addLine(to: CGPoint(x: frame.minX + 143.97, y: frame.minY + 301))
    bezier2Path.addLine(to: CGPoint(x: frame.minX + 143.97, y: frame.minY + 192.88))
    bezier2Path.addCurve(to: CGPoint(x: frame.minX + 208.05, y: frame.minY + 128.81), controlPoint1: CGPoint(x: frame.minX + 143.98, y: frame.minY + 157.55), controlPoint2: CGPoint(x: frame.minX + 172.72, y: frame.minY + 128.81))
    bezier2Path.addLine(to: CGPoint(x: frame.minX + 352.29, y: frame.minY + 128.81))
    bezier2Path.close()
    bezier2Path.move(to: CGPoint(x: frame.minX + 129.01, y: frame.minY + 191.33))
    bezier2Path.addLine(to: CGPoint(x: frame.minX + 129.01, y: frame.minY + 315.99))
    bezier2Path.addLine(to: CGPoint(x: frame.minX + 290.58, y: frame.minY + 315.99))
    bezier2Path.addCurve(to: CGPoint(x: frame.minX + 367.26, y: frame.minY + 238.49), controlPoint1: CGPoint(x: frame.minX + 332.86, y: frame.minY + 315.99), controlPoint2: CGPoint(x: frame.minX + 367.26, y: frame.minY + 281.22))
    bezier2Path.addLine(to: CGPoint(x: frame.minX + 367.26, y: frame.minY + 113.83))
    bezier2Path.addLine(to: CGPoint(x: frame.minX + 205.68, y: frame.minY + 113.83))
    bezier2Path.addCurve(to: CGPoint(x: frame.minX + 129.01, y: frame.minY + 191.33), controlPoint1: CGPoint(x: frame.minX + 163.41, y: frame.minY + 113.83), controlPoint2: CGPoint(x: frame.minX + 129.02, y: frame.minY + 148.6))
    bezier2Path.close()
    fillColor.setFill()
    bezier2Path.fill()


    context.endTransparencyLayer()
    context.restoreGState()


    //// Bezier 3 Drawing
    let bezier3Path = UIBezierPath()
    bezier3Path.move(to: CGPoint(x: frame.minX + 319.98, y: frame.minY + 201.25))
    bezier3Path.addCurve(to: CGPoint(x: frame.minX + 315.44, y: frame.minY + 204.02), controlPoint1: CGPoint(x: frame.minX + 319.85, y: frame.minY + 201.36), controlPoint2: CGPoint(x: frame.minX + 317.35, y: frame.minY + 202.87))
    bezier3Path.addCurve(to: CGPoint(x: frame.minX + 304.48, y: frame.minY + 180.56), controlPoint1: CGPoint(x: frame.minX + 313.53, y: frame.minY + 195.33), controlPoint2: CGPoint(x: frame.minX + 309.73, y: frame.minY + 187.36))
    bezier3Path.addCurve(to: CGPoint(x: frame.minX + 307.83, y: frame.minY + 178.87), controlPoint1: CGPoint(x: frame.minX + 306.06, y: frame.minY + 179.74), controlPoint2: CGPoint(x: frame.minX + 307.69, y: frame.minY + 178.91))
    bezier3Path.addCurve(to: CGPoint(x: frame.minX + 323.01, y: frame.minY + 185.61), controlPoint1: CGPoint(x: frame.minX + 313.73, y: frame.minY + 177.24), controlPoint2: CGPoint(x: frame.minX + 320.15, y: frame.minY + 179.93))
    bezier3Path.addCurve(to: CGPoint(x: frame.minX + 319.98, y: frame.minY + 201.25), controlPoint1: CGPoint(x: frame.minX + 325.71, y: frame.minY + 191.03), controlPoint2: CGPoint(x: frame.minX + 324.3, y: frame.minY + 197.42))
    bezier3Path.close()
    fillColor2.setFill()
    bezier3Path.fill()


    //// Bezier 4 Drawing
    let bezier4Path = UIBezierPath()
    bezier4Path.move(to: CGPoint(x: frame.minX + 223.93, y: frame.minY + 202.61))
    bezier4Path.addCurve(to: CGPoint(x: frame.minX + 253.02, y: frame.minY + 252.78), controlPoint1: CGPoint(x: frame.minX + 218.34, y: frame.minY + 227.69), controlPoint2: CGPoint(x: frame.minX + 232.45, y: frame.minY + 248.93))
    bezier4Path.addCurve(to: CGPoint(x: frame.minX + 223.93, y: frame.minY + 202.61), controlPoint1: CGPoint(x: frame.minX + 258.75, y: frame.minY + 236.32), controlPoint2: CGPoint(x: frame.minX + 253.13, y: frame.minY + 210.93))
    bezier4Path.close()
    fillColor.setFill()
    bezier4Path.fill()


    //// Bezier 5 Drawing
    let bezier5Path = UIBezierPath()
    bezier5Path.move(to: CGPoint(x: frame.minX + 287.08, y: frame.minY + 222.2))
    bezier5Path.addCurve(to: CGPoint(x: frame.minX + 260.13, y: frame.minY + 252.78), controlPoint1: CGPoint(x: frame.minX + 287.48, y: frame.minY + 240.26), controlPoint2: CGPoint(x: frame.minX + 274.83, y: frame.minY + 252.96))
    bezier5Path.addCurve(to: CGPoint(x: frame.minX + 287.08, y: frame.minY + 222.2), controlPoint1: CGPoint(x: frame.minX + 258.44, y: frame.minY + 240.65), controlPoint2: CGPoint(x: frame.minX + 265.81, y: frame.minY + 223.92))
    bezier5Path.close()
    fillColor.setFill()
    bezier5Path.fill()




    //// Oval Drawing
    let ovalRect = CGRect(x: frame.minX, y: frame.minY, width: 502, height: 480)
    let ovalPath = UIBezierPath()
    ovalPath.addArc(withCenter: CGPoint.zero, radius: ovalRect.width / 2, startAngle: -startAngle * CGFloat.pi/180, endAngle: -endAngle * CGFloat.pi/180, clockwise: true)
    ovalPath.addLine(to: CGPoint.zero)
    ovalPath.close()

    var ovalTransform = CGAffineTransform(translationX: ovalRect.midX, y: ovalRect.midY)
    ovalTransform = ovalTransform.scaledBy(x: 1, y: ovalRect.height / ovalRect.width)
    ovalPath.apply(ovalTransform)

    UIColor.gray.setFill()
    ovalPath.fill()
}

Solution

  • You are like 50% complete. :) Now you need to create a class to hold your circular view and manipulate the end angle.

    But before that, in PaintCode, hook up an angle type variable to the circle End Angle property. Like so:

    PaintCode

    Notice: in my example I'm using an image as the circle's Fill. Not sure if that is the approach you envisioned, but there you go. If you are going to follow this path, make sure the image you used in PaintCode is also in Xcode under Assets.xcassets (using the same name referenced in the StyleKit class).

    Now back to that class. This could be a regular UIView, with a reference to the angle type property; as well as functions to increment it, start and stop the animation:

    @IBDesignable class CircleView: UIView {
    
        // MARK: - Properties
    
        var angle: CGFloat = 0 {
            didSet {
                setNeedsDisplay()
            }
        }
    
        // MARK: - Private Properties
    
        private var animationTimer = Timer()
    
        // MARK: - Lifecycle
    
        override func draw(_ rect: CGRect) {
            StyleKit.drawCircleShape(frame: rect, resizing: .aspectFill, angle: angle)
        }
    }
    
    // MARK: - Interface
    
    extension CircleView {
    
        func startAnimating() {
            animationTimer = Timer.scheduledTimer(
                timeInterval: 0.01,
                target: self,
                selector: #selector(updateAngle),
                userInfo: nil,
                repeats: true
            )
            animationTimer.fire()
        }
    
        func stopAnimating() {
            animationTimer.invalidate()
        }
    
        @objc func updateAngle() {
            if angle == 360 { angle = 0 }
            angle += 1
        }
    }
    

    This of course is just an example to give you an idea. Your implementation may vary.

    Finally, you could use a ViewController to start / stop the animation as in:

    class CircleViewController: UIViewController {
    
        // MARK: - Private Properties
    
        @IBOutlet private weak var circleView: CircleView! {
            didSet {
                circleView.startAnimating()
            }
        }
    
        // MARK: - Lifecycle
    
        override func viewDidDisappear(_ animated: Bool) {
            circleView.stopAnimating()
        }
    }
    

    Final result:

    Example

    That's it. You can find the project here (it's the Fifth test):
    https://github.com/backslash-f/paintcode-tests

    Ah, another thing to note: as soon as I started using an image to fill my circular shape, Interface Builder gave me some weird errors: IB Designables: Failed to render and update auto layout status for... That's something to investigate. Not sure if it's reproducible tho.

    Cheers!