setWantsLayer:YESの後レイヤーのframeが変わる

Mac OS X のCore Animationを使ったアプリケーションを作成したときにつまずいたところをメモしておきます。

CIFilterを使って画像の一部分にフィルタをかけるアニメーションを実行したいのですが、フィルタをかける位置がずれてしまうのです。

次の様にawakeFromNibの中でCALayerのコンテンツを指定し、ビューに設定しています。

- (void)awakeFromNib {

    // レイヤーを作成する
    CALayer* layer;
    layer = [CALayer layer];

    // レイヤーに画像をセットする
    NSURL *imageUrl = [NSURL fileURLWithPath:
      [[NSBundle mainBundle]pathForResource:@"map" ofType:@"png"]];
    NSData *imageData =
      [NSData dataWithContentsOfURL:imageUrl];
    NSBitmapImageRep *imageRep =
      [NSBitmapImageRep imageRepWithData:imageData];
    layer.frame =
      CGRectMake(0, 0, [imageRep pixelsWide], [imageRep pixelsHigh]);
    layer.contents = (id)[imageRep CGImage];
    layer.contentsGravity = kCAGravityCenter;

    // レイヤーをビューに設定する
    [imageView setLayer:layer];
    [imageView setWantsLayer:YES];

}

画面はこのような感じです。
アニメーション実行前の画面

画面の”done”ボタンを押すと画像の吹き出し部分にフィルタがかかるはずなのですが・・・・。
アニメーション実行後の画像(失敗)

残念な感じに・・・。

awakeFromNibでCALayerのframe.size.widthとframe.size.heightにコンテンツに指定した画像の横と縦のピクセル数(353,278)を設定しています。しかし、調べてみると

[imageView setWantsLayer:YES];

の後、frame.size.widthとframe.size.heightの値がビューのframeの値(400,400)に変わっているのです。これが原因でした。
CIFilterではコンテンツに指定した画像の大きさを元にフィルタをかける位置を設定しています。ところが、レイヤーの大きさが画像の大きさと異なっていた為、フィルタを実行する位置がずれてしまったのです。

次の様にサブレイヤーに画像をセットしてフィルタをかけると、意図した位置でフィルタを実行することができました。

- (void)awakeFromNib {
    
    // レイヤーを作成する
    CALayer* layer;
    layer = [CALayer layer];
    // レイアウトマネージャの設定
    layer.layoutManager = [CAConstraintLayoutManager layoutManager];

    // レイヤーをビューに設定する
    [imageView setLayer:layer];
    [imageView setWantsLayer:YES];
    
    // サブレイヤーを作成する
    CALayer *imageLayer = [CALayer layer];

    // サブレイヤーに画像をセットする
    NSURL *imageUrl = [NSURL fileURLWithPath:
      [[NSBundle mainBundle]pathForResource:@"map" ofType:@"png"]];
    NSData *imageData =
      [NSData dataWithContentsOfURL:imageUrl];
    NSBitmapImageRep *imageRep =
      [NSBitmapImageRep imageRepWithData:imageData];
    imageLayer.frame =
      CGRectMake(0, 0, [imageRep pixelsWide], [imageRep pixelsHigh]);
    imageLayer.contents = (id)[imageRep CGImage];
    imageLayer.contentsGravity = kCAGravityCenter;
    
    // スーパーレイヤーの中心にサブレイヤーを表示する
    [imageLayer addConstraint:
      [CAConstraint constraintWithAttribute:kCAConstraintMidY
                                 relativeTo:@"superlayer"
                                  attribute:kCAConstraintMidY]];

    [imageLayer addConstraint:
      [CAConstraint constraintWithAttribute:kCAConstraintMidX
                                 relativeTo:@"superlayer"
                                  attribute:kCAConstraintMidX]];
    
    // レイヤーにサブレイヤーを追加する
    [layer addSublayer:imageLayer];

}

アニメーション実行後の画像(成功)

レイアウトマネージャを使用して、サブレイヤーの位置をスーパーレイヤーの位置を基準に決定しています。Core AnimationやレイヤについてはCore Animationプログラミングガイドが参考になりました。レイアウトマネージャについては、Core AnimationプログラミングガイドCore Animationレイヤの配置を参考にしています。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*