Search code examples
iosswiftuibezierpathcashapelayer

iOS (Swift) Annuls-shaped UIBezierPath for CAShapeLayer


I'm trying to make an annulus-shaped UIBezierPath to use as the path of a CAShapeLayer

The following produces a circular path:

let radius = 100.0
let circularPath = UIBezierPath(arcCenter: .zero, radius: radius, startAngle: 0.0, endAngle: 2.0 * .pi, clockwise: true)

let layer = CAShapeLayer()
layer.path = circularPath.cgPath

However, I want an annuls-shaped UIBezierPath that fills between radius and say outerRadius = radius + 10.


Solution

  • If this is what you're going for ("Annulus" shape):

    enter image description here

    You can achieve it by creating an Oval path and appending a smaller Oval path.

    You can run this directly in a Playground page to get that result:

    import PlaygroundSupport
    import UIKit
    
    class AnnulusView: UIView {
    
        private var annulusLayer: CAShapeLayer!
        private var annulusWidth: CGFloat = 10.0
        private var fillColor: UIColor = .red
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            if annulusLayer == nil {
                annulusLayer = CAShapeLayer()
                layer.addSublayer(annulusLayer)
            }
    
            let r = bounds
            let outerPath = UIBezierPath(ovalIn: r)
            let innerPath = UIBezierPath(ovalIn: r.insetBy(dx: annulusWidth, dy:annulusWidth))
            outerPath.append(innerPath)
            outerPath.usesEvenOddFillRule = true
    
            annulusLayer.path = outerPath.cgPath
            annulusLayer.fillRule = kCAFillRuleEvenOdd
            annulusLayer.fillColor = fillColor.cgColor
    
            // if you want a border
    //      annulusLayer.lineWidth = 1
    //      annulusLayer.strokeColor = UIColor.black.cgColor
        }
    
    }
    
    class TestingViewController: UIViewController {
    
        override public var preferredContentSize: CGSize {
            get { return CGSize(width: 400, height: 400) }
            set { super.preferredContentSize = newValue }
        }
    
        var theAnnulusView: AnnulusView = {
            let v = AnnulusView()
            v.translatesAutoresizingMaskIntoConstraints = false
            return v
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.backgroundColor = .white
    
            view.addSubview(theAnnulusView)
    
            // make the Annulus view 100x100 centered in this view
            NSLayoutConstraint.activate([
                theAnnulusView.widthAnchor.constraint(equalToConstant: 100),
                theAnnulusView.heightAnchor.constraint(equalToConstant: 100),
                theAnnulusView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                theAnnulusView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                ])
    
        }
    
    }
    
    let viewController = TestingViewController()
    
    PlaygroundPage.current.liveView = viewController