Sil*_*tar 5 objective-c ios uibezierpath
有谁知道如何沿着bezier路径放置图像?我可以编写路径精细并沿路径动画精灵,但我想让路径成为一系列箭头而不是虚线.我假设必须有一种方法在路径上添加箭头图像但无法找到它.路径都是弯曲的:
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:startingPoint];
[path addCurveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];
Run Code Online (Sandbox Code Playgroud)
rob*_*off 16
我想你想要这样的东西:
您可以在此github存储库中找到我的完整演示应用程序项目.
无论如何,这是一个有趣的小问题.
您需要沿路径生成一个点阵列,我假设您希望它们具有相等的间距.产生这一点并非易事.
幸运的是,Core Graphics包含一个可以为你完成的功能,但是哪一个并不明显.功能是CGPathCreateCopyByDashingPath.
首先,让我们UIBezierPath创建一个创建虚线副本的类别:
UIBezierPath+Rob_dash.h#import <UIKit/UIKit.h>
@interface UIBezierPath (Rob_dash)
- (instancetype)Rob_dashedPathWithPattern:(NSArray *)pattern phase:(CGFloat)phase;
@end
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
一旦我们有一个虚线路径,我们需要枚举路径的元素(各个命令,如moveToPoint:,addLineToPoint:等等).唯一的方法是使用另一个核心图形功能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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
好了,现在我们想要一起使用这两个类别来划分路径,然后沿着破折号走,并在每个破折号的末尾发出点.请注意,短划线可能包含多个连续的线/曲线段.我们需要注意移动命令才能知道破折号何时结束.另外,要以正确的角度绘制每个箭头,我们需要知道每个点处的曲线的切线,因此我们也将计算它作为单位矢量.在直线段的情况下,切线矢量平行于线段.在曲线的情况下,紧接曲线终点之前的控制点确定端点处的切线.
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
现在我们可以在路径上找到点,并在每个点找到单位切线向量.让我们创建一个使用此功能的自定义视图drawRect::
ArrowView.h#import <UIKit/UIKit.h>
@interface ArrowView : UIView
@property (nonatomic) CGFloat interval;
@end
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
如果你想沿着路径绘制箭头图像,那就是它的全部.
我的演示应用程序库中有一点奖励.如果你回到第一次提交,我也实现了一个不同的解决方案:一个采用路径并"箭头化"它的类别,在每个子路径的末尾放置一个箭头.如果你将它与破折号结合起来(就像我在该项目版本中所做的那样),你会沿着路径获得箭头.但它最终看起来不像使用箭头图像那么好.
| 归档时间: |
|
| 查看次数: |
2017 次 |
| 最近记录: |