【问题标题】:Draw radial background on a UIBezierPath在 UIBezierPath 上绘制径向背景
【发布时间】:2026-02-14 09:10:01
【问题描述】:

上图显示了以下代码是如何运行的:

extension Int {
    var degreesToRadians: Double { return Double(self) * .pi / 180 }
    var radiansToDegrees: Double { return Double(self) * 180 / .pi }
}
extension FloatingPoint {
    var degreesToRadians: Self { return self * .pi / 180 }
    var radiansToDegrees: Self { return self * 180 / .pi }
}


class SunBurstView: UIView {
    override func draw(_ rect: CGRect) {
        let radius: CGFloat = rect.size.width / 2
        UIColor.yellow.setFill()
        let bezierPath = UIBezierPath()
        let centerPoint = CGPoint(x: rect.origin.x + radius, y: rect.size.height / 2)
        var thisPoint = CGPoint(x: centerPoint.x + radius, y: centerPoint.y + radius)
        bezierPath.move(to: centerPoint)
        var thisAngle: CGFloat = 0.0
        let sliceDegrees: CGFloat = 360.0 / 20 / 2.0
        for _ in 0..<20 {
            let x = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
            let y = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
            thisPoint = CGPoint(x: x, y: y)
            bezierPath.addLine(to: thisPoint)
            thisAngle += sliceDegrees
            let x2 = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
            let y2 = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
            thisPoint = CGPoint(x: x2, y: y2)
            bezierPath.addLine(to: thisPoint)
            bezierPath.addLine(to: centerPoint)
            thisAngle += sliceDegrees
        }
        bezierPath.close()
        bezierPath.lineWidth = 0

        let colors = [UIColor.green.cgColor, UIColor.blue.cgColor] as CFArray
        let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
        let endPosition = min(frame.width, frame.height) / 2
        let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
        UIGraphicsGetCurrentContext()?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endPosition, options: .drawsAfterEndLocation)

        bezierPath.fill()
        bezierPath.stroke()
    }
}

我希望径向背景只填充 UIBezierPath。通常这可以通过遮罩层来完成,但渐变没有属性遮罩。感谢您对如何仅在 UIBezierPath 上绘制渐变径向背景的任何帮助!在没有 UIBezierPath 的 UIView 上应该是透明的。

带有线性渐变的完整代码(复制粘贴即可):

class SunBurstView: UIView {
    override func draw(_ rect: CGRect) {
        let radius: CGFloat = rect.size.width / 2
        UIColor.yellow.setFill()
        let bezierPath = UIBezierPath()
        let centerPoint = CGPoint(x: rect.origin.x + radius, y: rect.size.height / 2)
        var thisPoint = CGPoint(x: centerPoint.x + radius, y: centerPoint.y + radius)
        bezierPath.move(to: centerPoint)
        var thisAngle: CGFloat = 0.0
        let sliceDegrees: CGFloat = 360.0 / 20 / 2.0
        for _ in 0..<20 {
            let x = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
            let y = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
            thisPoint = CGPoint(x: x, y: y)
            bezierPath.addLine(to: thisPoint)
            thisAngle += sliceDegrees
            let x2 = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
            let y2 = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
            thisPoint = CGPoint(x: x2, y: y2)
            bezierPath.addLine(to: thisPoint)
            bezierPath.addLine(to: centerPoint)
            thisAngle += sliceDegrees
        }
        bezierPath.close()
//        let colors = [UIColor.green.cgColor, UIColor.blue.cgColor] as CFArray
//        let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
//        let endPosition = min(frame.width, frame.height) / 2
//        let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
//        UIGraphicsGetCurrentContext()?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endPosition, options: .drawsAfterEndLocation)


        //linear
        let shape = CAShapeLayer()
        shape.path = bezierPath.cgPath
        shape.lineWidth = 0.0
        shape.strokeColor = UIColor.black.cgColor
        self.layer.addSublayer(shape)

        let gradient = CAGradientLayer()
        gradient.frame = bezierPath.bounds
        gradient.colors = [UIColor.blue.cgColor, UIColor.green.cgColor]

        let shapeMask = CAShapeLayer()
        shapeMask.path = bezierPath.cgPath
        gradient.mask = shapeMask

        self.layer.addSublayer(gradient)


        bezierPath.lineWidth = 0
        bezierPath.fill()
        bezierPath.stroke()
    }
}

【问题讨论】:

  • 一种方法:创建一个渐变层,然后使用Path作为图层蒙版。那里有很多示例...只需搜索UIBezierPath gradient fill,您应该就可以了。
  • @DonMag 是的,我知道遮罩层,我在我的问题中说过。但是渐变没有属性掩码。而在制作新的渐变层时,如何制作圆形渐变呢?
  • 你能用线性渐变填充你的 SunBurst 路径吗?
  • @DonMag 是的,带有遮罩层。但是圆形渐变有点难……
  • 好的 - 用你用来用线性渐变填充贝塞尔路径的代码更新你的问题......然后我可以告诉你如何将线性渐变更改为径向。

标签: ios swift uiview


【解决方案1】:

这是径向渐变层的一种实现:

class RadialGradientLayer: CALayer {

    var center: CGPoint {
        return CGPoint(x: bounds.width/2, y: bounds.height/2)
    }

    var radius: CGFloat {
        return min(bounds.width / 2.0, bounds.height / 2.0)
    }

    var colors: [UIColor] = [UIColor.black, UIColor.lightGray] {
        didSet {
            setNeedsDisplay()
        }
    }

    var cgColors: [CGColor] {
        return colors.map({ (color) -> CGColor in
            return color.cgColor
        })
    }

    override init() {
        super.init()
        needsDisplayOnBoundsChange = true
    }

    required init(coder aDecoder: NSCoder) {
        super.init()
    }

    override func draw(in ctx: CGContext) {
        ctx.saveGState()
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        guard let gradient = CGGradient(colorsSpace: colorSpace, colors: cgColors as CFArray, locations: nil) else {
            return
        }
        ctx.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: radius, options: CGGradientDrawingOptions(rawValue: 0))
    }

}

因此,在您的最新代码中,替换:

    let gradient = CAGradientLayer()
    gradient.frame = bezierPath.bounds
    gradient.colors = [UIColor.blue.cgColor, UIColor.green.cgColor]

与:

    let gradient = RadialGradientLayer()
    gradient.frame = bezierPath.bounds
    gradient.colors = [UIColor.blue, UIColor.green]

导致:

【讨论】:

    【解决方案2】:

    以下是您可以通过最少的更改使您的原始代码工作的方法。

    override func draw(_ rect: CGRect) {
        // [...]
        // your code untouched until this line:
        bezierPath.close()
    
        // change the rest of the function to:
        bezierPath.fill()
    
        UIGraphicsGetCurrentContext()!.setBlendMode(.sourceIn)
        let colors = [UIColor.green.cgColor, UIColor.blue.cgColor] as CFArray
        let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
        let endPosition = min(frame.width, frame.height) / 2
        let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
        UIGraphicsGetCurrentContext()?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endPosition, options: .drawsAfterEndLocation)
    }
    

    结果:

    这个想法是首先用光线绘制贝塞尔曲线。这里的颜色无所谓,这只是为了绘制alpha。

    然后我们使用 Quartz 的一种特殊混合模式在 alpha 顶部绘制渐变:kCGBlendModeSourceIn(请参阅Porter-Duff alpha compositing)。

    此模式在现有 Alpha 之上绘制任何内容,仅替换像素的颜色,保持 Alpha 不变。这基本上就像使用当前绘图的 alpha 作为蒙版。

    【讨论】: