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