gpt4 book ai didi

ios - 带有 CAShapelayer 的圆弧圆环图 - 底层的边界可见

转载 作者:行者123 更新时间:2023-12-01 18:06:12 26 4
gpt4 key购买 nike

我用 CAShapeLayers 弧线绘制了一个圆环图。我画了enter image description here通过将一个放在另一个之上以及下面层边缘可见的问题。

绘图代码如下

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

但它只掩盖了内部边缘,外部仍然有边缘

最佳答案

您看到的边缘发生是因为您在同一位置两次绘制完全相同的形状,而 alpha 合成(通常实现)并不是为了处理这种情况而设计的。 Porter and Duff's paper, “Compositing Digital Images” ,介绍了 alpha 合成,讨论了这个问题:

We must remember that our basic assumption about the division of subpixel areas by geometric objects breaks down in the face of input pictures with correlated mattes. When one picture appears twice in a compositing expression, we must take care with our computations of F A and F B. Those listed in the table are correct only for uncorrelated pictures.



当它说“哑光”时,它基本上意味着透明。当它说“不相关的图片”时,是指透明区域没有特殊关系的两张图片。但在你的情况下,你的两张图片确实有一个特殊的关系:图片在完全相同的区域是透明的!

这是一个可以重现您的问题的独立测试:
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)
}

这是显示问题的屏幕部分:

the problem

解决此问题的一种方法是将颜色绘制到环边缘之外,并使用蒙版将它们剪辑到环形状。

我将更改我的代码,以便在其顶部绘制一个红色圆盘和一个灰色楔形,而不是绘制一个红色环和灰色环的一部分:

red disc with gray wedge

如果放大,您可以看到这仍然显示灰色楔形边缘的红色条纹。所以诀窍是使用环形蒙版来获得最终形状。这是蒙版的形状,在先前图像的顶部以白色绘制:

white mask

请注意, mask 远离边缘有问题的区域。当我将蒙版用作蒙版而不是绘制它时,我得到了最终的完美结果:

perfect ring

这是绘制完美版本的代码:
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
}

请注意,掩码适用于 view 中的所有内容。 ,因此(在您的情况下)您可能需要将所有图层移动到没有其他内容的 subview 中,因此可以安全地屏蔽。

更新

看看你的操场,问题是(仍然)你正在绘制两个形状,它们彼此顶部具有完全相同的部分透明边缘。你不能那样做。解决方法是将彩色形状画得更大,使它们在 donut 边缘完全不透明,然后使用图层蒙版将它们剪辑到 donut 形状。

我修好了你的操场。注意在我的版本中, lineWidth每个彩色部分的 donutThickness + 10 ,以及面具的 lineWidthdonutThickness .结果如下:

playground output

这里是游乐场:
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)]
}

关于ios - 带有 CAShapelayer 的圆弧圆环图 - 底层的边界可见,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43633059/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com