如何为圆圈制作透明轮廓以使背景显示出来?像这样:
我尝试使用掩码,但此代码不起作用......
也许还有其他解决方案?
我的代码:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let backgroundImage = UIImageView(frame: UIScreen.main.bounds)
backgroundImage.image = UIImage(named: "background")
backgroundImage.contentMode = .scaleAspectFill
view.addSubview(backgroundImage)
view.sendSubviewToBack(backgroundImage)
// Параметры для кругов
let circleSize: CGFloat = 100
let borderWidth: CGFloat = 4
let circleSpacing: CGFloat = 30
var previousCircleView: UIImageView?
for i in 0..<3 {
let circleImageView = UIImageView()
circleImageView.image = UIImage(named: "ggg")
circleImageView.contentMode = .scaleAspectFill
view.addSubview(circleImageView)
let maskLayer = CAShapeLayer()
let circlePath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: circleSize, height: circleSize))
let borderPath = UIBezierPath(ovalIn: CGRect(x: -borderWidth, y: -borderWidth, width: circleSize + 2 * borderWidth, height: circleSize + 2 * borderWidth))
borderPath.append(circlePath)
borderPath.usesEvenOddFillRule = true
maskLayer.path = borderPath.cgPath
maskLayer.fillRule = .evenOdd
circleImageView.layer.mask = maskLayer
circleImageView.snp.makeConstraints { make in
make.width.height.equalTo(circleSize + 2 * borderWidth)
make.centerY.equalToSuperview()
if let previous = previousCircleView {
make.left.equalTo(previous.snp.right).offset(-(circleSize - circleSpacing + borderWidth))
} else {
make.left.equalToSuperview().offset(20)
}
}
previousCircleView = circleImageView
}
}
}
每个形状都由两条弧线组成。唯一的技巧是计算每个角度的起始角和结束角,需要一点三角学知识。例如,您可以执行以下操作:
func addSublayers(to view: UIView) {
let radius: CGFloat = 150
let borderWidth: CGFloat = 10
let circleCenterOffset: CGFloat = 120
let startCenter = CGPoint(x: 200, y: 200)
for i in 0..<3 {
let shapeLayer = createShapeLayer()
shapeLayer.path = circleDifference(
center: point(startCenter, xOffset: CGFloat(i) * circleCenterOffset),
radius: radius,
offset: circleCenterOffset,
width: borderWidth
).cgPath
view.layer.addSublayer(shapeLayer)
}
let shapeLayer = createShapeLayer()
shapeLayer.path = UIBezierPath(arcCenter: point(startCenter, xOffset: 3 * circleCenterOffset), radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true).cgPath
view.layer.addSublayer(shapeLayer)
}
private func point(_ point: CGPoint, xOffset: CGFloat) -> CGPoint {
CGPoint(x: point.x + xOffset, y: point.y)
}
private func createShapeLayer() -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.clear.cgColor
shapeLayer.fillColor = …
return shapeLayer
}
private func circleDifference(center: CGPoint, radius: CGFloat, offset: CGFloat, width: CGFloat) -> UIBezierPath {
let r1 = radius
let r2 = radius + width
let d = offset
// angles; using law of cosines
let angle1 = acos((r1 * r1 + d * d - r2 * r2) / (2 * r1 * d))
let angle2 = acos((r2 * r2 + d * d - r1 * r1) / (2 * r2 * d))
// path
let path = UIBezierPath()
path.addArc(
withCenter: center,
radius: r1,
startAngle: angle1,
endAngle: 2 * .pi - angle1,
clockwise: true
)
path.addArc(
withCenter: point(center, xOffset: offset),
radius: r2,
startAngle: 3 * .pi / 2 - (.pi / 2 - angle2),
endAngle: .pi / 2 + (.pi / 2 - angle2),
clockwise: false
)
path.close()
return path
}
结果:
如果您想了解这里的三角学,请考虑下图,其中左侧红色圆圈是要渲染的圆圈(减去右侧外部绿色圆圈(即下一个圆圈)、内部右侧蓝色圆圈,加上任何间距我们要)。因此,我们要绘制红色和绿色圆圈的弧,以与这些圆圈相交的位置相对应的角度开始和停止弧。
所以,我想象了一个三角形(蓝色阴影),其中顶点是两个圆的中心加上两个圆相交的位置。抚摸两条圆弧时使用的角度是该三角形的左下角和右下角。我们知道这个三角形所有边的长度(左边是圆的半径,右边是圆的半径加上间距,底线是圆之间的距离)。
在我原来的答案中,我计算了三角形的高度,h,短垂直紫色线段(使用划分面积的技术,用Heron公式计算,除以半周长),然后计算两者使用
asin(h/r)
. 的角度
但是我修改了我的示例,通过余弦定律计算两个角度。这对大多数读者来说可能会更直观。
如果所有这些数学太令人困惑,您可以将每个形状图层添加为被另一个圆圈遮盖的圆圈:
func addSublayers(to view: UIView) {
let radius: CGFloat = 150
let borderWidth: CGFloat = 10
let circleCenterOffset: CGFloat = 120
let startCenter = CGPoint(x: 200, y: 200)
for i in 0..<3 {
let shapeLayer = createShapeLayer()
var center = point(startCenter, xOffset: CGFloat(i) * circleCenterOffset)
shapeLayer.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true).cgPath
let mask = CAShapeLayer()
mask.fillColor = UIColor.white.cgColor
mask.strokeColor = UIColor.clear.cgColor
center = point(startCenter, xOffset: CGFloat(i + 1) * circleCenterOffset)
let path = UIBezierPath(rect: view.bounds)
path.addArc(withCenter: center, radius: radius + borderWidth, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
mask.fillRule = .evenOdd
mask.path = path.cgPath
shapeLayer.mask = mask
view.layer.addSublayer(shapeLayer)
}
let shapeLayer = createShapeLayer()
let center = point(startCenter, xOffset: 3 * circleCenterOffset)
shapeLayer.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true).cgPath
view.layer.addSublayer(shapeLayer)
}
就我个人而言,我总是倾向于在没有任何掩码的情况下创建所需的路径,但后一种方法完全避免了任何令人困惑的数学。
如果您的目标是一系列以整圆结尾的新月,则创建形状图层和蒙版将涉及绘制部分圆弧和一些三角函数。
您可以通过创建包含所需内容的图像视图来实现此目的。在这种情况下,您可以编写在循环中运行的代码,用不透明的颜色绘制一个圆圈,然后切换到清晰模式并擦除圆圈的轮廓。每个新圆都会擦除前一个圆的一部分,但最后一个圆不会被剪掉。这就是您的示例图像所显示的内容。
这是我编写的用于创建该图像的代码,大致基于您的起始代码:
import UIKit
class ViewController: UIViewController {
var imageView = UIImageView(frame: CGRectZero)
// Install an image in imageView that contains a series of crescent shapes, ending in a full circle.
private func setShades(count: Int) {
var image = UIImage()
let lineWidth = 7.0
let circleDiameter = 50.0
let width = CGFloat(count+1)/2.0 * circleDiameter
let bounds = CGRect(origin: CGPointZero, size: CGSize(width: width, height: circleDiameter))
let circleColor = UIColor(_colorLiteralRed: 161.0/255, green: 62.0/255, blue: 3.0/255, alpha: 1.0)
let renderer = UIGraphicsImageRenderer(size: bounds.size)
image = renderer.image { context in
for index in 0..<count {
let x = CGFloat(index) * circleDiameter / 2
let circleRect = CGRect(
x: x, y: 0,
width: circleDiameter, height: circleDiameter)
let circle = UIBezierPath.init(ovalIn: circleRect)
circle.lineWidth = lineWidth
circleColor.setFill()
context.cgContext.setBlendMode(.normal)
//Fill the circle with our circle color
circle.fill()
//Switch the drawing mode to .clear, so we erase anything we draw
context.cgContext.setBlendMode(.clear)
// Erase the outline of this circle
circle.stroke()
}
}
// Place the image view near the bottom of the content view, and on the right side.
let frame = CGRect(x: view.bounds.maxX - bounds.width, y: view.bounds.maxY - 100 - bounds.height, width: bounds.width, height: bounds.height)
imageView.frame = frame
imageView.image = image
}
override func viewDidLayoutSubviews() {
// You need to create teh image view when the view changes its subviews
//so that it is placed correctly (e.g. after device rotation)
setShades(count: 4)
}
override func viewDidLoad() {
super.viewDidLoad()
let backgroundImage = UIImageView(frame: UIScreen.main.bounds)
backgroundImage.image = UIImage(named: "background")
backgroundImage.contentMode = .scaleAspectFill
view.addSubview(backgroundImage)
view.addSubview(imageView)
}
}
这里是 Github 上的一个 示例项目,它创建了下面的图像: