UIBezierPath- 带曲线的双向箭头 [英] UIBezierPath- Both direction Arrow with Curve
问题描述
我正在尝试制作一个带有两个侧箭头标记和中间弧形曲线的箭头
I am trying to make an arrow which have two side arrow mark and in middle arc curve
但是现在我已经使用了 mayOff_gist 并且能够在一侧制作单线箭头.
But Now i have used mayOff_gist and able to make single line arrow with one side .
代码-
//.h
@interface UIBezierPath (arrow)
+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
toPoint:(CGPoint)endPoint
tailWidth:(CGFloat)tailWidth
headWidth:(CGFloat)headWidth
headLength:(CGFloat)headLength;
@end
//.m
#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 };
}
以上代码给出输出
推荐答案
在曲线路径上绘制箭头的基本思想是计算曲线末端的切线(例如该点与其控制点之间的斜率)),然后弄清楚如何偏移箭头.例如,考虑对 UIBezierPath
的这个扩展:
The basic idea in drawing arrowheads on a curved path is to figure out the tangent of the end of the curve (e.g. the slope between the point and its control point), and then figure out how to offset the arrow head. For example, consider this extension to 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
然后您可以像这样渲染路径:
You can then render a path like so:
@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
这会产生:
或
如果您想为任意两点设置动态,只需做一点三角函数即可找出正确的控制点.例如,这将使用一个角度为 π 的控制点./8 从起点和终点之间的角度偏移:
If you want to make this dynamic for any two points, just do a little trigonometry to figure out the right control point. For example, this will use a control point that is an angle that is π / 8 offset from the angle between the starting and ending points:
- (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;
}
结果:
很明显,您可以根据自己的需要进行操作(针对实线箭头等进行操作),但它说明了如何确定一个合理的控制点,在给定两点的情况下产生一条适度的曲线.
Clearly you can play around with this as you see fit (do it for the solid arrow, etc.), but it illustrates how to determine a plausible control point yielding a modest curve given two points.
这篇关于UIBezierPath- 带曲线的双向箭头的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!