UIBezierPath-两个方向箭头与曲线

时间:2017-11-21 03:04:51

标签: objective-c uibezierpath

我正在尝试制作一个有两个侧箭头标记和中间弧形曲线的箭头

但是现在我已经使用了mayOff_gist并且能够在一侧制作单行箭头。

代码 -

//的.h

@interface UIBezierPath (arrow)


+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
                                       toPoint:(CGPoint)endPoint
                                     tailWidth:(CGFloat)tailWidth
                                     headWidth:(CGFloat)headWidth
                                    headLength:(CGFloat)headLength;


@end

//。米

#define kArrowPointCount 7

@implementation UIBezierPath (arrow)


+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
                                       toPoint:(CGPoint)endPoint
                                     tailWidth:(CGFloat)tailWidth
                                     headWidth:(CGFloat)headWidth
                                    headLength:(CGFloat)headLength {
CGFloat length = hypotf(endPoint.x - startPoint.x, endPoint.y - startPoint.y);

CGPoint points[kArrowPointCount];
[self dqd_getAxisAlignedArrowPoints:points
                          forLength:length
                          tailWidth:tailWidth
                          headWidth:headWidth
                         headLength:headLength];

CGAffineTransform transform = [self dqd_transformForStartPoint:startPoint
                                                      endPoint:endPoint
                                                        length:length];

CGMutablePathRef cgPath = CGPathCreateMutable();
CGPathAddLines(cgPath, &transform, points, sizeof points / sizeof *points);
CGPathCloseSubpath(cgPath);

UIBezierPath *uiPath = [UIBezierPath bezierPathWithCGPath:cgPath];
CGPathRelease(cgPath);
return uiPath;
}

+ (void)dqd_getAxisAlignedArrowPoints:(CGPoint[kArrowPointCount])points
                        forLength:(CGFloat)length
                        tailWidth:(CGFloat)tailWidth
                        headWidth:(CGFloat)headWidth
                       headLength:(CGFloat)headLength {
CGFloat tailLength = length - headLength;
points[0] = CGPointMake(0, tailWidth / 2);
points[1] = CGPointMake(tailLength, tailWidth / 2);
points[2] = CGPointMake(tailLength, headWidth / 2);
points[3] = CGPointMake(length, 0);
points[4] = CGPointMake(tailLength, -headWidth / 2);
points[5] = CGPointMake(tailLength, -tailWidth / 2);
points[6] = CGPointMake(0, -tailWidth / 2);
}

+ (CGAffineTransform)dqd_transformForStartPoint:(CGPoint)startPoint
                                   endPoint:(CGPoint)endPoint
                                     length:(CGFloat)length {
CGFloat cosine = (endPoint.x - startPoint.x) / length;
CGFloat sine = (endPoint.y - startPoint.y) / length;
return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y };
}

以上代码给出了输出  mark

1 个答案:

答案 0 :(得分:4)

在弯曲路径上绘制箭头的基本思想是找出曲线末端的切线(例如,点与其控制点之间的斜率),然后弄清楚如何偏移箭头。例如,请将此扩展名考虑为UIBezierPath

@interface UIBezierPath (ArrowHead)

/**
 Function to add an arrow from the currentPoint.

 @param point         The point of the arrow
 @param controlPoint  The point from which the arrow is aligned. Typically, this will be the
                      control point for the previous quad/cubic bezier, or if dealing with a
                      line, the starting point.
 @param width         The width of the arrow (distance from the line).
 @param height        The height of the arrow (distance from the start point).
 @param isOpen        Whether the arrowhead is open or closed.
 */

- (void)addArrowFrom:(CGPoint)point
        controlPoint:(CGPoint)controlPoint
               width:(CGFloat)width
              height:(CGFloat)height
              isOpen:(BOOL)isOpen;

@end

@implementation UIBezierPath (ArrowHead)

- (void)addArrowFrom:(CGPoint)point
        controlPoint:(CGPoint)controlPoint
               width:(CGFloat)width
              height:(CGFloat)height
              isOpen:(BOOL)isOpen {
    CGFloat angle = atan2f(point.y - controlPoint.y, point.x - controlPoint.x);
    CGFloat angleAdjustment = atan2f(width, -height);
    CGFloat distance = hypotf(width, height);

    [self moveToPoint:point];
    [self addLineToPoint:[self calculatePointFromPoint:point angle:angle + angleAdjustment distance:distance]]; // to the right
    if (isOpen) [self addLineToPoint:point];                                                                    // move back to the point
    [self addLineToPoint:[self calculatePointFromPoint:point angle:angle - angleAdjustment distance:distance]]; // to the left
    if (isOpen) {
        [self addLineToPoint:point]; // straight ahead
    } else {
        [self closePath];
    }
}

/**
 Private function for calculating a point at a particular angle from some point.

 @param point       The starting point.
 @param angle       The angle from that point.
 @param distance    The distance from that point.
 @return            The resulting CGPoint.
 */

- (CGPoint)calculatePointFromPoint:(CGPoint)point angle:(CGFloat)angle distance:(CGFloat)distance {
    return CGPointMake(point.x + cosf(angle) * distance, point.y + sinf(angle) * distance);
}

@end

然后你可以像这样渲染一条路径:

