将特定值设置为 UIBezierPath 的圆角
Set specific value to round corners of UIBezierPath
我知道有一些类似的问题,但就我而言,我想为我的图层设置 cornerRadius = 8。
如果我设置:
shapeLayer.lineCap = CAShapeLayerLineCap.round
显示如下:
-> 与设计不符。
我的代码:
import Foundation
import UIKit
final class OnboardingCell: UICollectionViewCell {
@IBOutlet weak var boxView: UIView!
@IBOutlet weak var continueButton: UIButton!
@IBOutlet weak var progressView: UIView!
var onTap: (() -> Void)?
var levelProgress: CGFloat = 0.1 {
didSet {
fgLayer.strokeEnd = levelProgress
}
}
let bgLayer = CAShapeLayer()
let fgLayer = CAShapeLayer()
override func awakeFromNib() {
super.awakeFromNib()
setup()
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
setupShapeLayer(shapeLayer: bgLayer)
setupShapeLayer(shapeLayer: fgLayer)
}
private func setup() {
bgLayer.lineWidth = 50
bgLayer.fillColor = nil
bgLayer.strokeStart = 470 / 1590 + 0.008
bgLayer.strokeEnd = 1
bgLayer.cornerRadius = 8
progressView.layer.addSublayer(bgLayer)
fgLayer.lineWidth = 50
fgLayer.fillColor = nil
fgLayer.strokeStart = 0
fgLayer.strokeEnd = 470 / 1590 - 0.008
progressView.layer.addSublayer(fgLayer)
}
private func configure() {
bgLayer.strokeColor = UIColor(rgb: 0xE3EDF7).cgColor
fgLayer.strokeColor = UIColor(rgb: 0x18D4F4).cgColor
}
private func setupShapeLayer(shapeLayer: CAShapeLayer) {
let linePath = UIBezierPath(arcCenter: CGPoint(x: progressView.bounds.midX, y: progressView.bounds.midY), radius: progressView.frame.height / 2, startAngle: 9/11 * CGFloat.pi, endAngle: 2/11 * CGFloat.pi, clockwise: true)
linePath.lineWidth = 10
shapeLayer.path = linePath.cgPath
}
override func draw(_ rect: CGRect) {
super.draw(rect)
boxView.addFloatEffect()
continueButton.addFloatEffectForButton()
bgLayer.addSankEffect()
fgLayer.addFloatEffect()
}
@IBAction func continueButtonTapped(_ sender: UIButton) {
onTap?()
}
}
cornerRadius
只调整图层边界的角,不调整路径的角。如果你想要圆角的路径,你必须自己抚摸它。
有两种方法可以渲染具有特定角半径的圆弧:
如果渲染一个简单的实体再现,一种非常简单的方法是渲染两条弧线,一条顺时针,一条逆时针,使用不同的半径。这些单独圆弧的线宽应该是最终形状所需圆角的两倍。然后,如果您使用匹配的描边颜色和填充颜色渲染这两个圆弧,您将获得所需的形状。
这是动画,这样您就可以看到发生了什么:
这是代码(没有动画):
class ArcView: UIView {
var startAngle: CGFloat = .pi * 3 / 4
var endAngle: CGFloat = .pi * 5 / 4
var clockwise: Bool = true
/// Radius of center of this arc
var radius: CGFloat = 100
/// The linewidth of this thick arc
var lineWidth: CGFloat = 50
/// The corner radius of this thick arc
var cornerRadius: CGFloat = 10
static override var layerClass: AnyClass { return CAShapeLayer.self }
var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer }
override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
func updatePath() {
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let innerRadius = radius - lineWidth / 2 + cornerRadius
let innerAngularDelta = asin(cornerRadius / innerRadius) * (clockwise ? 1 : -1)
let outerRadius = radius + lineWidth / 2 - cornerRadius
let outerAngularDelta = asin(cornerRadius / outerRadius) * (clockwise ? 1 : -1)
let path = UIBezierPath(arcCenter: center, radius: innerRadius, startAngle: startAngle + innerAngularDelta, endAngle: endAngle - innerAngularDelta, clockwise: clockwise)
path.addArc(withCenter: center, radius: outerRadius, startAngle: endAngle - outerAngularDelta, endAngle: startAngle + outerAngularDelta, clockwise: !clockwise)
path.close()
// configure shapeLayer
shapeLayer.lineWidth = cornerRadius * 2
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineJoin = .round
shapeLayer.path = path.cgPath
}
}
上面唯一的技巧是如何调整这些内弧和外弧的起点和终点角度,使它们完美地被所需的最终形状包围。但是可以使用一点三角函数来计算那些 angular 增量,如上所示。
另一种方法是为所需形状的轮廓定义路径。计算内弧和外弧类似,但您必须手动计算四个圆角的角度。就是一个小三角,不过有点毛:
class ArcView: UIView {
var startAngle: CGFloat = .pi * 3 / 4
var endAngle: CGFloat = .pi * 5 / 4
var clockwise: Bool = true
/// Radius of center of this arc
var radius: CGFloat = 100
/// The linewidth of this thick arc
var lineWidth: CGFloat = 100
/// The corner radius of this thick arc
var cornerRadius: CGFloat = 10
static override var layerClass: AnyClass { return CAShapeLayer.self }
var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer }
override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
func updatePath() {
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let innerRadius = radius - lineWidth / 2
let innerAngularDelta = asin(cornerRadius / (innerRadius + cornerRadius)) * (clockwise ? 1 : -1)
let outerRadius = radius + lineWidth / 2
let outerAngularDelta = asin(cornerRadius / (outerRadius - cornerRadius)) * (clockwise ? 1 : -1)
let path = UIBezierPath(arcCenter: center, radius: innerRadius, startAngle: startAngle + innerAngularDelta, endAngle: endAngle - innerAngularDelta, clockwise: clockwise)
var angle = endAngle - innerAngularDelta
var cornerStartAngle = angle + .pi * (clockwise ? 1 : -1)
var cornerEndAngle = endAngle + .pi / 2 * (clockwise ? 1 : -1)
var cornerCenter = CGPoint(x: center.x + (innerRadius + cornerRadius) * cos(angle), y: center.y + (innerRadius + cornerRadius) * sin(angle))
path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise)
angle = endAngle - outerAngularDelta
cornerStartAngle = cornerEndAngle
cornerEndAngle = endAngle - outerAngularDelta
cornerCenter = CGPoint(x: center.x + (outerRadius - cornerRadius) * cos(angle), y: center.y + (outerRadius - cornerRadius) * sin(angle))
path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise)
path.addArc(withCenter: center, radius: outerRadius, startAngle: endAngle - outerAngularDelta, endAngle: startAngle + outerAngularDelta, clockwise: !clockwise)
angle = startAngle + outerAngularDelta
cornerStartAngle = angle
cornerEndAngle = startAngle - .pi / 2 * (clockwise ? 1 : -1)
cornerCenter = CGPoint(x: center.x + (outerRadius - cornerRadius) * cos(angle), y: center.y + (outerRadius - cornerRadius) * sin(angle))
path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise)
angle = startAngle + innerAngularDelta
cornerStartAngle = cornerEndAngle
cornerEndAngle = angle + .pi * (clockwise ? 1 : -1)
cornerCenter = CGPoint(x: center.x + (innerRadius + cornerRadius) * cos(angle), y: center.y + (innerRadius + cornerRadius) * sin(angle))
path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise)
path.close()
// configure shapeLayer
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.strokeColor = UIColor.clear.cgColor
shapeLayer.lineJoin = .round
shapeLayer.path = path.cgPath
}
}
我知道有一些类似的问题,但就我而言,我想为我的图层设置 cornerRadius = 8。
如果我设置:
shapeLayer.lineCap = CAShapeLayerLineCap.round
显示如下:
-> 与设计不符。
我的代码:
import Foundation
import UIKit
final class OnboardingCell: UICollectionViewCell {
@IBOutlet weak var boxView: UIView!
@IBOutlet weak var continueButton: UIButton!
@IBOutlet weak var progressView: UIView!
var onTap: (() -> Void)?
var levelProgress: CGFloat = 0.1 {
didSet {
fgLayer.strokeEnd = levelProgress
}
}
let bgLayer = CAShapeLayer()
let fgLayer = CAShapeLayer()
override func awakeFromNib() {
super.awakeFromNib()
setup()
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
setupShapeLayer(shapeLayer: bgLayer)
setupShapeLayer(shapeLayer: fgLayer)
}
private func setup() {
bgLayer.lineWidth = 50
bgLayer.fillColor = nil
bgLayer.strokeStart = 470 / 1590 + 0.008
bgLayer.strokeEnd = 1
bgLayer.cornerRadius = 8
progressView.layer.addSublayer(bgLayer)
fgLayer.lineWidth = 50
fgLayer.fillColor = nil
fgLayer.strokeStart = 0
fgLayer.strokeEnd = 470 / 1590 - 0.008
progressView.layer.addSublayer(fgLayer)
}
private func configure() {
bgLayer.strokeColor = UIColor(rgb: 0xE3EDF7).cgColor
fgLayer.strokeColor = UIColor(rgb: 0x18D4F4).cgColor
}
private func setupShapeLayer(shapeLayer: CAShapeLayer) {
let linePath = UIBezierPath(arcCenter: CGPoint(x: progressView.bounds.midX, y: progressView.bounds.midY), radius: progressView.frame.height / 2, startAngle: 9/11 * CGFloat.pi, endAngle: 2/11 * CGFloat.pi, clockwise: true)
linePath.lineWidth = 10
shapeLayer.path = linePath.cgPath
}
override func draw(_ rect: CGRect) {
super.draw(rect)
boxView.addFloatEffect()
continueButton.addFloatEffectForButton()
bgLayer.addSankEffect()
fgLayer.addFloatEffect()
}
@IBAction func continueButtonTapped(_ sender: UIButton) {
onTap?()
}
}
cornerRadius
只调整图层边界的角,不调整路径的角。如果你想要圆角的路径,你必须自己抚摸它。
有两种方法可以渲染具有特定角半径的圆弧:
如果渲染一个简单的实体再现,一种非常简单的方法是渲染两条弧线,一条顺时针,一条逆时针,使用不同的半径。这些单独圆弧的线宽应该是最终形状所需圆角的两倍。然后,如果您使用匹配的描边颜色和填充颜色渲染这两个圆弧,您将获得所需的形状。
这是动画,这样您就可以看到发生了什么:
这是代码(没有动画):
class ArcView: UIView { var startAngle: CGFloat = .pi * 3 / 4 var endAngle: CGFloat = .pi * 5 / 4 var clockwise: Bool = true /// Radius of center of this arc var radius: CGFloat = 100 /// The linewidth of this thick arc var lineWidth: CGFloat = 50 /// The corner radius of this thick arc var cornerRadius: CGFloat = 10 static override var layerClass: AnyClass { return CAShapeLayer.self } var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer } override func layoutSubviews() { super.layoutSubviews() updatePath() } func updatePath() { let center = CGPoint(x: bounds.midX, y: bounds.midY) let innerRadius = radius - lineWidth / 2 + cornerRadius let innerAngularDelta = asin(cornerRadius / innerRadius) * (clockwise ? 1 : -1) let outerRadius = radius + lineWidth / 2 - cornerRadius let outerAngularDelta = asin(cornerRadius / outerRadius) * (clockwise ? 1 : -1) let path = UIBezierPath(arcCenter: center, radius: innerRadius, startAngle: startAngle + innerAngularDelta, endAngle: endAngle - innerAngularDelta, clockwise: clockwise) path.addArc(withCenter: center, radius: outerRadius, startAngle: endAngle - outerAngularDelta, endAngle: startAngle + outerAngularDelta, clockwise: !clockwise) path.close() // configure shapeLayer shapeLayer.lineWidth = cornerRadius * 2 shapeLayer.fillColor = UIColor.blue.cgColor shapeLayer.strokeColor = UIColor.blue.cgColor shapeLayer.lineJoin = .round shapeLayer.path = path.cgPath } }
上面唯一的技巧是如何调整这些内弧和外弧的起点和终点角度,使它们完美地被所需的最终形状包围。但是可以使用一点三角函数来计算那些 angular 增量,如上所示。
另一种方法是为所需形状的轮廓定义路径。计算内弧和外弧类似,但您必须手动计算四个圆角的角度。就是一个小三角,不过有点毛:
class ArcView: UIView { var startAngle: CGFloat = .pi * 3 / 4 var endAngle: CGFloat = .pi * 5 / 4 var clockwise: Bool = true /// Radius of center of this arc var radius: CGFloat = 100 /// The linewidth of this thick arc var lineWidth: CGFloat = 100 /// The corner radius of this thick arc var cornerRadius: CGFloat = 10 static override var layerClass: AnyClass { return CAShapeLayer.self } var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer } override func layoutSubviews() { super.layoutSubviews() updatePath() } func updatePath() { let center = CGPoint(x: bounds.midX, y: bounds.midY) let innerRadius = radius - lineWidth / 2 let innerAngularDelta = asin(cornerRadius / (innerRadius + cornerRadius)) * (clockwise ? 1 : -1) let outerRadius = radius + lineWidth / 2 let outerAngularDelta = asin(cornerRadius / (outerRadius - cornerRadius)) * (clockwise ? 1 : -1) let path = UIBezierPath(arcCenter: center, radius: innerRadius, startAngle: startAngle + innerAngularDelta, endAngle: endAngle - innerAngularDelta, clockwise: clockwise) var angle = endAngle - innerAngularDelta var cornerStartAngle = angle + .pi * (clockwise ? 1 : -1) var cornerEndAngle = endAngle + .pi / 2 * (clockwise ? 1 : -1) var cornerCenter = CGPoint(x: center.x + (innerRadius + cornerRadius) * cos(angle), y: center.y + (innerRadius + cornerRadius) * sin(angle)) path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise) angle = endAngle - outerAngularDelta cornerStartAngle = cornerEndAngle cornerEndAngle = endAngle - outerAngularDelta cornerCenter = CGPoint(x: center.x + (outerRadius - cornerRadius) * cos(angle), y: center.y + (outerRadius - cornerRadius) * sin(angle)) path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise) path.addArc(withCenter: center, radius: outerRadius, startAngle: endAngle - outerAngularDelta, endAngle: startAngle + outerAngularDelta, clockwise: !clockwise) angle = startAngle + outerAngularDelta cornerStartAngle = angle cornerEndAngle = startAngle - .pi / 2 * (clockwise ? 1 : -1) cornerCenter = CGPoint(x: center.x + (outerRadius - cornerRadius) * cos(angle), y: center.y + (outerRadius - cornerRadius) * sin(angle)) path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise) angle = startAngle + innerAngularDelta cornerStartAngle = cornerEndAngle cornerEndAngle = angle + .pi * (clockwise ? 1 : -1) cornerCenter = CGPoint(x: center.x + (innerRadius + cornerRadius) * cos(angle), y: center.y + (innerRadius + cornerRadius) * sin(angle)) path.addArc(withCenter: cornerCenter, radius: cornerRadius, startAngle: cornerStartAngle, endAngle: cornerEndAngle, clockwise: !clockwise) path.close() // configure shapeLayer shapeLayer.fillColor = UIColor.blue.cgColor shapeLayer.strokeColor = UIColor.clear.cgColor shapeLayer.lineJoin = .round shapeLayer.path = path.cgPath } }