沿着贝塞尔path放置图像
有谁知道如何沿着贝塞尔path放置图像? 我可以写好path并沿pathanimation一个精灵,但是我想让path成为一系列箭头而不是虚线。 我认为必须有一种方法可以在path上添加箭头图像,但无法find它。 path也是弯曲的:
UIBezierPath * path = [UIBezierPath bezierPath]; [path moveToPoint:startingPoint]; [path addCurveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];
我想你想要这样的东西:
你可以在这个github仓库中find我完整的演示应用程序项目。
无论如何,这是一个有趣的小问题。
您需要沿path生成一个点arrays,我假设您希望它们的距离相等。 产生这一点并不是微不足道的。
幸运的是,Core Graphics包含一个可以为你做的function,但是哪一个并不明显。 该函数是CGPathCreateCopyByDashingPath
。
首先,我们来创build一个创build虚线副本的UIBezierPath
类别:
UIBezierPath+Rob_dash.h
#import <UIKit/UIKit.h> @interface UIBezierPath (Rob_dash) - (instancetype)Rob_dashedPathWithPattern:(NSArray *)pattern phase:(CGFloat)phase; @end
UIBezierPath+Rob_dash.m
#import "UIBezierPath+Rob_dash.h" @implementation UIBezierPath (Rob_dash) - (instancetype)Rob_dashedPathWithPattern:(NSArray *)pattern phase:(CGFloat)phase { CGFloat lengths[pattern.count]; size_t i = 0; for (NSNumber *number in pattern) { lengths[i++] = number.doubleValue; } CGPathRef dashedCGPath = CGPathCreateCopyByDashingPath(self.CGPath, NULL, phase, lengths, pattern.count); UIBezierPath *dashedPath = [self.class bezierPathWithCGPath:dashedCGPath]; CGPathRelease(dashedCGPath); return dashedPath; } @end
一旦我们有了一个虚线的path,我们需要枚举path的元素(像moveToPoint:
, addLineToPoint:
等单独的命令)。 唯一的方法是使用另一个核心graphics函数CGPathApply
。 让我们编写另一个使用块的UIBezierPath
类别,使其更容易。 这个更长一点:
UIBezierPath+Rob_forEach.h
#import <UIKit/UIKit.h> typedef void (^Rob_UIBezierPath_moveBlock)(CGPoint destination); typedef void (^Rob_UIBezierPath_lineBlock)(CGPoint destination); typedef void (^Rob_UIBezierPath_quadBlock)(CGPoint control, CGPoint destination); typedef void (^Rob_UIBezierPath_cubicBlock)(CGPoint control0, CGPoint control1, CGPoint destination); typedef void (^Rob_UIBezierPath_closeBlock)(void); @interface UIBezierPath (Rob_forEach) - (void)Rob_forEachMove:(Rob_UIBezierPath_moveBlock)moveBlock line:(Rob_UIBezierPath_lineBlock)lineBlock quad:(Rob_UIBezierPath_quadBlock)quadBlock cubic:(Rob_UIBezierPath_cubicBlock)cubicBlock close:(Rob_UIBezierPath_closeBlock)closeBlock; @end
UIBezierPath+Rob_forEach.m
#import "UIBezierPath+Rob_forEach.h" struct ForEachBlocks { __unsafe_unretained Rob_UIBezierPath_moveBlock moveBlock; __unsafe_unretained Rob_UIBezierPath_lineBlock lineBlock; __unsafe_unretained Rob_UIBezierPath_quadBlock quadBlock; __unsafe_unretained Rob_UIBezierPath_cubicBlock cubicBlock; __unsafe_unretained Rob_UIBezierPath_closeBlock closeBlock; }; static void applyBlockToPathElement(void *info, const CGPathElement *element) { struct ForEachBlocks *blocks = info; switch (element->type) { case kCGPathElementMoveToPoint: if (blocks->moveBlock != nil) { blocks->moveBlock(element->points[0]); } break; case kCGPathElementAddLineToPoint: if (blocks->lineBlock != nil) { blocks->lineBlock(element->points[0]); } break; case kCGPathElementAddQuadCurveToPoint: if (blocks->quadBlock) { blocks->quadBlock(element->points[0], element->points[1]); } break; case kCGPathElementAddCurveToPoint: if (blocks->cubicBlock) { blocks->cubicBlock(element->points[0], element->points[1], element->points[2]); } break; case kCGPathElementCloseSubpath: if (blocks->closeBlock) { blocks->closeBlock(); } break; } } @implementation UIBezierPath (Rob_forEach) - (void)Rob_forEachMove:(Rob_UIBezierPath_moveBlock)moveBlock line:(Rob_UIBezierPath_lineBlock)lineBlock quad:(Rob_UIBezierPath_quadBlock)quadBlock cubic:(Rob_UIBezierPath_cubicBlock)cubicBlock close:(Rob_UIBezierPath_closeBlock)closeBlock { struct ForEachBlocks blocks = { .moveBlock = moveBlock, .lineBlock = lineBlock, .quadBlock = quadBlock, .cubicBlock = cubicBlock, .closeBlock = closeBlock }; CGPathApply(self.CGPath, &blocks, applyBlockToPathElement); } @end
好的,现在我们要把这两个类别一起用来冲破path,然后沿着破折号走,并在每个破折号的末尾发出点。 请注意,短划线可能由多个连续的线段/曲线段组成。 我们需要注意移动命令以知道什么时候破折号结束。 另外,为了以正确的angular度画出每一个箭头,我们需要知道每个点的曲线的正切,所以我们也将它作为一个单位向量进行计算。 在直线段的情况下,切线向量平行于线段。 在曲线的情况下,曲线终点之前的控制点决定了终点处的切线。
UIBezierPath+Rob_points.h
#import <UIKit/UIKit.h> @interface UIBezierPath (Rob_points) - (void)Rob_forEachPointAtInterval:(CGFloat)interval perform:(void (^)(CGPoint point, CGVector vector))block; @end
UIBezierPath+Rob_points.m
#import "UIBezierPath+Rob_points.h" #import "UIBezierPath+Rob_dash.h" #import "UIBezierPath+Rob_forEach.h" #import <tgmath.h> static CGVector vectorFromPointToPoint(CGPoint tail, CGPoint head) { CGFloat length = hypot(head.x - tail.x, head.y - tail.y); return CGVectorMake((head.x - tail.x) / length, (head.y - tail.y) / length); } @implementation UIBezierPath (Rob_points) - (void)Rob_forEachPointAtInterval:(CGFloat)interval perform:(void (^)(CGPoint, CGVector))block { UIBezierPath *dashedPath = [self Rob_dashedPathWithPattern:@[ @(interval * 0.5), @(interval * 0.5) ] phase:0]; __block BOOL hasPendingSegment = NO; __block CGPoint pendingControlPoint; __block CGPoint pendingPoint; [dashedPath Rob_forEachMove:^(CGPoint destination) { if (hasPendingSegment) { block(pendingPoint, vectorFromPointToPoint(pendingControlPoint, pendingPoint)); hasPendingSegment = NO; } pendingPoint = destination; } line:^(CGPoint destination) { pendingControlPoint = pendingPoint; pendingPoint = destination; hasPendingSegment = YES; } quad:^(CGPoint control, CGPoint destination) { pendingControlPoint = control; pendingPoint = destination; hasPendingSegment = YES; } cubic:^(CGPoint control0, CGPoint control1, CGPoint destination) { pendingControlPoint = control1; pendingPoint = destination; hasPendingSegment = YES; } close:nil]; if (hasPendingSegment) { block(pendingPoint, vectorFromPointToPoint(pendingControlPoint, pendingPoint)); } } @end
现在我们可以findpath上的点,以及每个点上的单位切线向量。 让我们在drawRect:
创build一个自定义的视图
ArrowView.h
#import <UIKit/UIKit.h> @interface ArrowView : UIView @property (nonatomic) CGFloat interval; @end
ArrowView.m
#import "ArrowView.h" #import "UIBezierPath+Rob_figureEight.h" #import "UIBezierPath+Rob_points.h" @implementation ArrowView - (void)setInterval:(CGFloat)interval { _interval = interval; [self setNeedsDisplay]; } - (void)drawRect:(CGRect)rect { UIImage *arrow = [UIImage imageNamed:@"right233.png"]; UIBezierPath *path = [UIBezierPath Rob_figureEightInRect:CGRectInset(self.bounds, 40, 40)]; // [path stroke]; [path Rob_forEachPointAtInterval:self.interval perform:^(CGPoint point, CGVector vector) { CGContextRef gc = UIGraphicsGetCurrentContext(); CGContextSaveGState(gc); { CGContextTranslateCTM(gc, point.x, point.y); CGContextConcatCTM(gc, CGAffineTransformMake(vector.dx, vector.dy, -vector.dy, vector.dx, 0, 0)); CGContextTranslateCTM(gc, -0.5 * arrow.size.width, -0.5 * arrow.size.height); // UIRectFrame((CGRect){ CGPointZero, arrow.size }); [arrow drawAtPoint:CGPointZero]; } CGContextRestoreGState(gc); }]; } @end
如果你想沿着一条path画箭头图像,那就是这一切。
我的演示应用程序库有一点奖励。 如果你回到第一个提交,我也实现了一个不同的解决scheme:一个类别需要一个path和“箭头”,并在每个子path的末尾放置一个箭头。 如果你把它和潇洒相结合(就像我在那个版本的项目中那样),你会得到沿着path的箭头。 但最终不像使用箭头图像那么好。