2017-11-21 4 views
1

Ich versuche, einen Pfeil zu machen, die zwei Seitenpfeilmarkierung haben und in der mittleren BogenkurveUIBezierPath- Beide Richtung Pfeil mit Kurve

aber jetzt habe ich verwendet mayOff_gist und in der Lage einzelne Linie Pfeil mit einer Seite zu machen.

Code-

//.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 }; 
} 

und über Code gibt Ausgang  mark

+0

@Rob Ich kann keine Kurve in der Mitte der Zeile hinzufügen. Könnten Sie mir helfen, – ChenSmile

Antwort

2

Die Grundidee Pfeilspitzen in Zeichnung auf Ein gekrümmter Pfad ist, um die Tangente des Endes des Cu herauszufinden rve (z.B. die Steigung zwischen dem Punkt und seinem Kontrollpunkt) und dann herauszufinden, wie man den Pfeilkopf versetzt. Betrachten wir zum Beispiel diese Erweiterung 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 

Und

@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 

Sie können dann ein Pfad wie so machen:

@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 

Und das ergibt:

open arrowheads

Oder

solid filled arrowheads


Wenn Sie diese Dynamik für zwei beliebige Punkte machen wollen, tun nur ein wenig Trigonometrie des richtigen Kontrollpunkt, um herauszufinden. Zum Beispiel wird dies einen Kontrollpunkt verwenden, dass ein Winkel ist, die π/8-Offset aus dem Winkel zwischen dem Start ist und Endpunkte:

- (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; 
} 

Das ergibt:

enter image description here

Sie klar kann damit spielen, wie Sie es für richtig halten (tun Sie es für den durchgezogenen Pfeil, etc.), aber es veranschaulicht, wie man einen plausiblen Kontrollpunkt bestimmt, der eine bescheidene Kurve gibt, die zwei Punkte gibt.

+0

wie könnte ich dynamische x und y und Kontrollpunkte übergeben. Ich konnte die Start- und Kontrollpunktberechnung nicht verstehen. Bitte erläutern Sie es – ChenSmile

+0

@ChenSmile - Siehe überarbeitete Antwort, wie Kontrollpunkt berechnet wird, der eine konsistente Kurve ergibt, wenn Sie Endpunkte ändern. – Rob

+0

Vielen Dank rauben – ChenSmile