Search code examples
iosswiftuibuttoncalayer

Creating a thin black circle (unfilled) within a filled white circle (UIButton)


I'm trying to replicate the default camera button on iOS devices:

https://www.iphonefaq.org/images/archives/photo-tip-shutter1.jpg

I'm able to create a white circular button with black button within it. However, the black button is also filled, instead of just being a thin circle.

This is what I have (most of it has been copied from different sources and put together, so the code isn't efficient)

The object represents the button,

func applyRoundCorner(_ object: AnyObject) {
    //object.layer.shadowColor = UIColor.black.cgColor
    //object.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
    object.layer.cornerRadius = (object.frame.size.width)/2
    object.layer.borderColor = UIColor.black.cgColor
    object.layer.borderWidth = 5
    object.layer.masksToBounds = true
    //object.layer.shadowRadius = 1.0
    //object.layer.shadowOpacity = 0.5

    var CircleLayer   = CAShapeLayer()
    let center = CGPoint (x: object.frame.size.width / 2, y: object.frame.size.height / 2)
    let circleRadius = object.frame.size.width / 6
    let circlePath = UIBezierPath(arcCenter: center, radius: circleRadius, startAngle: CGFloat(M_PI), endAngle: CGFloat(M_PI * 2), clockwise: true)
    CircleLayer.path = circlePath.cgPath
    CircleLayer.strokeColor = UIColor.black.cgColor
    //CircleLayer.fillColor = UIColor.blue.cgColor
    CircleLayer.lineWidth = 1
    CircleLayer.strokeStart = 0
    CircleLayer.strokeEnd  = 1
    object.layer.addSublayer(CircleLayer)
}

Solution

  • Basic Approach

    You could do it like this (for the purpose of demonstration, I would do the button programmatically, using a playground):

    let buttonWidth = 100.0
    
    let button = UIButton(frame: CGRect(x: 0, y: 0, width: buttonWidth, height: buttonWidth))
    button.backgroundColor = .white
    button.layer.cornerRadius = button.frame.width / 2
    

    Drawing Part:

    So, after adding the button and do the desired setup (make it circular), here is part of how you could draw a circle in it:

    let circlePath = UIBezierPath(arcCenter: CGPoint(x: buttonWidth / 2,y: buttonWidth / 2), radius: 40.0, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
    
    let circleLayer = CAShapeLayer()
    circleLayer.path = circlePath.cgPath
    circleLayer.fillColor = UIColor.clear.cgColor
    circleLayer.strokeColor = UIColor.black.cgColor
    circleLayer.lineWidth = 2.5
    
    // adding the layer into the button:
    button.layer.addSublayer(circleLayer)
    

    Probably, circleLayer.fillColor = UIColor.clear.cgColor is the part you missing 🙂.

    Therefore:

    enter image description here


    Back to your case:

    Aside Bar Tip:

    For implementing applyRoundCorner, I would suggest to let it has only the job for rounding the view, and then create another function to add the circle inside the view. And that's for avoiding any naming conflict, which means that when reading "applyRoundCorner" I would not assume that it is also would add circle to my view! So:

    func applyRoundedCorners(for view: UIView) {
        view.layer.cornerRadius = view.frame.size.width / 2
        view.layer.borderColor = UIColor.white.cgColor
        view.layer.borderWidth = 5.0
        view.layer.masksToBounds = true
    }
    
    func drawCircle(in view: UIView) {
        let circlePath = UIBezierPath(arcCenter: CGPoint(x: view.frame.size.width / 2,y: view.frame.size.width / 2),
                                                 radius: view.frame.size.width / 2.5,
                                                 startAngle: 0,
                                                 endAngle: CGFloat.pi * 2,
                                                 clockwise: true)
    
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = circlePath.cgPath
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.black.cgColor
        shapeLayer.lineWidth = 2.5
    
        button.layer.addSublayer(shapeLayer)
    }
    

    and now:

    applyRoundedCorners(for: button)
    drawCircle(in: button)
    

    That's seems to be better. From another aspect, consider that you want to make a view to be circular without add a circle in it, with separated methods you could simply applyRoundedCorners(for: myView) without the necessary of adding a circle in it.


    Furthermore:

    As you can see, I changed AnyObject to UIView, it seems to be more logical to your case. So here is a cool thing that we could do:

    extension UIView {
        func applyRoundedCorners(for view: UIView) {
            view.layer.cornerRadius = view.frame.size.width / 2
            view.layer.borderColor = UIColor.white.cgColor
            view.layer.borderWidth = 5.0
            view.layer.masksToBounds = true
        }
    
        func drawCircle(in view: UIView) {
            let circlePath = UIBezierPath(arcCenter: CGPoint(x: view.frame.size.width / 2,y: view.frame.size.width / 2),
                                          radius: view.frame.size.width / 2.5,
                                          startAngle: 0,
                                          endAngle: CGFloat.pi * 2,
                                          clockwise: true)
    
            let shapeLayer = CAShapeLayer()
            shapeLayer.path = circlePath.cgPath
            shapeLayer.fillColor = UIColor.clear.cgColor
            shapeLayer.strokeColor = UIColor.black.cgColor
            shapeLayer.lineWidth = 2.5
    
            button.layer.addSublayer(shapeLayer)
        }
    }
    

    Now both applyRoundedCorners and drawCircle are implicitly included to the UIView (which means UIButton), instead of passing the button to these functions, you would be able to:

    button.applyRoundedCorners()
    button.drawCircle()