ios 使用 UIBezierPath 绘制图形曲线
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13719143/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Draw Graph curves with UIBezierPath
提问by Abid Hussain
I'm drawing a graph in my application. My problem is that I want to draw line joining vertex points as curves. Currently I'm drawing them with UIBezierPath
's function addLineToPoint:
. I want to draw them as curves. I'm well aware that UIBezierPath
has following two functions to support this feature.
我正在我的应用程序中绘制图形。我的问题是我想绘制连接顶点的线作为曲线。目前我正在用UIBezierPath
's function绘制它们addLineToPoint:
。我想把它们画成曲线。我很清楚它UIBezierPath
有以下两个功能来支持此功能。
Cubic curve:addCurveToPoint:controlPoint1:controlPoint2:
Quadratic curve:addQuadCurveToPoint:controlPoint:
三次曲线:addCurveToPoint:controlPoint1:controlPoint2:
二次曲线:addQuadCurveToPoint:controlPoint:
But problem is that I don't have control points. All i have is two end points. Neither I found a method/formula to determine control points. Can anyone help me here? I will appreciate if someone can suggest some alternative...
但问题是我没有控制点。我只有两个端点。我也没有找到确定控制点的方法/公式。有人能帮我一下吗?如果有人可以提出一些替代方案,我将不胜感激...
采纳答案by Abid Hussain
SO I found a work around based on @Fogmeister's answer.
所以我根据@Fogmeister 的回答找到了一个解决方法。
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineWidth:3.0];
[path setLineCapStyle:kCGLineCapRound];
[path setLineJoinStyle:kCGLineJoinRound];
// actualPoints are my points array stored as NSValue
NSValue *value = [actualPoints objectAtIndex:0];
CGPoint p1 = [value CGPointValue];
[path moveToPoint:p1];
for (int k=1; k<[actualPoints count];k++) {
NSValue *value = [actualPoints objectAtIndex:k];
CGPoint p2 = [value CGPointValue];
CGPoint centerPoint = CGPointMake((p1.x+p2.x)/2, (p1.y+p2.y)/2);
// See if your curve is decreasing or increasing
// You can optimize it further by finding point on normal of line passing through midpoint
if (p1.y<p2.y) {
centerPoint = CGPointMake(centerPoint.x, centerPoint.y+(abs(p2.y-centerPoint.y)));
}else if(p1.y>p2.y){
centerPoint = CGPointMake(centerPoint.x, centerPoint.y-(abs(p2.y-centerPoint.y)));
}
[path addQuadCurveToPoint:p2 controlPoint:centerPoint];
p1 = p2;
}
[path stroke];
回答by user1244109
Expand on Abid Hussain's answer on Dec 5 '12.
扩展 Abid Hussain 于 12 年 12 月 5 日的回答。
i implemeted the code, and it worked but result looked like that:
我实现了代码,它起作用了,但结果看起来像这样:
With little adjustment, i was able to get what i wanted:
稍加调整,我就能得到我想要的:
What i did was addQuadCurveToPoint to mid points as well, using mid-mid points as control points. Below is the code based on Abid's sample; hope it helps someone.
我所做的是将QuadCurveToPoint 也添加到中点,使用中点作为控制点。以下是基于 Abid 示例的代码;希望它可以帮助某人。
+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points
{
UIBezierPath *path = [UIBezierPath bezierPath];
NSValue *value = points[0];
CGPoint p1 = [value CGPointValue];
[path moveToPoint:p1];
if (points.count == 2) {
value = points[1];
CGPoint p2 = [value CGPointValue];
[path addLineToPoint:p2];
return path;
}
for (NSUInteger i = 1; i < points.count; i++) {
value = points[i];
CGPoint p2 = [value CGPointValue];
CGPoint midPoint = midPointForPoints(p1, p2);
[path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)];
[path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)];
p1 = p2;
}
return path;
}
static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) {
CGPoint controlPoint = midPointForPoints(p1, p2);
CGFloat diffY = abs(p2.y - controlPoint.y);
if (p1.y < p2.y)
controlPoint.y += diffY;
else if (p1.y > p2.y)
controlPoint.y -= diffY;
return controlPoint;
}
回答by Roman Filippov
Using @user1244109's answer I implement a better algorithm in Swift 5.
使用@user1244109 的答案,我在 Swift 5 中实现了更好的算法。
class GraphView: UIView {
var data: [CGFloat] = [2, 6, 12, 4, 5, 7, 5, 6, 6, 3] {
didSet {
setNeedsDisplay()
}
}
func coordYFor(index: Int) -> CGFloat {
return bounds.height - bounds.height * data[index] / (data.max() ?? 0)
}
override func draw(_ rect: CGRect) {
let path = quadCurvedPath()
UIColor.black.setStroke()
path.lineWidth = 1
path.stroke()
}
func quadCurvedPath() -> UIBezierPath {
let path = UIBezierPath()
let step = bounds.width / CGFloat(data.count - 1)
var p1 = CGPoint(x: 0, y: coordYFor(index: 0))
path.move(to: p1)
drawPoint(point: p1, color: UIColor.red, radius: 3)
if (data.count == 2) {
path.addLine(to: CGPoint(x: step, y: coordYFor(index: 1)))
return path
}
var oldControlP: CGPoint?
for i in 1..<data.count {
let p2 = CGPoint(x: step * CGFloat(i), y: coordYFor(index: i))
drawPoint(point: p2, color: UIColor.red, radius: 3)
var p3: CGPoint?
if i < data.count - 1 {
p3 = CGPoint(x: step * CGFloat(i + 1), y: coordYFor(index: i + 1))
}
let newControlP = controlPointForPoints(p1: p1, p2: p2, next: p3)
path.addCurve(to: p2, controlPoint1: oldControlP ?? p1, controlPoint2: newControlP ?? p2)
p1 = p2
oldControlP = antipodalFor(point: newControlP, center: p2)
}
return path;
}
/// located on the opposite side from the center point
func antipodalFor(point: CGPoint?, center: CGPoint?) -> CGPoint? {
guard let p1 = point, let center = center else {
return nil
}
let newX = 2 * center.x - p1.x
let diffY = abs(p1.y - center.y)
let newY = center.y + diffY * (p1.y < center.y ? 1 : -1)
return CGPoint(x: newX, y: newY)
}
/// halfway of two points
func midPointForPoints(p1: CGPoint, p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2);
}
/// Find controlPoint2 for addCurve
/// - Parameters:
/// - p1: first point of curve
/// - p2: second point of curve whose control point we are looking for
/// - next: predicted next point which will use antipodal control point for finded
func controlPointForPoints(p1: CGPoint, p2: CGPoint, next p3: CGPoint?) -> CGPoint? {
guard let p3 = p3 else {
return nil
}
let leftMidPoint = midPointForPoints(p1: p1, p2: p2)
let rightMidPoint = midPointForPoints(p1: p2, p2: p3)
var controlPoint = midPointForPoints(p1: leftMidPoint, p2: antipodalFor(point: rightMidPoint, center: p2)!)
if p1.y.between(a: p2.y, b: controlPoint.y) {
controlPoint.y = p1.y
} else if p2.y.between(a: p1.y, b: controlPoint.y) {
controlPoint.y = p2.y
}
let imaginContol = antipodalFor(point: controlPoint, center: p2)!
if p2.y.between(a: p3.y, b: imaginContol.y) {
controlPoint.y = p2.y
}
if p3.y.between(a: p2.y, b: imaginContol.y) {
let diffY = abs(p2.y - p3.y)
controlPoint.y = p2.y + diffY * (p3.y < p2.y ? 1 : -1)
}
// make lines easier
controlPoint.x += (p2.x - p1.x) * 0.1
return controlPoint
}
func drawPoint(point: CGPoint, color: UIColor, radius: CGFloat) {
let ovalPath = UIBezierPath(ovalIn: CGRect(x: point.x - radius, y: point.y - radius, width: radius * 2, height: radius * 2))
color.setFill()
ovalPath.fill()
}
}
extension CGFloat {
func between(a: CGFloat, b: CGFloat) -> Bool {
return self >= Swift.min(a, b) && self <= Swift.max(a, b)
}
}
回答by Fogmeister
If you only have two points then you cannot really extrapolate them out into a curve.
如果你只有两个点,那么你就不能真正将它们外推成一条曲线。
If you have more than two points (i.e. several points along a curve) then you can roughly draw a curve to follow them.
如果您有两个以上的点(即沿曲线的几个点),那么您可以粗略地绘制一条曲线来跟随它们。
If you do the following...
如果您执行以下操作...
OK, say you have 5 points. p1, p2, p3, p4, and p5. You need to work out the midpoint between each pair of points. m1 = midpoint of p1 and p2 (and so on)...
好吧,假设你有 5 分。p1、p2、p3、p4 和 p5。您需要计算出每对点之间的中点。m1 = p1 和 p2(依此类推)的中点...
So you have m1, m2, m3, and m4.
所以你有 m1、m2、m3 和 m4。
Now you can use the mid points as the end points of the sections of the curve and the points as the control points for a quad curve...
现在您可以将中点用作曲线各部分的端点,将这些点用作四边形曲线的控制点...
So...
所以...
Move to point m1. Add quad curve to point m2 with p2 as a control point.
移动到点 m1。以 p2 作为控制点,将四边形曲线添加到点 m2。
Add quad curve to point m3 with p3 as a control point.
以 p3 作为控制点,将四边形曲线添加到点 m3。
Add quad curve to point m4 with p4 as a control point.
以 p4 作为控制点,将四边形曲线添加到点 m4。
and so on...
等等...
This will get you all apart from the ends of the curve (can't remember how to get them at the moment, sorry).
这将使你们远离曲线的末端(现在不记得如何得到它们,抱歉)。
回答by johnyorke
I've done a bit of work on this and get pretty good results from the Catmull-Rom spline. Here is a method that takes an array of CGPoints (stored as NSValues) and adds the line to the given view.
我在这方面做了一些工作,并从 Catmull-Rom 样条曲线中获得了相当不错的结果。这是一个获取 CGPoints 数组(存储为 NSValues)并将该行添加到给定视图的方法。
- (void)addBezierPathBetweenPoints:(NSArray *)points
toView:(UIView *)view
withColor:(UIColor *)color
andStrokeWidth:(NSUInteger)strokeWidth
{
UIBezierPath *path = [UIBezierPath bezierPath];
float granularity = 100;
[path moveToPoint:[[points firstObject] CGPointValue]];
for (int index = 1; index < points.count - 2 ; index++) {
CGPoint point0 = [[points objectAtIndex:index - 1] CGPointValue];
CGPoint point1 = [[points objectAtIndex:index] CGPointValue];
CGPoint point2 = [[points objectAtIndex:index + 1] CGPointValue];
CGPoint point3 = [[points objectAtIndex:index + 2] CGPointValue];
for (int i = 1; i < granularity ; i++) {
float t = (float) i * (1.0f / (float) granularity);
float tt = t * t;
float ttt = tt * t;
CGPoint pi;
pi.x = 0.5 * (2*point1.x+(point2.x-point0.x)*t + (2*point0.x-5*point1.x+4*point2.x-point3.x)*tt + (3*point1.x-point0.x-3*point2.x+point3.x)*ttt);
pi.y = 0.5 * (2*point1.y+(point2.y-point0.y)*t + (2*point0.y-5*point1.y+4*point2.y-point3.y)*tt + (3*point1.y-point0.y-3*point2.y+point3.y)*ttt);
if (pi.y > view.frame.size.height) {
pi.y = view.frame.size.height;
}
else if (pi.y < 0){
pi.y = 0;
}
if (pi.x > point0.x) {
[path addLineToPoint:pi];
}
}
[path addLineToPoint:point2];
}
[path addLineToPoint:[[points objectAtIndex:[points count] - 1] CGPointValue]];
CAShapeLayer *shapeView = [[CAShapeLayer alloc] init];
shapeView.path = [path CGPath];
shapeView.strokeColor = color.CGColor;
shapeView.fillColor = [UIColor clearColor].CGColor;
shapeView.lineWidth = strokeWidth;
[shapeView setLineCap:kCALineCapRound];
[view.layer addSublayer:shapeView];
}
I use it here https://github.com/johnyorke/JYGraphViewController
回答by ade177
A good solution would be to create a straight UIBezierPath
through your points and then use a spline to curve the line. Check out this other answer which gives a category for UIBezierPath that performs a Catmull-Rom Spline. Drawing Smooth Curves - Methods Needed
一个好的解决方案是UIBezierPath
通过您的点创建一条直线,然后使用样条曲线来弯曲线。查看这个其他答案,它为执行 Catmull-Rom 样条的 UIBezierPath 提供了一个类别。绘制平滑曲线 - 需要的方法
回答by aBikis
here's @user1244109 's answer converted to Swift 3
这是@user1244109 的答案转换为 Swift 3
private func quadCurvedPath(with points:[CGPoint]) -> UIBezierPath {
let path = UIBezierPath()
var p1 = points[0]
path.move(to: p1)
if points.count == 2 {
path.addLine(to: points[1])
return path
}
for i in 1..<points.count {
let mid = midPoint(for: (p1, points[i]))
path.addQuadCurve(to: mid,
controlPoint: controlPoint(for: (mid, p1)))
path.addQuadCurve(to: points[i],
controlPoint: controlPoint(for: (mid, points[i])))
p1 = points[i]
}
return path
}
private func midPoint(for points: (CGPoint, CGPoint)) -> CGPoint {
return CGPoint(x: (points.0.x + points.1.x) / 2 , y: (points.0.y + points.1.y) / 2)
}
private func controlPoint(for points: (CGPoint, CGPoint)) -> CGPoint {
var controlPoint = midPoint(for: points)
let diffY = abs(points.1.y - controlPoint.y)
if points.0.y < points.1.y {
controlPoint.y += diffY
} else if points.0.y > points.1.y {
controlPoint.y -= diffY
}
return controlPoint
}
回答by mackworth
While analyzing the code from @roman-filippov, I simplified the code some. Here's a complete Swift playground version, and an ObjC version below that.
在分析@roman-filippov 的代码时,我简化了一些代码。这是一个完整的 Swift Playground 版本,以及低于它的 ObjC 版本。
I found that if x increments are not regular, then the original algorithm creates some unfortunate retrograde lines when points are close together on the x axis. Simply constraining the control points to not exceed the following x-value seems to fix the problem, although I have no mathematical justification for this, just experimentation. There are two sections marked as //**added` which implement this change.
我发现如果 x 增量不规则,那么当点在 x 轴上靠得很近时,原始算法会创建一些不幸的逆行线。简单地将控制点限制为不超过以下 x 值似乎可以解决问题,尽管我对此没有数学依据,只是实验。有两个部分标记为 //** added` 实现了此更改。
import UIKit
import PlaygroundSupport
infix operator °
func °(x: CGFloat, y: CGFloat) -> CGPoint {
return CGPoint(x: x, y: y)
}
extension UIBezierPath {
func drawPoint(point: CGPoint, color: UIColor, radius: CGFloat) {
let ovalPath = UIBezierPath(ovalIn: CGRect(x: point.x - radius, y: point.y - radius, width: radius * 2, height: radius * 2))
color.setFill()
ovalPath.fill()
}
func drawWithLine (point: CGPoint, color: UIColor) {
let startP = self.currentPoint
self.addLine(to: point)
drawPoint(point: point, color: color, radius: 3)
self.move(to: startP)
}
}
class TestView : UIView {
var step: CGFloat = 1.0;
var yMaximum: CGFloat = 1.0
var xMaximum: CGFloat = 1.0
var data: [CGPoint] = [] {
didSet {
xMaximum = data.reduce(-CGFloat.greatestFiniteMagnitude, { max(+ (UIBezierPath *)pathWithPoints:(NSArray <NSValue *> *)points open:(BOOL) open {
//based on Roman Filippov code: http://stackoverflow.com/a/40203583/580850
//open means allow gaps in path.
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint p1 = [points[0] CGPointValue];
[path moveToPoint:p1];
CGPoint oldControlPoint = p1;
for (NSUInteger pointIndex = 1; pointIndex< points.count; pointIndex++) {
CGPoint p2 = [points[pointIndex] CGPointValue]; //note: mark missing data with CGFloatMax
if (p1.y >= CGFloatMax || p2.y >= CGFloatMax) {
if (open) {
[path moveToPoint:p2];
} else {
[path addLineToPoint:p2];
}
oldControlPoint = p2;
} else {
CGPoint p3 = CGPointZero;
if (pointIndex +1 < points.count) p3 = [points[pointIndex+1] CGPointValue] ;
if (p3.y >= CGFloatMax) p3 = CGPointZero;
CGPoint newControlPoint = controlPointForPoints2(p1, p2, p3);
if (!CGPointEqualToPoint( newControlPoint, CGPointZero)) {
[path addCurveToPoint: p2 controlPoint1:oldControlPoint controlPoint2: newControlPoint];
oldControlPoint = imaginForPoints( newControlPoint, p2);
//**added to algorithm
if (! CGPointEqualToPoint(p3,CGPointZero)) {
if (oldControlPoint.x > p3.x ) {
oldControlPoint.x = p3.x;
}
//***
} else {
[path addCurveToPoint: p2 controlPoint1:oldControlPoint controlPoint2: p2];
oldControlPoint = p2;
}
}
p1 = p2;
}
return path;
}
static CGPoint imaginForPoints(CGPoint point, CGPoint center) {
//returns "mirror image" of point: the point that is symmetrical through center.
if (CGPointEqualToPoint(point, CGPointZero) || CGPointEqualToPoint(center, CGPointZero)) {
return CGPointZero;
}
CGFloat newX = center.x + (center.x-point.x);
CGFloat newY = center.y + (center.y-point.y);
if (isinf(newY)) {
newY = BEMNullGraphValue;
}
return CGPointMake(newX,newY);
}
static CGFloat clamp(CGFloat num, CGFloat bounds1, CGFloat bounds2) {
//ensure num is between bounds.
if (bounds1 < bounds2) {
return MIN(MAX(bounds1,num),bounds2);
} else {
return MIN(MAX(bounds2,num),bounds1);
}
}
static CGPoint controlPointForPoints2(CGPoint p1, CGPoint p2, CGPoint p3) {
if (CGPointEqualToPoint(p3, CGPointZero)) return CGPointZero;
CGPoint leftMidPoint = midPointForPoints(p1, p2);
CGPoint rightMidPoint = midPointForPoints(p2, p3);
CGPoint imaginPoint = imaginForPoints(rightMidPoint, p2);
CGPoint controlPoint = midPointForPoints(leftMidPoint, imaginPoint);
controlPoint.y = clamp(controlPoint.y, p1.y, p2.y);
CGFloat flippedP3 = p2.y + (p2.y-p3.y);
controlPoint.y = clamp(controlPoint.y, p2.y, flippedP3);
//**added to algorithm
controlPoint.x = clamp(controlPoint.x, p1.x, p2.x);
//**
return controlPoint;
}
, .x) })
yMaximum = data.reduce(-CGFloat.greatestFiniteMagnitude, { max(let path = createCurve(from: points, withSmoothness: 0.5) // 0.5 smoothness
, .y) })
setNeedsDisplay()
}
}
func scale(point: CGPoint) -> CGPoint {
return CGPoint(x: bounds.width * point.x / xMaximum ,
y: (bounds.height - bounds.height * point.y / yMaximum))
}
override func draw(_ rect: CGRect) {
if data.count <= 1 {
return
}
let path = cubicCurvedPath()
UIColor.black.setStroke()
path.lineWidth = 1
path.stroke()
}
func cubicCurvedPath() -> UIBezierPath {
let path = UIBezierPath()
var p1 = scale(point: data[0])
path.drawPoint(point: p1, color: UIColor.red, radius: 3)
path.move(to: p1)
var oldControlP = p1
for i in 0..<data.count {
let p2 = scale(point:data[i])
path.drawPoint(point: p2, color: UIColor.red, radius: 3)
var p3: CGPoint? = nil
if i < data.count - 1 {
p3 = scale(point:data [i+1])
}
let newControlP = controlPointForPoints(p1: p1, p2: p2, p3: p3)
//uncomment the following four lines to graph control points
//if let controlP = newControlP {
// path.drawWithLine(point:controlP, color: UIColor.blue)
//}
//path.drawWithLine(point:oldControlP, color: UIColor.gray)
path.addCurve(to: p2, controlPoint1: oldControlP , controlPoint2: newControlP ?? p2)
oldControlP = imaginFor(point1: newControlP, center: p2) ?? p1
//***added to algorithm
if let p3 = p3 {
if oldControlP.x > p3.x { oldControlP.x = p3.x }
}
//***
p1 = p2
}
return path;
}
func imaginFor(point1: CGPoint?, center: CGPoint?) -> CGPoint? {
//returns "mirror image" of point: the point that is symmetrical through center.
//aka opposite of midpoint; returns the point whose midpoint with point1 is center)
guard let p1 = point1, let center = center else {
return nil
}
let newX = center.x + center.x - p1.x
let newY = center.y + center.y - p1.y
return CGPoint(x: newX, y: newY)
}
func midPointForPoints(p1: CGPoint, p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2);
}
func clamp(num: CGFloat, bounds1: CGFloat, bounds2: CGFloat) -> CGFloat {
//ensure num is between bounds.
if (bounds1 < bounds2) {
return min(max(bounds1,num),bounds2);
} else {
return min(max(bounds2,num),bounds1);
}
}
func controlPointForPoints(p1: CGPoint, p2: CGPoint, p3: CGPoint?) -> CGPoint? {
guard let p3 = p3 else {
return nil
}
let leftMidPoint = midPointForPoints(p1: p1, p2: p2)
let rightMidPoint = midPointForPoints(p1: p2, p2: p3)
let imaginPoint = imaginFor(point1: rightMidPoint, center: p2)
var controlPoint = midPointForPoints(p1: leftMidPoint, p2: imaginPoint!)
controlPoint.y = clamp(num: controlPoint.y, bounds1: p1.y, bounds2: p2.y)
let flippedP3 = p2.y + (p2.y-p3.y)
controlPoint.y = clamp(num: controlPoint.y, bounds1: p2.y, bounds2: flippedP3);
//***added:
controlPoint.x = clamp (num:controlPoint.x, bounds1: p1.x, bounds2: p2.x)
//***
// print ("p1: \(p1), p2: \(p2), p3: \(p3), LM:\(leftMidPoint), RM:\(rightMidPoint), IP:\(imaginPoint), fP3:\(flippedP3), CP:\(controlPoint)")
return controlPoint
}
}
let u = TestView(frame: CGRect(x: 0, y: 0, width: 700, height: 600));
u.backgroundColor = UIColor.white
PlaygroundPage.current.liveView = u
u.data = [0.5 ° 1, 1 ° 3, 2 ° 5, 4 ° 9, 8 ° 15, 9.4 ° 8, 9.5 ° 10, 12 ° 4, 13 ° 10, 15 ° 3, 16 ° 1]
And the same code in ObjC (more or less. this doesn't include the drawing routines themselves, and it does allow the points array to include missing data
和 ObjC 中的相同代码(或多或少。这不包括绘图程序本身,它确实允许点数组包含丢失的数据
let path = createCurve(from: points, withSmoothness: 0) // 0 smoothness
回答by Alexander Volkov
If you need "horizontal" curve near every point:
如果您需要每个点附近的“水平”曲线:
/// Create UIBezierPath
///
/// - Parameters:
/// - points: the points
/// - smoothness: the smoothness: 0 - no smooth at all, 1 - maximum smoothness
private func createCurve(from points: [CGPoint], withSmoothness smoothness: CGFloat, addZeros: Bool = false) -> UIBezierPath {
let path = UIBezierPath()
guard points.count > 0 else { return path }
var prevPoint: CGPoint = points.first!
let interval = getXLineInterval()
if addZeros {
path.move(to: CGPoint(x: interval.origin.x, y: interval.origin.y))
path.addLine(to: points[0])
}
else {
path.move(to: points[0])
}
for i in 1..<points.count {
let cp = controlPoints(p1: prevPoint, p2: points[i], smoothness: smoothness)
path.addCurve(to: points[i], controlPoint1: cp.0, controlPoint2: cp.1)
prevPoint = points[i]
}
if addZeros {
path.addLine(to: CGPoint(x: prevPoint.x, y: interval.origin.y))
}
return path
}
/// Create control points with given smoothness
///
/// - Parameters:
/// - p1: the first point
/// - p2: the second point
/// - smoothness: the smoothness: 0 - no smooth at all, 1 - maximum smoothness
/// - Returns: two control points
private func controlPoints(p1: CGPoint, p2: CGPoint, smoothness: CGFloat) -> (CGPoint, CGPoint) {
let cp1: CGPoint!
let cp2: CGPoint!
let percent = min(1, max(0, smoothness))
do {
var cp = p2
// Apply smoothness
let x0 = max(p1.x, p2.x)
let x1 = min(p1.x, p2.x)
let x = x0 + (x1 - x0) * percent
cp.x = x
cp2 = cp
}
do {
var cp = p1
// Apply smoothness
let x0 = min(p1.x, p2.x)
let x1 = max(p1.x, p2.x)
let x = x0 + (x1 - x0) * percent
cp.x = x
cp1 = cp
}
return (cp1, cp2)
}
/// Defines interval width, height (not used in this example) and coordinate of the first interval.
/// - Returns: (x0, y0, step, height)
internal func getXLineInterval() -> CGRect {
return CGRect.zero
}
##代码##
##代码##