Ios 带CAShapelayer的arcs甜甜圈图表-底层边界可见

Ios 带CAShapelayer的arcs甜甜圈图表-底层边界可见,ios,calayer,cashapelayer,Ios,Calayer,Cashapelayer,我画了一个带圆弧的油炸圈饼图。我把一个放在另一个上面画,问题是下面的层边缘是可见的 图纸代码如下 for (index, item) in values.enumerated() { var currentValue = previousValue + item.value previousValue = currentValue if index == values.count - 1 {

我画了一个带圆弧的油炸圈饼图。我把一个放在另一个上面画,问题是下面的层边缘是可见的

图纸代码如下

for (index, item) in values.enumerated() {
            var currentValue = previousValue + item.value
            previousValue = currentValue
            if index == values.count - 1 {
                currentValue = 100
            }

            let layer = CAShapeLayer()
            let path = UIBezierPath()

            let separatorLayer = CAShapeLayer()
            let separatorPath = UIBezierPath()

            let radius: CGFloat = self.frame.width / 2 - lineWidth / 2
            let center: CGPoint = CGPoint(x: self.bounds.width / 2, y: self.bounds.width / 2)

            separatorPath.addArc(withCenter: center, radius: radius, startAngle: percentToRadians(percent: -25), endAngle: percentToRadians(percent: CGFloat(currentValue - 25 + 0.2)), clockwise: true)
            separatorLayer.path = separatorPath.cgPath
            separatorLayer.fillColor = UIColor.clear.cgColor
            separatorLayer.strokeColor = UIColor.white.cgColor
            separatorLayer.lineWidth = lineWidth
            separatorLayer.contentsScale = UIScreen.main.scale
            self.layer.addSublayer(separatorLayer)
            separatorLayer.add(createGraphAnimation(), forKey: nil)
            separatorLayer.zPosition = -(CGFloat)(index)

            path.addArc(withCenter: center, radius: radius, startAngle: percentToRadians(percent: -25), endAngle: percentToRadians(percent: CGFloat(currentValue - 25)), clockwise: true)
            layer.path = path.cgPath
            layer.fillColor = UIColor.clear.cgColor
            layer.strokeColor = item.color.cgColor
            layer.lineWidth = lineWidth
            layer.contentsScale = UIScreen.main.scale
            layer.shouldRasterize = true
            layer.rasterizationScale = UIScreen.main.scale
            layer.allowsEdgeAntialiasing = true
            separatorLayer.addSublayer(layer)
            layer.add(createGraphAnimation(), forKey: nil)
            layer.zPosition = -(CGFloat)(index)
我做错了什么

UPD

试码

let mask = CAShapeLayer()
        mask.frame = CGRect(x: 0, y: 0, width: radius * 2, height: radius * 2)
        mask.fillColor = nil
        mask.strokeColor = UIColor.white.cgColor
        mask.lineWidth = lineWidth * 2
        let maskPath = CGMutablePath()
        maskPath.addArc(center: CGPoint(x: self.radius, y: self.radius), radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        maskPath.closeSubpath()
        mask.path = maskPath
        self.layer.mask = mask

但它只遮罩了内部边缘,外部仍然有边缘

对我来说,边缘看起来是抗锯齿的,从而产生了一些透明的像素。然后可以通过叠加的“模糊”边缘看到背景的橙色。 您是否尝试过使覆盖层不透明

layer.Opaque = true; //C#

另一种方法是在橙色边缘的顶部绘制一个背景色的薄圆。这应该是可行的,但它不是最漂亮的方法。

对我来说,它看起来像是边缘经过抗锯齿处理,产生了一些透明的像素。然后可以通过叠加的“模糊”边缘看到背景的橙色。 您是否尝试过使覆盖层不透明

layer.Opaque = true; //C#

另一种方法是在橙色边缘的顶部绘制一个背景色的薄圆。这应该行得通,但这不是最漂亮的方法。

你看到的条纹是因为你在同一位置绘制了两次完全相同的形状,而alpha合成(通常实现的)并不是为了处理这个问题而设计的,介绍了alpha合成,讨论了问题:

我们必须记住,我们关于 按几何对象分割亚像素区域 在有相关蒙版的输入图片面前向下。 当一张图片在合成表达式中出现两次时, 我们必须注意F和A的计算 F B.表中列出的仅适用于不相关的情况 图片

当它说“哑光”时,它基本上是指透明度。当它说“不相关图片”时,它指的是两张透明区域没有特殊关系的图片。但是在你的例子中,你的两张图片确实有一种特殊的关系:图片在完全相同的区域是透明的

下面是一个完整的测试,它再现了您的问题:

private func badVersion() {
    let center = CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2)
    let radius: CGFloat = 100
    let ringWidth: CGFloat = 44

    let ring = CAShapeLayer()
    ring.frame = view.bounds
    ring.fillColor = nil
    ring.strokeColor = UIColor.red.cgColor
    ring.lineWidth = ringWidth
    let ringPath = CGMutablePath()
    ringPath.addArc(center: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
    ringPath.closeSubpath()
    ring.path = ringPath
    view.layer.addSublayer(ring)

    let wedge = CAShapeLayer()
    wedge.frame = view.bounds
    wedge.fillColor = nil
    wedge.strokeColor = UIColor.darkGray.cgColor
    wedge.lineWidth = ringWidth
    wedge.lineCap = kCALineCapButt
    let wedgePath = CGMutablePath()
    wedgePath.addArc(center: center, radius: radius, startAngle: 0.1, endAngle: 0.6, clockwise: false)
    wedge.path = wedgePath
    view.layer.addSublayer(wedge)
}
以下是显示问题的屏幕部分:

解决此问题的一种方法是在环的边缘之外绘制颜色,并使用遮罩将其剪裁到环形状

我将更改我的代码,这样我就不用在上面画一个红色的环和一部分灰色的环,而是画一个红色的圆盘,在上面画一个灰色的楔子:

如果放大,可以看到这仍然显示灰色楔体边缘的红色条纹。所以诀窍是使用一个环形面具来获得最终的形状。以下是遮罩的形状,在前一张图像的顶部以白色绘制:

请注意,遮罩远离带有条纹的问题区域。当我将遮罩用作遮罩而不是绘制它时,我得到了最终的完美结果:

下面是绘制完美版本的代码:

private func goodVersion() {
    let center = CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2)
    let radius: CGFloat = 100
    let ringWidth: CGFloat = 44
    let slop: CGFloat = 10

    let disc = CAShapeLayer()
    disc.frame = view.bounds
    disc.fillColor = UIColor.red.cgColor
    disc.strokeColor = nil
    let ringPath = CGMutablePath()
    ringPath.addArc(center: center, radius: radius + ringWidth / 2 + slop, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
    ringPath.closeSubpath()
    disc.path = ringPath
    view.layer.addSublayer(disc)

    let wedge = CAShapeLayer()
    wedge.frame = view.bounds
    wedge.fillColor = UIColor.darkGray.cgColor
    wedge.strokeColor = nil
    let wedgePath = CGMutablePath()
    wedgePath.move(to: center)
    wedgePath.addArc(center: center, radius: radius + ringWidth / 2 + slop, startAngle: 0.1, endAngle: 0.6, clockwise: false)
    wedgePath.closeSubpath()
    wedge.path = wedgePath
    view.layer.addSublayer(wedge)

    let mask = CAShapeLayer()
    mask.frame = view.bounds
    mask.fillColor = nil
    mask.strokeColor = UIColor.white.cgColor
    mask.lineWidth = ringWidth
    let maskPath = CGMutablePath()
    maskPath.addArc(center: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
    maskPath.closeSubpath()
    mask.path = maskPath
    view.layer.mask = mask
}
请注意,遮罩适用于
视图
中的所有内容,因此(在您的情况下)可能需要将所有层移动到没有其他内容的子视图中,这样可以安全地遮罩

更新 看看你的操场,问题是(仍然)你正在画两个形状,它们的顶部有完全相同的部分透明边缘。你不能那样做。解决方案是将彩色形状画得更大,使它们在甜甜圈边缘完全不透明,然后使用层遮罩将它们剪裁到甜甜圈形状

我修好了你的操场。请注意,在我的版本中,每个彩色部分的
线宽
donutThickness+10
,而遮罩的
线宽
只是
donutThickness
。结果如下:

这里是操场:

import UIKit
import PlaygroundSupport

class ABDonutChart: UIView {

    struct Datum {
        var value: Double
        var color: UIColor
    }

    var donutThickness: CGFloat = 20 { didSet { setNeedsLayout() } }
    var separatorValue: Double = 1 { didSet { setNeedsLayout() } }
    var separatorColor: UIColor = .white { didSet { setNeedsLayout() } }
    var data = [Datum]() { didSet { setNeedsLayout() } }

    func withAnimation(_ wantAnimation: Bool, do body: () -> ()) {
        let priorFlag = wantAnimation
        self.wantAnimation = true
        defer { self.wantAnimation = priorFlag }
        body()
        layoutIfNeeded()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let bounds = self.bounds
        let center = CGPoint(x: bounds.origin.x + bounds.size.width / 2, y: bounds.origin.y + bounds.size.height / 2)
        let radius = (min(bounds.size.width, bounds.size.height) - donutThickness) / 2

        let maskLayer = layer.mask as? CAShapeLayer ?? CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.fillColor = nil
        maskLayer.strokeColor = UIColor.white.cgColor
        maskLayer.lineWidth = donutThickness
        maskLayer.path = CGPath(ellipseIn: CGRect(x: center.x - radius, y: center.y - radius, width: 2 * radius, height: 2 * radius), transform: nil)
        layer.mask = maskLayer

        var spareLayers = segmentLayers
        segmentLayers.removeAll()

        let finalSum = data.reduce(Double(0)) { $0 + $1.value + separatorValue }
        var runningSum: Double = 0

        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = 0.0
        animation.toValue = 1.0
        animation.duration = 2
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

        func addSegmentLayer(color: UIColor, segmentSum: Double) {
            let angleOffset: CGFloat = -0.25 * 2 * .pi

            let segmentLayer = spareLayers.popLast() ?? CAShapeLayer()
            segmentLayer.strokeColor = color.cgColor
            segmentLayer.lineWidth = donutThickness + 10
            segmentLayer.lineCap = kCALineCapButt
            segmentLayer.fillColor = nil

            let path = CGMutablePath()
            path.addArc(center: center, radius: radius, startAngle: angleOffset, endAngle: CGFloat(segmentSum / finalSum * 2 * .pi) + angleOffset, clockwise: false)
            segmentLayer.path = path

            layer.insertSublayer(segmentLayer, at: 0)
            segmentLayers.append(segmentLayer)

            if wantAnimation {
                segmentLayer.add(animation, forKey: animation.keyPath)
            }
        }

        for datum in data {
            addSegmentLayer(color: separatorColor, segmentSum: runningSum + separatorValue / 2)
            runningSum += datum.value + separatorValue
            addSegmentLayer(color: datum.color, segmentSum: runningSum - separatorValue / 2)
        }

        addSegmentLayer(color: separatorColor, segmentSum: finalSum)

        spareLayers.forEach { $0.removeFromSuperlayer() }
    }

    private var segmentLayers = [CAShapeLayer]()
    private var wantAnimation = false
}

let container = UIView()
container.frame.size = CGSize(width: 300, height: 300)
container.backgroundColor = .black
PlaygroundPage.current.liveView = container
PlaygroundPage.current.needsIndefiniteExecution = true

let m = ABDonutChart(frame: CGRect(x: 0, y: 0, width: 215, height: 215))
m.center = CGPoint(x: container.bounds.size.width / 2, y: container.bounds.size.height / 2)
container.addSubview(m)

m.withAnimation(true) {
    m.data = [
        .init(value: 10, color: .red),
        .init(value: 30, color: .blue),
        .init(value: 15, color: .orange),
        .init(value: 40, color: .yellow),
        .init(value: 50, color: .green)]
}

您看到的条纹是因为您在同一位置绘制了两次完全相同的形状,而alpha合成(通常实现)并不是为了处理这种情况而设计的,介绍了alpha合成,讨论了问题:

我们必须记住,我们关于 按几何对象分割亚像素区域 在有相关蒙版的输入图片面前向下。 当一张图片在合成表达式中出现两次时, 我们必须注意F和A的计算 F B.表中列出的仅适用于不相关的情况 图片

当它说“哑光”时,它基本上是指透明度。当它说“不相关图片”时,它指的是两张透明区域没有特殊关系的图片。但是在你的例子中,你的两张图片确实有一种特殊的关系:图片在完全相同的区域是透明的

下面是一个完整的测试,它再现了您的问题:

private func badVersion() {
    let center = CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2)
    let radius: CGFloat = 100
    let ringWidth: CGFloat = 44

    let ring = CAShapeLayer()
    ring.frame = view.bounds
    ring.fillColor = nil
    ring.strokeColor = UIColor.red.cgColor
    ring.lineWidth = ringWidth
    let ringPath = CGMutablePath()
    ringPath.addArc(center: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
    ringPath.closeSubpath()
    ring.path = ringPath
    view.layer.addSublayer(ring)

    let wedge = CAShapeLayer()
    wedge.frame = view.bounds
    wedge.fillColor = nil
    wedge.strokeColor = UIColor.darkGray.cgColor
    wedge.lineWidth = ringWidth
    wedge.lineCap = kCALineCapButt
    let wedgePath = CGMutablePath()
    wedgePath.addArc(center: center, radius: radius, startAngle: 0.1, endAngle: 0.6, clockwise: false)
    wedge.path = wedgePath
    view.layer.addSublayer(wedge)
}
以下是显示问题的屏幕部分:

解决此问题的一种方法是在环的边缘之外绘制颜色,并使用遮罩将其剪裁到环形状

我将更改我的代码,这样我就不用在上面画一个红色的环和一部分灰色的环,而是画一个红色的圆盘,在上面画一个灰色的楔子:

如果放大,可以看到这仍然显示灰色楔体边缘的红色条纹。所以诀窍是使用一个环形面具来获得最终的形状。以下是遮罩的形状,在前一张图像的顶部以白色绘制:

请注意,遮罩远离带有条纹的问题区域。当我将遮罩用作遮罩而不是绘制它时,我得到了最终的完美结果:

下面是绘制完美版本的代码:

private func goodVersion() {
    let center = CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2)
    let radius: CGFloat = 100
    let ringWidth: CGFloat = 44
    let slop: CGFloat = 10

    let disc = CAShapeLayer()
    disc.frame = view.bounds
    disc.fillColor = UIColor.red.cgColor
    disc.strokeColor = nil
    let ringPath = CGMutablePath()
    ringPath.addArc(center: center, radius: radius + ringWidth / 2 + slop, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
    ringPath.closeSubpath()
    disc.path = ringPath
    view.layer.addSublayer(disc)

    let wedge = CAShapeLayer()
    wedge.frame = view.bounds
    wedge.fillColor = UIColor.darkGray.cgColor
    wedge.strokeColor = nil
    let wedgePath = CGMutablePath()
    wedgePath.move(to: center)
    wedgePath.addArc(center: center, radius: radius + ringWidth / 2 + slop, startAngle: 0.1, endAngle: 0.6, clockwise: false)
    wedgePath.closeSubpath()
    wedge.path = wedgePath
    view.layer.addSublayer(wedge)

    let mask = CAShapeLayer()
    mask.frame = view.bounds
    mask.fillColor = nil
    mask.strokeColor = UIColor.white.cgColor
    mask.lineWidth = ringWidth
    let maskPath = CGMutablePath()
    maskPath.addArc(center: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
    maskPath.closeSubpath()
    mask.path = maskPath
    view.layer.mask = mask
}
请注意,遮罩适用于
视图
中的所有内容,因此(在您的情况下)可能需要将所有层移动到没有其他内容的子视图中,这样可以安全地遮罩

更新 看看你的操场,问题是(仍然)你正在画两个形状完全相同的pa