使用 UIBezierPath 绘制同心圆
Draw concentric circle using UIBezierPath
我正在尝试使用 UIBezierPath 绘制一个同心圆,这是 中的预期结果。
但我实际得到的与 不同。
我试过制作两个不同直径的圆圈并填充较小的圆圈。
let path = UIBezierPath(ovalIn: CGRect(x: position.x - diameter / 2, y: position.y - diameter / 2, width: diameter, height: diameter))
let path2 = UIBezierPath(ovalIn: CGRect(x: position.x - diameter / 2 + 2, y: position.y - diameter / 2 + 2, width: diameter - 2, height: diameter - 2))
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = color.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = lineWidth
let shapeLayer2 = CAShapeLayer()
shapeLayer.path = path2.cgPath
shapeLayer.strokeColor = color.cgColor
shapeLayer.fillColor = isFilled ? color.cgColor : UIColor.white.cgColor
shapeLayer.lineWidth = lineWidth
view.layer.addSublayer(shapeLayer)
view.layer.addSublayer(shapeLayer2)
你不需要图层。只需使用 UIBezierPath
s:
override func draw(_ rect: CGRect) {
// this is the line width of the outer circle
let outerLineWidth = CGFloat(3) // adjust this however you like
// if you didn't create a separate view for these concentric circles,
// which I strongly recommend you do, replace "bounds" with the desired frame
let outerCircle = UIBezierPath(ovalIn: bounds.insetBy(dx: outerLineWidth / 2, dy: outerLineWidth / 2))
UIColor.white.setStroke()
outerCircle.lineWidth = outerLineWidth
outerCircle.stroke()
// this is the distance between the outer and inner circle
let inset = CGFloat(5); // you can adjust this too
let innerCircle = UIBezierPath(ovalIn: bounds.insetBy(dx: inset, dy: inset))
UIColor.white.setFill()
innerCircle.fill()
}
如您所见,无需手动计算帧数,只需使用insetBy(dx:dy:)
方法即可。
下面是使用图层的具体方法。
使用图层而不是绘制确实有很多优点。
("drawing",我的意思是画在draw#rect
。)
从动画开始比较容易;而且它更可重复使用;而且您不必担心 "if the view moves/reshapes what exactly should I redraw" 的难度。一切都是自动的层。
所以这里是四个制作图层时要做的主要事情:
1。一如既往,制作图层,并在 layoutSubviews 中设置它们的框架。
在 iOS 中,当你制作图层时,你 (A) 制作它们 (几乎可以肯定使用 lazy var)和(B) 在布局时,当然你设置框架的大小。
这是在 iOS.
中使用图层的两个基本步骤
Note that you do not make them at layout time, and you do not set the frame at bringup time. You only make them at bringup time and only set the size of the frame at layout time.
我在情节提要中制作了一个 UIView,"Thingy" 并使用约束对其进行定位并将其设为 100x100。
我将背景颜色设为蓝色,这样您就可以看到发生了什么。 (您可能希望它清楚。)
在应用程序中:
我们需要环和间隙的厚度:
class Thingy: UIView {
var thicknessOfOuterRing: CGFloat = 20.0 {
didSet{ setNeedsLayout() }
}
var thicknessOfTheGap: CGFloat = 20.0 {
didSet{ setNeedsLayout() }
}
需要理解的一个关键点是,当您更改这些值时,您将不得不调整所有内容的大小。对吗?
这意味着您可以按常规方式将 "didSet .. setNeedsLayout" 添加到这些属性。
(同样,如果您希望能够即时更改颜色,您也可以对颜色执行此操作。任何您希望能够 "change" 的内容,您都需要 "didSet .. setNeedsLayout"图案。)
我们总是需要进行两个简单的计算,即半厚度和它们的组合脂肪。只需为这些使用计算变量,即可大大简化您的代码。
var halfT: CGFloat { return thicknessOfOuterRing / 2.0 }
var both: CGFloat { return thicknessOfOuterRing + thicknessOfTheGap }
好的!所以两层。环将是一个形状层,但中间的斑点可以简单地是一个层:
private lazy var outerBand: CAShapeLayer = {
let l = CAShapeLayer()
layer.addSublayer(l)
return l
}()
private lazy var centralBlob: CALayer = {
let l = CALayer()
layer.addSublayer(l)
return l
}()
现在当然,您必须在布局时设置它们的框架:
override func layoutSubviews() {
super.layoutSubviews()
outerBand.frame = bounds
centralBlob.frame = bounds
}
这就是在iOS.
中使用图层的基本过程
(A) 制作层(几乎可以肯定是用惰性变量)和 (B) 在布局时,当然你设置框架的大小。
2。设置图层的所有 "fixed" 方面,当它们被制作时:
层的哪些特性永远不会改变?
在惰性变量中设置 "never-changing" 品质 :
private lazy var outerBand: CAShapeLayer = {
let l = CAShapeLayer()
l.fillColor = UIColor.clear.cgColor
l.strokeColor = UIColor.black.cgColor
layer.addSublayer(l)
return l
}()
private lazy var centralBlob: CALayer = {
let l = CALayer()
l.backgroundColor = UIColor.black.cgColor
layer.addSublayer(l)
return l
}()
再一次,任何永远不会改变的东西,把它推到惰性变量中。
下一个...
3。设置图层的所有 "changing" 方面,在布局中:
关于中心斑点。用 iOS 中的正方形 CALayer
做成硬币形状非常容易,你只需这样做:
someCALayer.cornerRadius = someCALayer.bounds.width / 2.0
请注意,您可以,而且有些程序员确实会这样做,实际上使用形状层,然后费心制作圆形等。但是只用一个普通的图层,简单地设置cornerRadius就可以了。
其次是关于内部斑点。请注意,它只是比整个单元小。换句话说,内部 blob 的框架必须缩小一些。
要实现这一点,请使用 iOS 中的关键 inset(by: UIEdgeInsets
调用。经常使用它!
您在iOS.
中经常使用inset(by: UIEdgeInsets
这里有一个重要的小提示。我真的鼓励你使用 inset(by: UIEdgeInsets( ...
你悲惨地写出 "top, left, bottom, right"
另一种变体,insetBy#dx#dy
容易引起混淆;不清楚你是在做双倍的数量还是什么。
我个人推荐使用"long form"inset(by: UIEdgeInsets( ...
因为有那么绝对没有疑问发生了什么.
所以我们现在完全完成了内点:
override func layoutSubviews() {
super.layoutSubviews()
outerBand.frame = bounds
outerBand.lineWidth = thicknessOfOuterRing
centralBlob.frame =
bounds.inset(by: UIEdgeInsets(top: both, left: both, bottom: both, right: both))
centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
}
最后,这是你问题的核心:
外圈是使用形状图层制作的,而不是普通图层。
形状图层由路径定义。
这里是 "one surprising trick",它是在 iOS 中使用形状图层和路径的核心:
4。在 iOS 中,您实际上在 layoutSubviews 中创建了形状图层的路径。
根据我的经验,这让从其他平台迁移到 iOS 的程序员感到困惑,但事情就是这样。
所以让我们创建一个计算变量来为我们创建路径。
请注意,您一定会使用方便的 inset(by: UIEdgeInsets( ...
调用。
不要忘记,在调用此函数时,您应该已经正确设置所有涉及的帧。
最后,Apple 好心地给我们提供了非常方便的 UIBezierPath#ovalIn
调用,这样您就不需要用弧线和 Pi 来愚弄了!
就是这么简单...
var circlePathForCurrentLayout: UIBezierPath {
var b = bounds
b = b.inset(by: UIEdgeInsets(top: halfT, left: halfT, bottom: halfT, right: halfT))
let p = UIBezierPath(ovalIn: b)
return p
}
然后,正如粗体字所示,在 iOS 中,您出人意料地设置了形状图层的实际路径,在 layoutSubviews.
override func layoutSubviews() {
super.layoutSubviews()
outerBand.frame = bounds
outerBand.lineWidth = thicknessOfOuterRing
outerBand.path = circlePathForCurrentLayout.cgPath
centralBlob.frame =
bounds.inset(by: UIEdgeInsets(top: both, left: both, bottom: both, right: both))
centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
}
回顾:
1。一如既往,制作图层,并在 layoutSubviews 中设置它们的框架。
2。设置图层的所有 "fixed" 方面,制作时。
3。设置图层的所有 "changing" 方面,in layoutSubviews.
4。令人惊讶的是,在 iOS 中,您实际上在 layoutSubviews.
中创建了形状图层的 paths
下面是所有内容:
// Thingy.swift
// Created for SO on 11/3/19.
import UIKit
class Thingy: UIView {
var thicknessOfOuterRing: CGFloat = 20.0 {
didSet{ setNeedsLayout() }
}
var thicknessOfTheGap: CGFloat = 20.0 {
didSet{ setNeedsLayout() }
}
var halfT: CGFloat { return thicknessOfOuterRing / 2.0 }
var both: CGFloat { return thicknessOfOuterRing + thicknessOfTheGap }
var circlePathForCurrentLayout: UIBezierPath {
var b = bounds
b = b.inset(by:
UIEdgeInsets(top: halfT, left: halfT, bottom: halfT, right: halfT))
let p = UIBezierPath(ovalIn: b)
return p
}
private lazy var outerBand: CAShapeLayer = {
let l = CAShapeLayer()
l.fillColor = UIColor.clear.cgColor
l.strokeColor = UIColor.black.cgColor
layer.addSublayer(l)
return l
}()
private lazy var centralBlob: CALayer = {
let l = CALayer()
l.backgroundColor = UIColor.black.cgColor
layer.addSublayer(l)
return l
}()
override func layoutSubviews() {
super.layoutSubviews()
outerBand.frame = bounds
outerBand.lineWidth = thicknessOfOuterRing
outerBand.path = circlePathForCurrentLayout.cgPath
centralBlob.frame = bounds.inset(by:
UIEdgeInsets(top: both, left: both, bottom: both, right: both))
centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
}
}
后续步骤:
如果你想接受在中间放置 图像 的挑战:link
如果你想,呃,接受阴影:
查看渐变?
部分圆弧:
我正在尝试使用 UIBezierPath 绘制一个同心圆,这是
但我实际得到的与
我试过制作两个不同直径的圆圈并填充较小的圆圈。
let path = UIBezierPath(ovalIn: CGRect(x: position.x - diameter / 2, y: position.y - diameter / 2, width: diameter, height: diameter))
let path2 = UIBezierPath(ovalIn: CGRect(x: position.x - diameter / 2 + 2, y: position.y - diameter / 2 + 2, width: diameter - 2, height: diameter - 2))
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = color.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = lineWidth
let shapeLayer2 = CAShapeLayer()
shapeLayer.path = path2.cgPath
shapeLayer.strokeColor = color.cgColor
shapeLayer.fillColor = isFilled ? color.cgColor : UIColor.white.cgColor
shapeLayer.lineWidth = lineWidth
view.layer.addSublayer(shapeLayer)
view.layer.addSublayer(shapeLayer2)
你不需要图层。只需使用 UIBezierPath
s:
override func draw(_ rect: CGRect) {
// this is the line width of the outer circle
let outerLineWidth = CGFloat(3) // adjust this however you like
// if you didn't create a separate view for these concentric circles,
// which I strongly recommend you do, replace "bounds" with the desired frame
let outerCircle = UIBezierPath(ovalIn: bounds.insetBy(dx: outerLineWidth / 2, dy: outerLineWidth / 2))
UIColor.white.setStroke()
outerCircle.lineWidth = outerLineWidth
outerCircle.stroke()
// this is the distance between the outer and inner circle
let inset = CGFloat(5); // you can adjust this too
let innerCircle = UIBezierPath(ovalIn: bounds.insetBy(dx: inset, dy: inset))
UIColor.white.setFill()
innerCircle.fill()
}
如您所见,无需手动计算帧数,只需使用insetBy(dx:dy:)
方法即可。
下面是使用图层的具体方法。
使用图层而不是绘制确实有很多优点。
("drawing",我的意思是画在draw#rect
。)
从动画开始比较容易;而且它更可重复使用;而且您不必担心 "if the view moves/reshapes what exactly should I redraw" 的难度。一切都是自动的层。
所以这里是四个制作图层时要做的主要事情:
1。一如既往,制作图层,并在 layoutSubviews 中设置它们的框架。
在 iOS 中,当你制作图层时,你 (A) 制作它们 (几乎可以肯定使用 lazy var)和(B) 在布局时,当然你设置框架的大小。
这是在 iOS.
中使用图层的两个基本步骤Note that you do not make them at layout time, and you do not set the frame at bringup time. You only make them at bringup time and only set the size of the frame at layout time.
我在情节提要中制作了一个 UIView,"Thingy" 并使用约束对其进行定位并将其设为 100x100。
我将背景颜色设为蓝色,这样您就可以看到发生了什么。 (您可能希望它清楚。)
在应用程序中:
我们需要环和间隙的厚度:
class Thingy: UIView {
var thicknessOfOuterRing: CGFloat = 20.0 {
didSet{ setNeedsLayout() }
}
var thicknessOfTheGap: CGFloat = 20.0 {
didSet{ setNeedsLayout() }
}
需要理解的一个关键点是,当您更改这些值时,您将不得不调整所有内容的大小。对吗?
这意味着您可以按常规方式将 "didSet .. setNeedsLayout" 添加到这些属性。
(同样,如果您希望能够即时更改颜色,您也可以对颜色执行此操作。任何您希望能够 "change" 的内容,您都需要 "didSet .. setNeedsLayout"图案。)
我们总是需要进行两个简单的计算,即半厚度和它们的组合脂肪。只需为这些使用计算变量,即可大大简化您的代码。
var halfT: CGFloat { return thicknessOfOuterRing / 2.0 }
var both: CGFloat { return thicknessOfOuterRing + thicknessOfTheGap }
好的!所以两层。环将是一个形状层,但中间的斑点可以简单地是一个层:
private lazy var outerBand: CAShapeLayer = {
let l = CAShapeLayer()
layer.addSublayer(l)
return l
}()
private lazy var centralBlob: CALayer = {
let l = CALayer()
layer.addSublayer(l)
return l
}()
现在当然,您必须在布局时设置它们的框架:
override func layoutSubviews() {
super.layoutSubviews()
outerBand.frame = bounds
centralBlob.frame = bounds
}
这就是在iOS.
中使用图层的基本过程(A) 制作层(几乎可以肯定是用惰性变量)和 (B) 在布局时,当然你设置框架的大小。
2。设置图层的所有 "fixed" 方面,当它们被制作时:
层的哪些特性永远不会改变?
在惰性变量中设置 "never-changing" 品质 :
private lazy var outerBand: CAShapeLayer = {
let l = CAShapeLayer()
l.fillColor = UIColor.clear.cgColor
l.strokeColor = UIColor.black.cgColor
layer.addSublayer(l)
return l
}()
private lazy var centralBlob: CALayer = {
let l = CALayer()
l.backgroundColor = UIColor.black.cgColor
layer.addSublayer(l)
return l
}()
再一次,任何永远不会改变的东西,把它推到惰性变量中。
下一个...
3。设置图层的所有 "changing" 方面,在布局中:
关于中心斑点。用 iOS 中的正方形 CALayer
做成硬币形状非常容易,你只需这样做:
someCALayer.cornerRadius = someCALayer.bounds.width / 2.0
请注意,您可以,而且有些程序员确实会这样做,实际上使用形状层,然后费心制作圆形等。但是只用一个普通的图层,简单地设置cornerRadius就可以了。
其次是关于内部斑点。请注意,它只是比整个单元小。换句话说,内部 blob 的框架必须缩小一些。
要实现这一点,请使用 iOS 中的关键 inset(by: UIEdgeInsets
调用。经常使用它!
您在iOS.
中经常使用inset(by: UIEdgeInsets
这里有一个重要的小提示。我真的鼓励你使用 inset(by: UIEdgeInsets( ...
你悲惨地写出 "top, left, bottom, right"
另一种变体,insetBy#dx#dy
容易引起混淆;不清楚你是在做双倍的数量还是什么。
我个人推荐使用"long form"inset(by: UIEdgeInsets( ...
因为有那么绝对没有疑问发生了什么.
所以我们现在完全完成了内点:
override func layoutSubviews() {
super.layoutSubviews()
outerBand.frame = bounds
outerBand.lineWidth = thicknessOfOuterRing
centralBlob.frame =
bounds.inset(by: UIEdgeInsets(top: both, left: both, bottom: both, right: both))
centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
}
最后,这是你问题的核心:
外圈是使用形状图层制作的,而不是普通图层。
形状图层由路径定义。
这里是 "one surprising trick",它是在 iOS 中使用形状图层和路径的核心:
4。在 iOS 中,您实际上在 layoutSubviews 中创建了形状图层的路径。
根据我的经验,这让从其他平台迁移到 iOS 的程序员感到困惑,但事情就是这样。
所以让我们创建一个计算变量来为我们创建路径。
请注意,您一定会使用方便的 inset(by: UIEdgeInsets( ...
调用。
不要忘记,在调用此函数时,您应该已经正确设置所有涉及的帧。
最后,Apple 好心地给我们提供了非常方便的 UIBezierPath#ovalIn
调用,这样您就不需要用弧线和 Pi 来愚弄了!
就是这么简单...
var circlePathForCurrentLayout: UIBezierPath {
var b = bounds
b = b.inset(by: UIEdgeInsets(top: halfT, left: halfT, bottom: halfT, right: halfT))
let p = UIBezierPath(ovalIn: b)
return p
}
然后,正如粗体字所示,在 iOS 中,您出人意料地设置了形状图层的实际路径,在 layoutSubviews.
override func layoutSubviews() {
super.layoutSubviews()
outerBand.frame = bounds
outerBand.lineWidth = thicknessOfOuterRing
outerBand.path = circlePathForCurrentLayout.cgPath
centralBlob.frame =
bounds.inset(by: UIEdgeInsets(top: both, left: both, bottom: both, right: both))
centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
}
回顾:
1。一如既往,制作图层,并在 layoutSubviews 中设置它们的框架。
2。设置图层的所有 "fixed" 方面,制作时。
3。设置图层的所有 "changing" 方面,in layoutSubviews.
4。令人惊讶的是,在 iOS 中,您实际上在 layoutSubviews.
中创建了形状图层的 paths下面是所有内容:
// Thingy.swift
// Created for SO on 11/3/19.
import UIKit
class Thingy: UIView {
var thicknessOfOuterRing: CGFloat = 20.0 {
didSet{ setNeedsLayout() }
}
var thicknessOfTheGap: CGFloat = 20.0 {
didSet{ setNeedsLayout() }
}
var halfT: CGFloat { return thicknessOfOuterRing / 2.0 }
var both: CGFloat { return thicknessOfOuterRing + thicknessOfTheGap }
var circlePathForCurrentLayout: UIBezierPath {
var b = bounds
b = b.inset(by:
UIEdgeInsets(top: halfT, left: halfT, bottom: halfT, right: halfT))
let p = UIBezierPath(ovalIn: b)
return p
}
private lazy var outerBand: CAShapeLayer = {
let l = CAShapeLayer()
l.fillColor = UIColor.clear.cgColor
l.strokeColor = UIColor.black.cgColor
layer.addSublayer(l)
return l
}()
private lazy var centralBlob: CALayer = {
let l = CALayer()
l.backgroundColor = UIColor.black.cgColor
layer.addSublayer(l)
return l
}()
override func layoutSubviews() {
super.layoutSubviews()
outerBand.frame = bounds
outerBand.lineWidth = thicknessOfOuterRing
outerBand.path = circlePathForCurrentLayout.cgPath
centralBlob.frame = bounds.inset(by:
UIEdgeInsets(top: both, left: both, bottom: both, right: both))
centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
}
}
后续步骤:
如果你想接受在中间放置 图像 的挑战:link
如果你想,呃,接受阴影:
查看渐变?
部分圆弧: