我正在尝试制作一个有两个侧箭头标记和中间弧形曲线的箭头
但是现在我已经使用了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 };
}
答案 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
然后产生:
或者
如果你想让任何两点动态,只需做一点三角法来找出正确的控制点。例如,这将使用一个控制点,该角度是从起点和终点之间的角度偏移π/ 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;
}
产量:
显然你可以在你认为合适的情况下玩这个(用实心箭头等),但它说明了如何确定一个合理的控制点,给出两点的适度曲线。