はじめに
iOSで点と点を結んだ曲線を描く事がたまにあります、今回はUIBezierPathを使った、曲線を描画する方法紹介します。
UIBezierPathは描画用のクラスです、下記サイトの画像を見ると、なんとなーくわかると思います。IllustratorやPhoto shopのパスを使ったことある人はわかりやすいかもしれませんが、あれをプログラムで定義でき、直線や曲線、円を描画できます。
Drawing Shapes Using Bézier Paths – Apple Developer
Viewを用意
描画対象のViewを用意します、いつもの様にUIViewを継承したクラスを作成します。※今回は「DrawCurveView」としました。
まずはinitWithFrameをオーバーライドして、背景色を透明にしてます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@interface DrawCurveView() @end @implementation DrawCurveView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setBackgroundColor:[UIColor clearColor]]; } return self; } - (void)drawRect:(CGRect)rect { } @end |
そしてViewControllerにaddSubViewします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@interface ViewController () { DrawCurveView *__dcv; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; CGSize vcSize = self.view.frame.size; __dcv = [[DrawCurveView alloc] initWithFrame:CGRectMake(0, 0, vcSize.width, vcSize.height)]; [self.view addSubview:__dcv]; } |
点を描画
適当に点を置きます、CGPointを6つ作って配列に入れつつ、drawRectにてその点を描画します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
@interface DrawCurveView() @property (nonatomic, retain) NSMutableArray *points; @end - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setBackgroundColor:[UIColor clearColor]]; _points = [[NSMutableArray alloc] init]; // 点の準備 [_points addObject:[NSValue valueWithCGPoint:CGPointMake(50.0f, 150.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(150.0f, 110.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(250.0f, 140.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(200.0f, 270.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(120.0f, 210.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(170.0f, 170.0f)]]; } return self; } - (void)drawRect:(CGRect)rect { [self drawPoint]; } - (void)drawPoint { [[UIColor whiteColor] setFill]; [[UIColor blackColor] setStroke]; for (int i = 0 ; i < _points.count ; i++) { CGPoint point = [[_points objectAtIndex:i] CGPointValue]; UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = 1.0f; [path addArcWithCenter:point radius:3.0f startAngle:0 endAngle:360 clockwise:YES]; [path fill]; [path stroke]; } } |
直線を描画
次に点と点を直線で結んでみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 〜 略 〜 - (void)drawRect:(CGRect)rect { [self drawLine]; // 追加 [self drawPoint]; } // 〜 略 〜 - (void)drawLine { [[UIColor blueColor] setStroke]; for (int i = 0 ; i < _points.count - 1 ; i++) { CGPoint point = [[_points objectAtIndex:i] CGPointValue]; CGPoint nextPoint = [[_points objectAtIndex:i + 1] CGPointValue]; UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = 2.0f; [path moveToPoint:point]; [path addLineToPoint:nextPoint]; [path stroke]; } } |
いよいよ曲線を描画
色々遠回りをしつつ、やっと曲線の描画です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
// 〜 略 〜 - (void)drawRect:(CGRect)rect { [self drawLine]; [self drawCurveLine]; // 追加 [self drawPoint]; } // 〜 略 〜 - (void)drawCurveLine { CGFloat lineWidth = 3.0f; [[UIColor redColor] setStroke]; // 始点から次の点の中間点までを直線で描画 CGPoint firstMovePoint = [[_points firstObject] CGPointValue]; CGPoint firstAddLinePoint = [[_points objectAtIndex:1] CGPointValue]; UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = lineWidth; [path moveToPoint:firstMovePoint]; [path addLineToPoint:[self midPoint:firstMovePoint :firstAddLinePoint]]; [path stroke]; // 曲線描画 for (int i = 2 ; i < _points.count ; i++) { UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = lineWidth; CGPoint previousPoint2 = [[_points objectAtIndex:i - 2] CGPointValue]; CGPoint previousPoint1 = [[_points objectAtIndex:i - 1] CGPointValue]; CGPoint currentPoint = [[_points objectAtIndex:i] CGPointValue]; // 2つ前の中間点 CGPoint mid1 = [self midPoint:previousPoint1: previousPoint2]; // 1つ前の中間点 CGPoint mid2 = [self midPoint:currentPoint: previousPoint1]; // 2つ前の中間点から [path moveToPoint:mid1]; // 1つ前の点を支点として、1つ前の中間点まで描画 [path addQuadCurveToPoint:mid2 controlPoint:previousPoint1]; [path stroke]; } // 終点の一つ前から、終点までの中間点を直線で描画 CGPoint endMovePoint = [[_points objectAtIndex:_points.count - 2] CGPointValue]; CGPoint endAddLinePoint = [[_points lastObject] CGPointValue]; path = [UIBezierPath bezierPath]; path.lineWidth = lineWidth; [path moveToPoint:[self midPoint:endMovePoint :endAddLinePoint]]; [path addLineToPoint:endAddLinePoint]; [path stroke]; } // 点と点の中間点を返す - (CGPoint) midPoint:(CGPoint) p1 :(CGPoint) p2 { return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5); } |
これまでに比べて長くなりました、わかりにくいですが結果は下記になります。
これで点と直線を描画するメソッドをコメントアウトすれば、一番最初の画像のように表示されます。
線に沿ってアニメーションしてみる
せっかく曲線書いたので、UIBezierPathの図形描画だけじゃない所を紹介します。なんと書いたパスに沿ってViewをアニメーションさせることができます。
まず、アニメーションさせるUIImageViewと、曲線のパスを保持するための配列を用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
@interface DrawCurveView() { UIImageView *__iv; // 追加 } @property (nonatomic, retain) NSMutableArray *paths; // 追加 @property (nonatomic, retain) NSMutableArray *points; @end @implementation DrawCurveView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setBackgroundColor:[UIColor clearColor]]; _paths = [[NSMutableArray alloc] init]; // 追加 _points = [[NSMutableArray alloc] init]; // 点の準備 [_points addObject:[NSValue valueWithCGPoint:CGPointMake(50.0f, 150.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(150.0f, 110.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(250.0f, 140.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(200.0f, 270.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(120.0f, 210.0f)]]; [_points addObject:[NSValue valueWithCGPoint:CGPointMake(170.0f, 170.0f)]]; // アニメーション用ImageViewの用意 ※追加 __iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"techun"]]; [self addSubview:__iv]; } return self; } |
※ちなみに画像は 下記の記事でも使った、当ブログのキャラクターを使用します。
海外で人気のフレームワーク”libGDX”を使って、Androidでアニメーション作ってみた
path配列を保持するため、曲線を書くメソッドに配列へ追加するコードを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
- (void)drawCurveLine { CGFloat lineWidth = 3.0f; [[UIColor redColor] setStroke]; CGPoint firstMovePoint = [[_points firstObject] CGPointValue]; CGPoint firstAddLinePoint = [[_points objectAtIndex:1] CGPointValue]; UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = lineWidth; [path moveToPoint:firstMovePoint]; [path addLineToPoint:[self midPoint:firstMovePoint :firstAddLinePoint]]; [path stroke]; [_paths addObject:path]; // 追加 for (int i = 2 ; i < _points.count ; i++) { UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = lineWidth; CGPoint previousPoint2 = [[_points objectAtIndex:i - 2] CGPointValue]; CGPoint previousPoint1 = [[_points objectAtIndex:i - 1] CGPointValue]; CGPoint currentPoint = [[_points objectAtIndex:i] CGPointValue]; CGPoint mid1 = [self midPoint:previousPoint1: previousPoint2]; CGPoint mid2 = [self midPoint:currentPoint: previousPoint1]; [path moveToPoint:mid1]; [path addQuadCurveToPoint:mid2 controlPoint:previousPoint1]; [path stroke]; [_paths addObject:path]; // 追加 } CGPoint endMovePoint = [[_points objectAtIndex:_points.count - 2] CGPointValue]; CGPoint endAddLinePoint = [[_points lastObject] CGPointValue]; path = [UIBezierPath bezierPath]; path.lineWidth = lineWidth; [path moveToPoint:[self midPoint:endMovePoint :endAddLinePoint]]; [path addLineToPoint:endAddLinePoint]; [path stroke]; [_paths addObject:path]; // 追加 } |
そしてアニメーションを開始するメソッドを記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (void)startAnimation { // 曲線パスを合成する UIBezierPath *path = [UIBezierPath bezierPath]; [_paths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [path appendPath:obj]; }]; // パスに沿ってUIImageViewをアニメーションさせる CAKeyframeAnimation *animation; animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; animation.fillMode = kCAFillModeForwards; animation.removedOnCompletion = NO; animation.duration = 3.0; animation.path = path.CGPath; [__iv.layer addAnimation:animation forKey:nil]; } |
このメソッドをヘッダファイルにも記述して、ViewControllerのviewDidAppearで呼びます。
1 2 3 4 5 |
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [__dcv startAnimation]; } |
結果として下記のように表示されます。一応gifなのですが、動いてなかったらクリックしてもらえば多分動きます。
最後に
なんだかんだで2014年はこれを使わない仕事のほうが少なかったです、覚えてみると楽しい上に応用が色々効くので、ぜひ触ってみてください。