ViewController#viewDidLoad内でアニメーションを開始してはいけない

このミス定期的にやっちゃうよね!

いや、それはただの物忘れ・・・私の脳が衰えてきているだけか・・・


ビューのアニメーションを開始するのは、ViewControllerのviewDidAppear以降でなければならない。


以下は、ラベルをゆっくりと点滅させるだけの単純な画面である。

【これはダメなやり方ダヨ!】

@interface ViewController : UIViewController
@end

@implementation ViewController

-(void)viewDidLoad {
    [super viewDidLoad];
    
    // ラベルをビューの中心に貼り付ける
    UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 30.0)];
    label.text = @"Blink";
    label.textAlignment = NSTextAlignmentCenter;
    label.center = self.view.center;
    [self.view addSubview:label];
    
    // ラベルをフェードイン、フェードアウトさせるアニメーションを開始する
    CABasicAnimation* blink = [CABasicAnimation animationWithKeyPath:@"opacity"];
    blink.duration = 1.95;
    blink.autoreverses = YES;
    blink.fromValue = @1.0;
    blink.toValue = @0.0;
    blink.repeatCount = HUGE_VALF;
    blink.fillMode = kCAFillModeBoth;
    blink.delegate = self;
    [label.layer addAnimation:blink forKey:@"MyAnimation"];
}

@end

上記コードを実際に試した人はきっとこう言うだろう。


「嘘ついてんじゃねえよこのピザ野郎、ちゃんと動くだろ」


実は、上記コードはある状況では動くが、ある状況では動かない。

具体的には、ViewControllerがアニメーションを伴う画面遷移後に表示される場合に、ラベルの点滅アニメーションが機能しない。エラーの類いは何も出ない。

アニメーションが機能しないという部分をもっと細かく述べるならば、アニメーションが開始した直後にすぐに終了してしまう。結果、見た目上は、まったく動かなかったように見える。


答えを知っていればひどくあっけない問題だが、これはviewDidLoad内でアニメーションを開始していることが原因である。

以下のようにすればきちんと動く。

【こちら推奨】

@interface ViewController : UIViewController
{
    CALayer* _blinkLayer;
    CAAnimation* _blinkAnimation;
}

@end

@implementation ViewController

-(void)viewDidLoad {
    [super viewDidLoad];
    
    UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 30.0)];
    label.text = @"Blink";
    label.textAlignment = NSTextAlignmentCenter;
    label.center = self.view.center;
    [self.view addSubview:label];
    
    // ラベルのアニメーションを生成する(まだ開始しない)
    CABasicAnimation* blink = [CABasicAnimation animationWithKeyPath:@"opacity"];
    blink.duration = 1.95;
    blink.autoreverses = YES;
    blink.fromValue = @1.0;
    blink.toValue = @0.0;
    blink.repeatCount = HUGE_VALF;
    blink.fillMode = kCAFillModeBoth;
    blink.delegate = self;
    
    _blinkLayer = label.layer;
    _blinkAnimation = blink;
}

-(void)viewDidAppear:(BOOL)animated {
    // 今です!
    [_blinkLayer addAnimation:_blinkAnimation forKey:@"MyAnimation"];
}

@end

もう少しいえば、本件は、間接的に、「生成と同時に自動的にアニメーションするようなカスタムビューを作ってはならない」という教訓を示している。ViewControllerがviewDidLoad内でサブビューを初期化、構成するのは当然の行為であり、カスタムビューがアニメーションするのであれば、ViewControllerがそのことを知っていなければならないのだ。即ち、カスタムビューがアニメーションするのであれば、初期化時に自動的にアニメーションを開始するのではなく、startAnimatingのようなメソッドを提供しなければならないということになる。


viewDidLoadの時点では、まだビューが表示されておらず、アニメーションを開始するのはビューが表示され「た」直後、即ちviewDidAppearでやるべき、なんて話は、考えてもみれば至極当然のことである。


はぁ・・・