没有内置的方法来从函数构建路径,但是您可以轻松地自己编写一个。在Swift 3中:
private func path(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGFloat)) -> UIBezierPath {
let numberOfPoints = count ?? Int(rect.size.width)
let path = UIBezierPath()
path.move(to: convert(point: CGPoint(x: 0, y: function(0)), in: rect))
for i in 1 ..< numberOfPoints {
let x = CGFloat(i) / CGFloat(numberOfPoints - 1)
path.addLine(to: convert(point: CGPoint(x: x, y: function(x)), in: rect))
}
return path
}
private func convert(point: CGPoint, in rect: CGRect) -> CGPoint {
return CGPoint(
x: rect.origin.x + point.x * rect.size.width,
y: rect.origin.y + rect.size.height - point.y * rect.size.height
)
}
那么,让我们传递一个函数,它随着 rect
的 width
逐渐进行一个正弦曲线:
func sinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
return path(in: rect, count: count) { (sin($0 * .pi * 2.0) + 1.0) / 2.0 }
}
请注意,上述假设您希望从左到右遍历,构建由函数定义的路径。您还可以进行更多的参数化呈现:
private func parametricPath(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGPoint)) -> UIBezierPath {
let numberOfPoints = count ?? max(Int(rect.size.width), Int(rect.size.height))
let path = UIBezierPath()
let result = function(0)
path.move(to: convert(point: CGPoint(x: result.x, y: result.y), in: rect))
for i in 1 ..< numberOfPoints {
let t = CGFloat(i) / CGFloat(numberOfPoints - 1)
let result = function(t)
path.addLine(to: convert(point: CGPoint(x: result.x, y: result.y), in: rect))
}
return path
}
然后您可以使用正弦曲线修改x
坐标,只需递增y
:
func verticalSinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
return parametricPath(in: rect, count: count) { CGPoint(
x: (sin($0 * .pi * 2.0) + 1.0) / 2.0,
y: $0
) }
}
这样做的好处是,您现在可以定义任何类型的路径,例如螺旋形:
func spiralPath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
return parametricPath(in: rect, count: count) { t in
let r = 1.0 - sin(t * .pi / 2.0)
return CGPoint(
x: (r * sin(t * 10.0 * .pi * 2.0) + 1.0) / 2.0,
y: (r * cos(t * 10.0 * .pi * 2.0) + 1.0) / 2.0
)
}
}
以下是上述内容的Swift 2版本:
private func path(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGFloat)) -> UIBezierPath {
let numberOfPoints = count ?? Int(rect.size.width)
let path = UIBezierPath()
path.moveToPoint(convert(point: CGPoint(x: 0, y: function(0)), rect: rect))
for i in 1 ..< numberOfPoints {
let x = CGFloat(i) / CGFloat(numberOfPoints - 1)
path.addLineToPoint(convert(point: CGPoint(x: x, y: function(x)), rect: rect))
}
return path
}
private func convert(point point: CGPoint, rect: CGRect) -> CGPoint {
return CGPoint(
x: rect.origin.x + point.x * rect.size.width,
y: rect.origin.y + rect.size.height - point.y * rect.size.height
)
}
func sinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
return path(in: rect, count: count) { (sin($0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0 }
}
private func parametricPath(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGPoint)) -> UIBezierPath {
let numberOfPoints = count ?? max(Int(rect.size.width), Int(rect.size.height))
let path = UIBezierPath()
let result = function(0)
path.moveToPoint(convert(point: CGPoint(x: result.x, y: result.y), rect: rect))
for i in 1 ..< numberOfPoints {
let t = CGFloat(i) / CGFloat(numberOfPoints - 1)
let result = function(t)
path.addLineToPoint(convert(point: CGPoint(x: result.x, y: result.y), rect: rect))
}
return path
}
func verticalSinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
return parametricPath(in: rect, count: count) { CGPoint(
x: (sin($0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0,
y: $0
) }
}
func spiralPath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
return parametricPath(in: rect, count: count) { t in
let r = 1.0 - sin(t * CGFloat(M_PI_2))
return CGPoint(
x: (r * sin(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0,
y: (r * cos(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0
)
}
}