背景
iOS开发中,经常会遇到要绘制一些图形,比如矩形,圆形,椭圆,弧或者不规则的多边形,这时我们可以通过重写UIView的draw(_ rect: CGRect)方法,然后通过UIBezierPath来绘制。
UIBezierPath这个类在UIKit中, 是Core Graphics框架关于path的一个封装。
关于iOS绘图原理,可以查看博主的这篇文章 iOS底层原理之 UIView绘制显示原理流程解析以及性能优化
基本属性和方法说明
1 UIColor.red.set()
设置线条颜色,也就是画笔颜色。
2 lineWidth
线宽
3 lineCapStyle
线帽样式,是CGLineCap类型的枚举,定义如下
public enum CGLineCap : Int32, @unchecked Sendable {
/// 指定不绘制端点,即线条结尾处直接结束。
case butt = 0
/// 绘制圆形端点,即线条结尾处绘制一个直径为线条宽度的半圆
case round = 1
/// 绘制方形端点,即线条结尾处绘制半个边长为线条宽度的正方形。
/// 需要说明的是,这种形状的端点与“butt”形状的端点十分相似,只是采用这种形式的端点的线条略长一点而已
case square = 2
}
效果如下,顺序和枚举顺序一一对应
4 lineJoinStyle
线连接样式,即拐角样式,是CGLineJoin类型的枚举,定义如下
public enum CGLineJoin : Int32, @unchecked Sendable {
/// 尖角
case miter = 0
/// 圆角
case round = 1
/// 缺角
case bevel = 2
}
效果如下,顺序和枚举顺序一一对应
5 stroke()
stroke 得到的是不被填充的view,即只会绘制曲线路线。
6 fill()
fill 得到的内部被填充的 view,即会将曲线形成的几何图形,填充满画笔颜色。
绘制矩形
通过init(rect: CGRect)方法绘制,代码如下
override func draw(_ rect: CGRect) {
UIColor.red.set()
let path1 = UIBezierPath(rect: CGRect(x: 100, y: 100, width: 200, height: 100))
path1.lineWidth = 5.0
path1.lineCapStyle = .round
path1.lineJoinStyle = .round
path1.stroke()
let path2 = UIBezierPath(rect: CGRect(x: 100, y: 230, width: 200, height: 200))
path2.lineWidth = 5.0
path2.lineCapStyle = .round
path2.lineJoinStyle = .round
path2.fill()
}
绘制带圆角的矩形
有两种方式,如果四个角都需要圆角,可以通过init(roundedRect rect: CGRect, cornerRadius: CGFloat)方法绘制。
如果只需要某些圆角,则可以通过init(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize)方法绘制。
代码如下
override func draw(_ rect: CGRect) {
UIColor.red.set()
let path1 = UIBezierPath(roundedRect: CGRect(x: 100, y: 100, width: 200, height: 100), cornerRadius: 10)
path1.lineWidth = 5.0
path1.lineCapStyle = .round
path1.lineJoinStyle = .round
path1.stroke()
let path2 = UIBezierPath(roundedRect: CGRect(x: 100, y: 230, width: 200, height: 200), byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 10, height: 10))
path2.lineWidth = 5.0
path2.lineCapStyle = .round
path2.lineJoinStyle = .round
path2.fill()
}
绘制多边形
通过move(to point: CGPoint)和addLine(to point: CGPoint)方法绘制,moveToPoint:这个方法是设置起始点,意味着从这个点开始。addLineToPoint:设置想要创建的多边形经过的点,也就是两线相交的那个点,可以连续创建line,每一个line的起点都是先前的终点,终点就是指定的点,将线段连接起来就是我们想要创建的多边形了。最后第五条线是用path.close()得到的,closePath方法不仅结束一个shape的subpath表述,它也在最后一个点和第一个点之间画一条线段,这是一个便利的方法,我们不需要去画最后一条线了。代码如下
override func draw(_ rect: CGRect) {
UIColor.red.set()
let path = UIBezierPath()
path.lineWidth = 5.0
path.lineCapStyle = .round
path.lineJoinStyle = .round
path.move(to: CGPoint(x: 100, y: 300))
path.addLine(to: CGPoint(x: 200, y: 200))
path.addLine(to: CGPoint(x: 300, y: 300))
path.addLine(to: CGPoint(x: 260, y: 400))
path.addLine(to: CGPoint(x: 140, y: 400))
path.close()
path.fill()
}
绘制椭圆
通过init(ovalIn rect: CGRect)方法绘制,如果传入的rect是一个矩形,则得到矩形的内切椭圆,如果传入的rect是一个正方形,则得到正方形的内切圆。代码如下
override func draw(_ rect: CGRect) {
UIColor.red.set()
let path1 = UIBezierPath(ovalIn: CGRect(x: 100, y: 100, width: 200, height: 100))
path1.lineWidth = 5.0
path1.lineCapStyle = .round
path1.lineJoinStyle = .round
path1.stroke()
let path2 = UIBezierPath(ovalIn: CGRect(x: 100, y: 230, width: 200, height: 200))
path2.lineWidth = 5.0
path2.lineCapStyle = .round
path2.lineJoinStyle = .round
path2.fill()
}
绘制一段弧线
通过init(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)方法绘制,其中arcCenter指圆心坐标,radius指圆的半径,startAngle指圆弧的起始角度,endAngle指圆弧的结束角度,clockwise指是否顺时针方向绘制。其中圆弧的角度参考系如下
代码如下
override func draw(_ rect: CGRect) {
UIColor.red.set()
let path1 = UIBezierPath(arcCenter: CGPoint(x: 200, y: 250), radius: 100, startAngle: 180 / 180 * .pi, endAngle: 45 / 180 * .pi, clockwise: true)
path1.lineWidth = 5.0
path1.lineCapStyle = .round
path1.lineJoinStyle = .round
path1.stroke()
let path2 = UIBezierPath(arcCenter: CGPoint(x: 200, y: 450), radius: 100, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
path2.lineWidth = 5.0
path2.lineCapStyle = .round
path2.lineJoinStyle = .round
path2.fill()
}
绘制二次贝塞尔曲线
通过addQuadCurve(to endPoint: CGPoint, controlPoint: CGPoint)方法绘制,曲线段在当前点开始,在指定的点结束,一个控制点的切线定义,图示如下
代码如下
override func draw(_ rect: CGRect) {
UIColor.red.set()
let path1 = UIBezierPath()
path1.lineWidth = 5.0
path1.lineCapStyle = .round
path1.lineJoinStyle = .round
path1.move(to: CGPoint(x: 100, y: 200))
path1.addQuadCurve(to: CGPoint(x: 300, y: 200), controlPoint: CGPoint(x: 100, y: 100))
path1.stroke()
let path2 = UIBezierPath()
path2.lineWidth = 5.0
path2.lineCapStyle = .round
path2.lineJoinStyle = .round
path2.move(to: CGPoint(x: 100, y: 250))
path2.addQuadCurve(to: CGPoint(x: 300, y: 250), controlPoint: CGPoint(x: 250, y: 400))
path2.fill()
}
绘制三次贝塞尔曲线
通过addCurve(to endPoint: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint)方法绘制,曲线段在当前点开始,在指定的点结束,两个控制点的切线定义,图示如下
代码如下
override func draw(_ rect: CGRect) {
UIColor.red.set()
let path1 = UIBezierPath()
path1.lineWidth = 5.0
path1.lineCapStyle = .round
path1.lineJoinStyle = .round
path1.move(to: CGPoint(x: 100, y: 200))
path1.addCurve(to: CGPoint(x: 300, y: 200), controlPoint1: CGPoint(x: 150, y: 50), controlPoint2: CGPoint(x: 250, y: 350))
path1.stroke()
let path2 = UIBezierPath()
path2.lineWidth = 5.0
path2.lineCapStyle = .round
path2.lineJoinStyle = .round
path2.move(to: CGPoint(x: 100, y: 350))
path2.addCurve(to: CGPoint(x: 300, y: 350), controlPoint1: CGPoint(x: 150, y: 200), controlPoint2: CGPoint(x: 250, y: 500))
path2.fill()
}
绘制虚线
通过setLineDash(_ pattern: UnsafePointer?, count: Int, phase: CGFloat)方法绘制。
其中pattern是C样式的浮点值数组,包含线段和模式中的间隙的长度(以点为单位)。 数组中的值交替,从第一个线段长度开始,后跟第一个间隙长度,后跟第二个线段长度,依此类推。
count是模式中的值的数量,即虚线数组元素个数。
phase是虚线开始的位置,即开始绘制图案的偏移量,沿着虚线图案的点测量。 例如,图案5-2-3-2的相位值phase为6将导致绘图在第一个间隙的中间开始。
代码如下
override func draw(_ rect: CGRect) {
UIColor.red.set()
let path1 = UIBezierPath()
path1.lineWidth = 5.0
path1.lineCapStyle = .round
path1.lineJoinStyle = .round
path1.move(to: CGPoint(x: 50, y: 200))
path1.addLine(to: CGPoint(x: 400, y: 200))
var dashConfig1:[CGFloat] = [10.0, 15.0]
path1.setLineDash(&dashConfig1, count: dashConfig1.count, phase: 0)
path1.stroke()
let path2 = UIBezierPath()
path2.lineWidth = 5.0
path2.lineCapStyle = .round
path2.lineJoinStyle = .round
path2.move(to: CGPoint(x: 50, y: 250))
path2.addLine(to: CGPoint(x: 400, y: 250))
var dashConfig2:[CGFloat] = [10.0, 15.0, 15.0, 25.0]
path2.setLineDash(&dashConfig2, count: dashConfig2.count, phase: 0)
path2.stroke()
}