iOS4 での UIView のアニメーション

http://www.yuyarin.net/screenshot/20100709005019.png

最近研究室の色々で地図関係の iPhone アプリを開発しているのだけど,Map のように自分の位置に青い丸いのを表示して,その周囲に波紋のようなものをアニメーションで表示したいと思って試行錯誤してみた.

UIView のアニメーションについては,iOS4 以降では animateWithDuration:delay:options:animations:completion などの block-based animation methods が推奨されている.従来のbeginAnimations:context: や setAnimationDuration: や setAnimationTransition:forView:cache: は推奨されていない(discouraged).

// block-based animation methods for iOS4
+ animateWithDuration:animations:
+ animateWithDuration:animations:completion:
+ animateWithDuration:delay:options:animations:completion:
+ transitionFromView:toView:duration:options:completion:
+ transitionWithView:duration:options:animations:completion:

この block-based animation methods では

    [UIView animateWithDuration:1.5f // 1.5秒おきに
                          delay:0.0f // 0.0秒後から
                        options:UIViewAnimationOptionRepeat // 永遠に繰り返す
                                    |UIViewAnimationOptionCurveEaseOut // 初めは速く終わりは遅くなるような変化
                                    |UIViewAnimationOptionAllowUserInteraction // アニメーション中でもユーザによるViewの操作を可能にする
                     animations:^{
                                    // このブロックの中にアニメーションの最終状態を記述する
                                    self.alpha = 0.0; // alphaを0にする
                                    self.bounds = CGRectMake(0, 0, 192, 192); // 波紋のサイズを192x192でframe全体に
                                 }
                     completion:nil]; // アニメーションが終わっても何もしない
    self.animating = YES;

こんな感じに1文で簡潔にアニメーションが記述できてしまう.今までの書き方と比べるとかなり良い.

さて,波紋のようにずっと繰り返す場合は options で UIViewAnimationOptionRepeat を指定する.

ここで嵌ったのが,これだけを指定してしまうとアニメーションが終わるまで UI 操作に制御が戻らなくなり,この場合,永遠に戻ってこなくなってしまう.アニメーション処理はどうも main thread で行われるみたいなので,ここの関数だけ別スレッドにしてもすぐに終了して,同じ状態になる.View のトランジションなどではなく,自分の位置を表示しながら他の操作ができないといけないので,これでは困る.

Cocoa の NSAnimationNonblocking みたいなものがないのかなーと blocking みたいなキーワードで探していたけど,見つからなかったのでもう一度 options を読み直したら見つかった.UIViewAnimationOptionAllowUserInteraction というオプション.これでアニメーション中でもユーザによる UI 操作が可能になる.

ちなみに初めは frame の値を変更するようなアニメーションを書いていたのだけど,これだとよく分からない動作が起きてしまう.frame は固定したまま描画領域だけを変えるのが安全だけど,この波紋をタップしたい,ってなったときにどうするんだろう...

以下ソースコード

/* MEMyself */

// 青い丸のマーカー
@interface MEMyselfMarkerView : UIImageView {
}

@end

// その周りの波紋
@interface MEMyselfRippleView : UIImageView {
    BOOL animating_;
}

@property BOOL animating;

- (void)startRippling;

@end

// 自分自身の位置を表すクラス
@interface MEMyself : MEUser {
    MEMyselfMarkerView *markerView_;
    MEMyselfRippleView *rippleView_;
}

@property (nonatomic, retain) MEMyselfMarkerView *markerView;
@property (nonatomic, retain) MEMyselfRippleView *rippleView;

@end
#import "MEMyself.h"

@implementation MEMyselfMarkerView

@end

@implementation MEMyselfRippleView

@synthesize animating=animating_;

- (id)initWithImage:(UIImage *)image {
    self = [super initWithImage:image];
    self.animating = NO;
    return self;
}

// 波紋のアニメーションを開始する
- (void)startRippling
{
    if(self.animating) return;
    
    // 初期状態の設定
    // 最初は frame の中心に 0x0 のサイズで.
    self.bounds = CGRectMake(self.frame.size.width/2, self.frame.size.height/2, 0, 0);
    [UIView animateWithDuration:1.5f // 1.5秒置きに
                          delay:0.0f // 0.0秒後から
                        options:UIViewAnimationOptionRepeat // 永遠に繰り返す
                                    |UIViewAnimationOptionCurveEaseOut // 初めは早く終わりは遅くなるような変化
                                    |UIViewAnimationOptionAllowUserInteraction // アニメーション中でもユーザによるViewの操作を可能にする
                     animations:^{
                                    // このブロックの中にアニメーションの最終状態を記述する
                                    self.alpha = 0.0; // alphaを0にする
                                    self.bounds = CGRectMake(0, 0, 192, 192); // 波紋のサイズを192x192でframe全体に
                                 }
                     completion:nil]; // アニメーションが終わっても何もしない
    self.animating = YES;
}

@end

@implementation MEMyself

@synthesize markerView=markerView_;
@synthesize rippleView=rippleView_;

- (id)init {
    self = [super init];
    
    self.screenCoord = CGPointMake(160, 240);
    self.frame = CGRectMake(self.screenCoord.x-16, self.screenCoord.y-16, 32, 32);
    self.backgroundColor = [UIColor clearColor];
    
    self.markerView = [[MEMyselfMarkerView alloc] initWithImage:[UIImage imageNamed:@"BlueDot.png"]];
    self.rippleView = [[MEMyselfRippleView alloc] initWithImage:[UIImage imageNamed:@"BlueDotRipple.png"]];
    
    self.markerView.frame = CGRectMake(self.frame.size.width/2-12, self.frame.size.height/2-12, 24, 24);
    self.rippleView.frame = CGRectMake(self.frame.size.width/2-96, self.frame.size.height/2-96, 192, 192);
    
    [self addSubview:self.rippleView];
    [self addSubview:self.markerView];

    // ここではまだ superview が無いのでアニメーションを開始できない
    
    return self;
}

// マーカーがどこかのViewに追加されたらアニメーションを開始する
- (void)didMoveToSuperview {
    [self.rippleView startRippling];
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy