我正在尝试使用 UIBezierPath 创建椭圆形/圆角矩形。我想要达到的就是这个形状
问题之一是我希望能够找到正确的半径来存档我的目标形状,我遇到的第二个问题是我可以看到线条伸出,代码不会产生干净的形状
override func layoutSubviews() {
super.layoutSubviews()
layer.sublayers?.forEach { $0.removeFromSuperlayer() }
let path = makePath()
path.lineJoinStyle = .round
path.lineCapStyle = .round
let shapeLayer = CAShapeLayer()
// Enable antialiasing
shapeLayer.shouldRasterize = true
shapeLayer.rasterizationScale = UIScreen.main.scale
shapeLayer.lineJoin = .round
shapeLayer.path = path.cgPath
//shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.lineCap = .round
layer.addSublayer(shapeLayer)
layer.backgroundColor = overlayColor.cgColor
//backgroundPath is the blur overlay
let backgroundPath = UIBezierPath(rect: bounds)
backgroundPath.lineJoinStyle = .round
backgroundPath.lineCapStyle = .round
let maskLayer = CAShapeLayer()
// Enable antialiasing
maskLayer.shouldRasterize = true
maskLayer.rasterizationScale = UIScreen.main.scale
maskLayer.frame = bounds
maskLayer.lineJoin = .round
//backgroundPath.append(path)
maskLayer.fillRule = .evenOdd
maskLayer.path = backgroundPath.cgPath
layer.mask = maskLayer
addAdditionalLayersIfNeeded(rect)
}
override func makePath(rect: CGRect) -> UIBezierPath {
UIBezierPath(roundedRect: preferedSize, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: preferedSize.width * 0.33, height: preferedSize.height * 0.33))
}
我用来创建形状的尺寸
preferedSize: CGRect(x: 32, y: 278, width: 320, height: 400)
这就是代码将呈现的内容
您遇到了一些错误
UIBezierPath(roundedRect: ...
首先,忽略
height
的 cornerRadii
值。所以,我们也忽略它并使用:
var cr: CGFloat = 0.25
let r: CGRect = CGRect(x: 20.0, y: 20.0, width: 320.0, height: 400.0)
let path = UIBezierPath(roundedRect: r, cornerRadius: cr * r.width)
shapeLayer.path = path.cgPath
基本
CAShapeLayer
包括:
.lineWidth = 20.0
.strokeColor = UIColor.orange.cgColor
.fillColor = UIColor.cyan.cgColor
因此,我们从 25% 的角半径开始,然后逐渐增加它:
如您所见,当我们到达
29.5%
的角半径时,实际半径跳跃并且我们得到一个奇怪的“间隙” - 这也留下了一个小的未对准,这就是您在侧面看到的“凹凸”。
随着我们不断增加宽度百分比半径值,实际半径保持不变,直到达到
36.5%
——此时半径发生变化并且未对准消失(我们得到平滑的边缘)。
请注意,这将根据路径的实际大小和笔划的宽度而有所不同。
如果我们这样做:
print(path.cgPath
(25%),我们会在调试控制台中得到这个:
moveto (154.828, 20)
lineto (205.172, 20)
curveto (243.995, 20) (263.407, 20) (284.302, 26.6072)
lineto (284.302, 26.6072)
curveto (307.117, 34.9111) (325.089, 52.8831) (333.393, 75.6978)
curveto (340, 96.5935) (340, 116.005) (340, 154.828)
lineto (340, 285.172)
curveto (340, 323.995) (340, 343.407) (333.393, 364.302)
lineto (333.393, 364.302)
curveto (325.089, 387.117) (307.117, 405.089) (284.302, 413.393)
curveto (263.407, 420) (243.995, 420) (205.172, 420)
lineto (154.828, 420)
curveto (116.005, 420) (96.5935, 420) (75.6978, 413.393)
lineto (75.6978, 413.393)
curveto (52.8831, 405.089) (34.9111, 387.117) (26.6072, 364.302)
curveto (20, 343.407) (20, 323.995) (20, 285.172)
lineto (20, 154.828)
curveto (20, 116.005) (20, 96.5935) (26.6072, 75.6978)
lineto (26.6072, 75.6978)
curveto (34.9111, 52.8831) (52.8831, 34.9111) (75.6978, 26.6072)
curveto (96.5935, 20) (116.005, 20) (154.828, 20)
lineto (154.828, 20)
正如我们所见,“圆角矩形”路径实际上是一系列直线到和曲线到指令。
我的猜测是苹果的内部
roundedRect
算法遇到了浮点错误。
避免错误的一种方法是使用此扩展自己构建路径:
extension UIBezierPath {
static func roundedRect(rect: CGRect, cornerRadius: CGFloat) -> UIBezierPath {
// use shorter of width or height as max corner radius value
// and don't exceed 50%
let v: CGFloat = min(rect.width, rect.height)
let cr: CGFloat = min(v * 0.5, cornerRadius)
let path = CGMutablePath()
let start = CGPoint(x: rect.midX, y: rect.minY)
path.move(to: start)
path.addArc(tangent1End: rect.topRight, tangent2End: rect.bottomRight, radius: cr)
path.addArc(tangent1End: rect.bottomRight, tangent2End: rect.bottomLeft, radius: cr)
path.addArc(tangent1End: rect.bottomLeft, tangent2End: rect.topLeft, radius: cr)
path.addArc(tangent1End: rect.topLeft, tangent2End: start, radius: cr)
path.closeSubpath()
return UIBezierPath(cgPath: path)
}
}
// uses this "convenience" extension
extension CGRect {
var topRight: CGPoint { CGPoint(x: maxX, y: minY) }
var topLeft: CGPoint { CGPoint(x: minX, y: minY) }
var bottomRight: CGPoint { CGPoint(x: maxX, y: maxY) }
var bottomLeft: CGPoint { CGPoint(x: minX, y: maxY) }
}
现在,宽度为 33% 的圆角半径路径生成如下:
var cr: CGFloat = 0.33
let r: CGRect = CGRect(x: 20.0, y: 20.0, width: 320.0, height: 400.0)
let path = UIBezierPath(roundedRect: r, cornerRadius: cr * r.width)
shapeLayer.path = path.cgPath
给我们这个:
值得注意的是:苹果的算法生成的是“连续曲线”圆角,略有不同。
此扩展:
extension UIBezierPath {
static func roundedRect(
rect: CGRect,
corners: UIRectCorner = .allCorners,
cornerRadius: CGFloat
) -> UIBezierPath {
// use shorter of width or height as max corner radius value
// and don't exceed 50%
let v: CGFloat = min(rect.width, rect.height)
let cr: CGFloat = min(v * 0.5, cornerRadius)
let tweak: CGFloat = 1.2 // could experiment with this
let offset = cr * tweak
if rect.width > 2 * offset { // less than that, my algorithm starts to break down — but theirs works!
let path = CGMutablePath()
let start = CGPoint(x: rect.midX, y: rect.minY)
path.move(to: start)
if corners.contains(.topRight) {
path.addLine(to: rect.topRight.offset(x: -offset, y: 0))
path.addQuadCurve(to: rect.topRight.offset(x: 0, y: offset), control: rect.topRight)
} else {
path.addLine(to: rect.topRight)
}
if corners.contains(.bottomRight) {
path.addLine(to: rect.bottomRight.offset(x: 0, y: -offset))
path.addQuadCurve(to: rect.bottomRight.offset(x: -offset, y: 0), control: rect.bottomRight)
} else {
path.addLine(to: rect.bottomRight)
}
if corners.contains(.bottomLeft) {
path.addLine(to: rect.bottomLeft.offset(x: offset, y: 0))
path.addQuadCurve(to: rect.bottomLeft.offset(x: -0, y: -offset), control: rect.bottomLeft)
} else {
path.addLine(to: rect.bottomLeft)
}
if corners.contains(.topLeft) {
path.addLine(to: rect.topLeft.offset(x: 0, y: offset))
path.addQuadCurve(to: rect.topLeft.offset(x: offset, y: 0), control: rect.topLeft)
} else {
path.addLine(to: rect.topLeft)
}
path.closeSubpath()
return UIBezierPath(cgPath: path)
}
return UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
}
}
// uses these "convenience" extensions
extension CGRect {
var topRight: CGPoint { CGPoint(x: maxX, y: minY) }
var topLeft: CGPoint { CGPoint(x: minX, y: minY) }
var bottomRight: CGPoint { CGPoint(x: maxX, y: maxY) }
var bottomLeft: CGPoint { CGPoint(x: minX, y: maxY) }
}
extension CGPoint {
func offset(x xOffset: CGFloat, y yOffset: CGFloat) -> CGPoint {
CGPoint(x: x + xOffset, y: y + yOffset)
}
}
给出这个结果:
请注意:这些扩展是此处讨论中略有修改的版本UIBezierPath bezierPathWithRoundedRect:cornerRadius值不一致