38

Drawアプリで、端に矢印のある線を描く必要があります。私は三角法が苦手なので、この問題が解けません。

ユーザーは画面に指を置き、任意の方向に線を引きます。したがって、矢印は行末に表示されるはずです。

4

4 に答える 4

198

アップデート

この回答の Swift バージョンを個別に投稿しました。

オリジナル

これは楽しい小さな問題です。まず第一に、矢印を描く方法はたくさんあります。曲線または直線の側面があります。非常に簡単な方法を選んで、必要な測定値にラベルを付けましょう。

矢印パーツ

始点、終点、尾の幅、頭の幅、頭の長さを取り、矢印の形を描くパスを返す関数を書きたいと思います。dqd_arrowheadこのメソッドを に追加するという名前のカテゴリを作成しましょうUIBezierPath:

// UIBezierPath+dqd_arrowhead.h

@interface UIBezierPath (dqd_arrowhead)

+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
                                           toPoint:(CGPoint)endPoint
                                         tailWidth:(CGFloat)tailWidth
                                         headWidth:(CGFloat)headWidth
                                        headLength:(CGFloat)headLength;

@end

矢印のパスには 7 つのコーナーがあるため、その定数に名前を付けて実装を開始しましょう。

// UIBezierPath+dqd_arrowhead.m

#import "UIBezierPath+dqd_arrowhead.h"

#define kArrowPointCount 7

@implementation UIBezierPath (dqd_arrowhead)

+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
                                           toPoint:(CGPoint)endPoint
                                         tailWidth:(CGFloat)tailWidth
                                         headWidth:(CGFloat)headWidth
                                        headLength:(CGFloat)headLength {

OK、簡単な部分は完了です。では、パス上のこれら 7 点の座標をどのように見つけるのでしょうか? 矢印が X 軸に沿って配置されていると、ポイントを見つけるのがはるかに簡単になります。

軸に沿った矢印ポイント

軸に沿った矢印のポイント座標を計算するのは非常に簡単ですが、そのためには矢印の全長が必要になります。hypotf標準ライブラリの関数を使用します。

    CGFloat length = hypotf(endPoint.x - startPoint.x, endPoint.y - startPoint.y);

実際に 7 つのポイントを計算するヘルパー メソッドを呼び出します。

    CGPoint points[kArrowPointCount];
    [self dqd_getAxisAlignedArrowPoints:points
                              forLength:length
                              tailWidth:tailWidth
                              headWidth:headWidth
                             headLength:headLength];

しかし、一般に、軸に沿った矢印を作成しようとしていないため、これらのポイントを変換する必要があります。幸いなことに、Core Graphics はアフィン変換と呼ばれる一種の変換をサポートしており、これを使用すると、ポイントを回転および移動 (スライド) できます。別のヘルパー メソッドを呼び出して、軸に沿った矢印を要求された矢印に変換する変換を作成します。

    CGAffineTransform transform = [self dqd_transformForStartPoint:startPoint
                                                          endPoint:endPoint
                                                            length:length];

これで、軸に沿った矢印のポイントと、それを目的の矢印に変換する変換を使用して、Core Graphics パスを作成できます。

    CGMutablePathRef cgPath = CGPathCreateMutable();
    CGPathAddLines(cgPath, &transform, points, sizeof points / sizeof *points);
    CGPathCloseSubpath(cgPath);

最後に、 をラップしUIBezierPathCGPath返すことができます:

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

アフィン変換の計算はより複雑です。ここで三角法の出番です。atan2CGAffineTransformRotateCGAffineTransformTranslate関数を使用して作成することもできますが、十分な三角法を覚えていれば、直接作成することもできます。ここで行っていることの詳細については、 Quartz 2D Programming Guideの「The Math Behind the Matrix」を参照してください。

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

@end

簡単にコピーアンドペーストできるように、すべてのコードを要点にまとめました。

このカテゴリを使用すると、簡単に矢印を描くことができます。

サンプル矢印 1 サンプル矢印 2

パスを生成しているだけなので、次の例のようにパスを塗りつぶしたり、ストロークしたりしないことを選択できます。

ストロークされていない矢印のサンプル

ただし、注意が必要です。このコードは、頭の幅を尾の幅よりも小さくした場合、または頭の長さを矢印の全長よりも大きくした場合に、ファンキーな結果が得られることを妨げません。

ナローヘッドサンプル 頭が長すぎるサンプル

于 2012-11-26T06:01:51.490 に答える
18

これは私の古い Objective-C コードの Swift バージョンです。Swift 3.2 以降のバージョンで動作するはずです。

extension UIBezierPath {

    static func arrow(from start: CGPoint, to end: CGPoint, tailWidth: CGFloat, headWidth: CGFloat, headLength: CGFloat) -> UIBezierPath {
        let length = hypot(end.x - start.x, end.y - start.y)
        let tailLength = length - headLength

        func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint { return CGPoint(x: x, y: y) }
        let points: [CGPoint] = [
            p(0, tailWidth / 2),
            p(tailLength, tailWidth / 2),
            p(tailLength, headWidth / 2),
            p(length, 0),
            p(tailLength, -headWidth / 2),
            p(tailLength, -tailWidth / 2),
            p(0, -tailWidth / 2)
        ]

        let cosine = (end.x - start.x) / length
        let sine = (end.y - start.y) / length
        let transform = CGAffineTransform(a: cosine, b: sine, c: -sine, d: cosine, tx: start.x, ty: start.y)

        let path = CGMutablePath()
        path.addLines(between: points, transform: transform)
        path.closeSubpath()

        return self.init(cgPath: path)
    }

}

これをどのように呼び出すかの例を次に示します。

let arrow = UIBezierPath.arrow(from: CGPoint(x: 50, y: 100), to: CGPoint(x: 200, y: 50),
        tailWidth: 10, headWidth: 25, headLength: 40)
于 2016-06-23T08:06:58.777 に答える
8
//This is the integration into the view of the previous exemple
//Attach the following class to your view in the xib file

#import <UIKit/UIKit.h>

@interface Arrow : UIView

@end

#import "Arrow.h"
#import "UIBezierPath+dqd_arrowhead.h"

@implementation Arrow
{
    CGPoint startPoint;
    CGPoint endPoint;
    CGFloat tailWidth;
    CGFloat headWidth;
    CGFloat headLength;
    UIBezierPath *path;

}


- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder])
    {
        [self setMultipleTouchEnabled:NO];
        [self setBackgroundColor:[UIColor whiteColor]];

    }
    return self;
}