@interface ViewController ()
@property (nonatomic, strong) NSMutableArray<CAShapeLayer *> *shapeLayers;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.shapeLayers = [NSMutableArray array];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    [self replaceShapeLayers];
}

- (void)replaceShapeLayers {
    // remove any old shape layers

    for (CAShapeLayer *shapeLayer in self.shapeLayers) {
        [shapeLayer removeFromSuperlayer];
    }
    [self.shapeLayers removeAllObjects];

    [self createOpenArrowPath];
}

/**
 Create open arrowhead path

 Note, because it's open arrowheads, we need rounded lineJoin and lineCap.
 And because both the curve and arrow head have no fill, we can use a single path for
 everything.
 */

- (void)createOpenArrowPath {
    // create new curve shape layer

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.strokeColor = [UIColor blueColor].CGColor;
    shapeLayer.lineWidth = 10;
    shapeLayer.lineJoin = kCALineJoinRound;
    shapeLayer.lineCap = kCALineCapRound;

    [self.view.layer addSublayer:shapeLayer];

    [self.shapeLayers addObject:shapeLayer];

    // now configure curve shape layer

    CGPoint start = CGPointMake(self.view.bounds.size.width * 0.1, self.view.bounds.size.height / 2.0);
    CGPoint end = CGPointMake(self.view.bounds.size.width * 0.9, self.view.bounds.size.height / 2.0);
    CGPoint controlPoint = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height / 3.0);

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path addArrowFrom:start controlPoint:controlPoint width:30 height:50 isOpen:true];
    [path addQuadCurveToPoint:end controlPoint:controlPoint];
    [path addArrowFrom:end controlPoint:controlPoint width:30 height:50 isOpen: true];

    shapeLayer.path = path.CGPath;
}

/**
 Create closed arrowhead path

 Note, because it's closed arrowheads, we need separate paths for the curve (rounded endpoints, no fill)
 and the arrowheads (mitred joins, but with fill).
 */

- (void)createClosedArrowPath {
    // create new curve shape layer (with no fill and rounded corners)

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.strokeColor = [UIColor blueColor].CGColor;
    shapeLayer.lineWidth = 10;
    shapeLayer.lineJoin = kCALineJoinRound;
    shapeLayer.lineCap = kCALineCapRound;

    [self.view.layer addSublayer:shapeLayer];

    [self.shapeLayers addObject:shapeLayer];

    // now configure curve shape layer

    CGPoint start = CGPointMake(self.view.bounds.size.width * 0.1, self.view.bounds.size.height / 2.0);
    CGPoint end = CGPointMake(self.view.bounds.size.width * 0.9, self.view.bounds.size.height / 2.0);
    CGPoint controlPoint = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height / 3.0);

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:start];
    [path addQuadCurveToPoint:end controlPoint:controlPoint];

    shapeLayer.path = path.CGPath;

    // create arrow heads shape layer

    shapeLayer = [CAShapeLayer layer];
    shapeLayer.fillColor = [UIColor blueColor].CGColor;
    shapeLayer.strokeColor = [UIColor blueColor].CGColor;
    shapeLayer.lineWidth = 10;
    shapeLayer.lineJoin = kCALineJoinMiter;
    shapeLayer.lineCap = kCALineCapButt;

    [self.view.layer addSublayer:shapeLayer];

    [self.shapeLayers addObject:shapeLayer];

    // now configure curve shape layer

    path = [UIBezierPath bezierPath];
    [path addArrowFrom:start controlPoint:controlPoint width:30 height:50 isOpen:false];
    [path addArrowFrom:end controlPoint:controlPoint width:30 height:50 isOpen:false];

    shapeLayer.path = path.CGPath;

}

@end

然后产生:

open arrowheads

或者

solid filled arrowheads

如果你想让任何两点动态,只需做一点三角法来找出正确的控制点。例如,这将使用一个控制点,该角度是从起点和终点之间的角度偏移π/ 8的角度:

- (void)createOpenArrowPathFrom:(CGPoint)start to:(CGPoint)end {
    // create new curve shape layer

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.strokeColor = [UIColor blueColor].CGColor;
    shapeLayer.lineWidth = 5;
    shapeLayer.lineJoin = kCALineJoinRound;
    shapeLayer.lineCap = kCALineCapRound;

    [self.view.layer addSublayer:shapeLayer];

    [self.shapeLayers addObject:shapeLayer];

    // now configure curve shape layer

    CGFloat angle = atan2f(end.y - start.y, end.x - start.x);
    CGFloat incrementalAngle = M_PI_4 / 2.0;
    angle -= incrementalAngle;
    CGFloat distance = hypotf(end.y - start.y, end.x - start.x) / 2.0 / cosf(-incrementalAngle);

    CGPoint controlPoint = CGPointMake(start.x + distance * cosf(angle), start.y + distance * sinf(angle));

    CGFloat percent = MIN(1.0, distance / 100.0);
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path addArrowFrom:start controlPoint:controlPoint width:30.0 * percent height:50.0 * percent isOpen:true];
    [path addQuadCurveToPoint:end controlPoint:controlPoint];
    [path addArrowFrom:end controlPoint:controlPoint width:30 * percent height:50 * percent isOpen: true];

    shapeLayer.path = path.CGPath;
}

产量:

enter image description here

显然你可以在你认为合适的情况下玩这个(用实心箭头等),但它说明了如何确定一个合理的控制点,给出两点的适度曲线。