在填充的白色圆圈 (UIButton) 内创建一个黑色细圆圈(未填充)

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

我正在尝试复制 iOS 设备上的默认相机按钮:

我可以创建一个带有黑色按钮的白色圆形按钮。但是,黑色按钮也被填充了,而不仅仅是一个细圆圈。

这是我的(大部分是从不同来源复制并放在一起的,所以代码效率不高)

对象代表按钮,

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)
}

只需要添加宽度和高度较小的圆形图层即可

试试这个代码

func applyRoundCorner(_ object: UIButton) {
    object.layer.cornerRadius = (object.frame.size.width)/2
    object.layer.borderColor = UIColor.black.cgColor
    object.layer.borderWidth = 5
    object.layer.masksToBounds = true

    let anotherFrame = CGRect(x: 12, y: 12, width: object.bounds.width - 24, height: object.bounds.height - 24)
    let circle = CAShapeLayer()
    let path = UIBezierPath(arcCenter: object.center, radius: anotherFrame.width / 2, startAngle: 0, endAngle: .pi * 2, clockwise: true)
    circle.path = path.cgPath
    circle.strokeColor = UIColor.black.cgColor
    circle.lineWidth = 1.0
    circle.fillColor = UIColor.clear.cgColor
    object.layer.addSublayer(circle)
}

注意:根据您的要求和最佳用户体验更改帧值

输出

基本方法

您可以这样做(出于演示目的,我会使用 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

绘图部分:

因此,在添加按钮并进行所需的设置(使其成为圆形)之后,这里是您可以在其中绘制圆圈的部分方法:

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)

可能,circleLayer.fillColor = UIColor.clear.cgColor是你遗漏的部分。

因此:


回到你的案例:

旁栏提示:

为了实现applyRoundCorner,我建议让它有只有圆视图的工作,然后创建另一个函数在视图内添加圆.这是为了避免任何命名冲突,这意味着在阅读 "applyRoundCorner" 时我会 而不是 假设它也会在我的视图中添加圆圈!所以:

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)
}

现在:

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

这似乎更好。从另一个方面来说,考虑到您希望将视图设为圆形 而无需在其中添加 圆圈,使用分离方法您可以简单地 applyRoundedCorners(for: myView) 而无需添加圆圈在里面。


此外:

如您所见,我将AnyObject更改为UIView,这似乎更符合您的情况。所以我们可以做一件很酷的事情:

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)
    }
}

现在 applyRoundedCornersdrawCircle 都隐式包含在 UIView 中(这意味着 UIButton),而不是将 button 传递给这些函数,您将能够:

button.applyRoundedCorners()
button.drawCircle()

我相信有一百万种不同的方法可以解决这个问题,这只是一个...

为了简单和速度,我从 UIButton 开始,我可能会考虑实际从 UIImage 开始并简单地设置按钮的图像属性,但这在很大程度上取决于我我正在努力实现

internal extension FloatingPoint {
    var degreesToRadians: Self { return self * .pi / 180 }
    var radiansToDegrees: Self { return self * 180 / .pi }
}

class RoundButton: UIButton {
    override func draw(_ rect: CGRect) {
        makeButtonImage()?.draw(at: CGPoint(x: 0, y: 0))
    }

    func makeButtonImage() -> UIImage? {
        let size = bounds.size

        UIGraphicsBeginImageContext(CGSize(width: size.width, height: size.height))
        defer {
            UIGraphicsEndImageContext()
        }
        guard let ctx = UIGraphicsGetCurrentContext() else {
            return nil
        }

        let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
        // Want to "over fill" the image area, so the mask can be applied
        // to the entire image
        let radius = min(size.width / 2.0, size.height / 2.0)

        let innerRadius = radius * 0.75
        let innerCircle = UIBezierPath(arcCenter: center,
                                                                     radius: innerRadius,
                                                                     startAngle: CGFloat(0.0).degreesToRadians,
                                                                     endAngle: CGFloat(360.0).degreesToRadians,
                                                                     clockwise: true)
        // The color doesn't matter, only it's alpha level
        UIColor.red.setStroke()
        innerCircle.lineWidth = 4.0
        innerCircle.stroke(with: .normal, alpha: 1.0)

        let circle = UIBezierPath(arcCenter: center,
                                                            radius: radius,
                                                            startAngle: CGFloat(0.0).degreesToRadians,
                                                            endAngle: CGFloat(360.0).degreesToRadians,
                                                            clockwise: true)
        UIColor.clear.setFill()
        ctx.fill(bounds)

        UIColor.white.setFill()
        circle.fill(with: .sourceOut, alpha: 1.0)

        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

nb:这是未优化的!我会考虑缓存 makeButtonImage 的结果并在按钮的 state/size 更改时使它无效,只是要注意

为什么这种方法是 "better" 而不是其他方法?我只想说,不是,而是它创造的,是一个"cut out"的内圈

这对我来说有点挑剔,但我认为它看起来更好,而且是一个更灵活的解决方案,因为您没有 "need" 内圆描边颜色,等等,等等,等等

该解决方案利用了 CoreGraphics CGBlendModes

当然我可能会在 PaintCodeApp 中完成所有事情并完成它