- (void)drawRect:(CGRect)rect {

    [[UIColor redColor] setStroke];
    tailWidth = 4;
    headWidth = 8;
    headLength = 8;
    path = [UIBezierPath dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
                                                  toPoint:(CGPoint)endPoint
                                                tailWidth:(CGFloat)tailWidth
                                                headWidth:(CGFloat)headWidth
                                               headLength:(CGFloat)headLength];
    [path setLineWidth:2.0];

    [path stroke];

}
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    UITouch* touchPoint = [touches anyObject];
    startPoint = [touchPoint locationInView:self];
    endPoint = [touchPoint locationInView:self];


    [self setNeedsDisplay];
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch* touch = [touches anyObject];
    endPoint=[touch locationInView:self];
    [self setNeedsDisplay];
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch* touch = [touches anyObject];
    endPoint = [touch locationInView:self];
    [self setNeedsDisplay];
}


@end
于 2013-06-18T12:21:23.860 に答える
2

Swift 3.0 では、これを実現できます

extension UIBezierPath {

class func arrow(from start: CGPoint, to end: CGPoint, tailWidth: CGFloat, headWidth: CGFloat, headLength: CGFloat) -> Self {
    let length = hypot(end.x - start.x, end.y - start.y)
    let tailLength = length - headLength

    func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint { return CGPoint(x: x, y: y) }
    var points: [CGPoint] = [
        p(0, tailWidth / 2),
        p(tailLength, tailWidth / 2),
        p(tailLength, headWidth / 2),
        p(length, 0),
        p(tailLength, -headWidth / 2),
        p(tailLength, -tailWidth / 2),
        p(0, -tailWidth / 2)
    ]

    let cosine = (end.x - start.x) / length
    let sine = (end.y - start.y) / length
    var transform = CGAffineTransform(a: cosine, b: sine, c: -sine, d: cosine, tx: start.x, ty: start.y)        
    let path = CGMutablePath()
    path.addLines(between: points, transform: transform)
    path.closeSubpath()
    return self.init(cgPath: path)
}

}
于 2017-01-06T13:35:54.660 に答える