iosswiftuibezierpathcgaffinetransform

UIBezierPath Rotation around a UIView's center


I'm creating a custom UIView, in which I implement its draw(rect:) method by drawing a circle with a large width using UIBezierPath, that draw a square on the top (as shown in the picture, don't consider the colors or the size). Then I try creating rotated copies of the square, to match a "settings" icon (picture 2, consider only the outer ring). To do that last thing, I need to rotate the square using a CGAffineTransform(rotationAngle:) but the problem is that this rotation's center is the origin of the frame, and not the center of the circle. How can I create a rotation around a certain point in my view?

picture 1

picture 2


Solution

  • As a demonstration of @DuncanC's answer (up voted), here is the drawing of a gear using CGAffineTransforms to rotate the gear tooth around the center of the circle:

    class Gear: UIView {
        var lineWidth: CGFloat = 16
        let boxWidth: CGFloat = 20
        let toothAngle: CGFloat = 45
    
        override func draw(_ rect: CGRect) {
    
            let radius = (min(bounds.width, bounds.height) - lineWidth) / 4.0
    
            var path = UIBezierPath()
            path.lineWidth = lineWidth
            UIColor.white.set()
    
            // Use the center of the bounds not the center of the frame to ensure
            // this draws correctly no matter the location of the view
            // (thanks @dulgan for pointing this out)
            let center = CGPoint(x: bounds.maxX / 2, y: bounds.maxY / 2)
    
            // Draw circle
            path.move(to: CGPoint(x: center.x + radius, y: center.y))
            path.addArc(withCenter: CGPoint(x: center.x, y: center.y), radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
            path.stroke()
    
            // Box for gear tooth
            path = UIBezierPath()
            let point = CGPoint(x: center.x - boxWidth / 2.0, y: center.y - radius)
            path.move(to: point)
            path.addLine(to: CGPoint(x: point.x, y: point.y - boxWidth))
            path.addLine(to: CGPoint(x: point.x + boxWidth, y: point.y - boxWidth))
            path.addLine(to: CGPoint(x: point.x + boxWidth, y: point.y))
            path.close()
            UIColor.red.set()
    
            // Draw a tooth every toothAngle degrees
            for _ in stride(from: toothAngle, through: 360, by: toothAngle) {
                // Move origin to center of the circle
                path.apply(CGAffineTransform(translationX: -center.x, y: -center.y))
    
                // Rotate
                path.apply(CGAffineTransform(rotationAngle: toothAngle * .pi / 180))
    
                // Move origin back to original location
                path.apply(CGAffineTransform(translationX: center.x, y: center.y))
    
                // Draw the tooth
                path.fill()
            }
        }
    }
    
    let view = Gear(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
    

    Here it is running in a Playground:

    Demo of gear running in a Playground