使用UIBezierPath绘制图形曲线

Abi*_*ain 15 iphone core-graphics objective-c ios uibezierpath

我正在申请中绘制图表.我的问题是我想画线连接顶点作为曲线.目前我正在绘制它们UIBezierPath的功能addLineToPoint:.我想把它们画成曲线.我很清楚,UIBezierPath有以下两个功能来支持此功能.

立方曲线:addCurveToPoint:controlPoint1:controlPoint2: 二次曲线:addQuadCurveToPoint:controlPoint:

但问题是我没有控制点.我只有两个终点.我都没有找到确定控制点的方法/公式.有人能帮我一下吗?如果有人能提出一些替代方案我会很感激......

use*_*109 22

在12月5日的12月5日扩展Abid Hussain的回答.

我实现了代码,它工作但结果看起来像这样: 在此输入图像描述

经过很少的调整,我能够得到我想要的东西: 在此输入图像描述

我所做的就是将addQuadCurveToPoint添加到中点,使用中中点作为控制点.以下是基于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;
}
Run Code Online (Sandbox Code Playgroud)


Rom*_*pov 14

使用@ user1244109的答案我在Swift 3中实现了一个更好的算法. 我的实施图表

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)
    }
}
Run Code Online (Sandbox Code Playgroud)


Fog*_*ter 12

如果你只有两个点,那么你无法将它们真正推断成曲线.

如果您有两个以上的点(即曲线上的几个点),那么您可以大致绘制一条曲线来跟随它们.

如果你做以下......

好吧,说你有5分.p1,p2,p3,p4和p5.你需要弄清楚每对点之间的中点.m1 = p1和p2的中点(依此类推)......

所以你有m1,m2,m3和m4.

现在,您可以使用中点作为曲线截面的终点,将点作为四边形曲线的控制点...

所以...

移至m1点.将四边形曲线添加到点m2,以p2作为控制点.

将四边形曲线添加到m3点,以p3作为控制点.

将四边形曲线添加到点m4,将p4作为控制点.

等等...

这将使你们从曲线的两端分开(不记得此刻如何获得它们,对不起).


Abi*_*ain 8

所以我找到了一个基于@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];
Run Code Online (Sandbox Code Playgroud)


小智 5

我已经做了一些工作,并从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];
 }
Run Code Online (Sandbox Code Playgroud)

我在这里使用https://github.com/johnyorke/JYGraphViewController