105
99

More than 5 years have passed since last update.

タップした点から円が広がるようにして画面遷移するアニメーションを作る

Last updated at Posted at 2015-06-24

※ すべてのコードを載せると量が多くなってしまうので、ポイントを絞って説明します。
※ すべて見たい方はGitHubを参照してください。


ポイントを簡潔にまとめると以下のようになります。

  1. 遷移の始まりと終わりの円のCGPathを取得する
  2. 遷移先のUIViewControllerのview.layer.maskにCAShapeLayerを代入する
  3. 取得したCGPathを使ってアニメーションを実行する

画面遷移のアニメーションの実装には、UIViewControllerAnimatedTransitioningを使っています。

遷移の始まりと終わりの円のCGPathを取得する

// 画面全体を覆う円の半径
let radius = { () -> CGFloat in
    // centerはタップしたCGPoint
    let x = max(self.center.x, containerView.frame.width - self.center.x)
    let y = max(self.center.y, containerView.frame.height - self.center.y)

    return sqrt(x * x + y * y)
}()

let rectAroundCircle = { (radius: CGFloat) -> CGRect in
    return CGRectInset(CGRect(origin: self.center, size: CGSizeZero), -radius, -radius)
}

// アニメーションにおけるはじまりの円と終わりの円のパスを取得
let startPath = CGPathCreateWithEllipseInRect(rectAroundCircle(0), nil)
let endPath = CGPathCreateWithEllipseInRect(rectAroundCircle(radius), nil)

radiusでは、タップした点から一番遠い角までの距離が半径が求められます。
つまり、これによって求められるものは、アニメーション終了時の円の半径となります。

rectAroundCircleは、タップした点から、指定したradius分余白を縦横両方に加えた矩形(CGRect)を取得できます。
これは、次に出てくるCGPathCreateWithEllipseInRectで、円のCGPathを求めるためのCGRectということになります。

CGPathCreateWithEllipseInRectは、指定されたCGRectの各辺に接するような楕円のCGPathを得ることができます(参考: CGPathCreateWithEllipseInRectを使って円形のviewを作る)。
先ほどのrectAroundCircleを使って、アニメーションにおけるはじまりの円と終わりの円のCGPathを求めています。

遷移先のUIViewControllerのview.layer.maskにCAShapeLayerを代入する

// targetは、遷移先のUIViewControllerのview
target.layer.mask = { () -> CALayer in
    let mask = CAShapeLayer()
    mask.path = endPath

    return mask
}()

target.layer.maskにpathを設定できるようにするため、CAShapeLayer(CALayerの子クラス)を代入します。
pathに先ほどの円のCGPathを設定することでアニメーションを実現するからです。

target.layer.maskにpathを設定できるようすればいいだけなので、上のようにendPathを代入しておくことはしなくてもいいのではないかと思うかもしれませんが、設定しておかないとアニメーション終了後にviewが真っ暗になる(pathの値がnilになり、viewに描画されない状態になると思われる)ので、アニメーション前に終了時のpathを設定しておきます。


追記

naoyashigaさんより、上の内容についてご指摘いただきました。
CABasicAnimationのremovedOnCompletionプロパティをfalseにし、fillModeプロパティをkCAFillModeForwardsに設定すれば、わざわざあらかじめmask.pathにendPathを指定しておかなくてもアニメーション後の状態を維持することができそうです。
つまり、CABasicAnimationの各プロパティを設定したら、上のコードは単純に以下のようにするだけで大丈夫です。

target.layer.mask = CAShapeLayer()

GitHubも更新してあるので、興味がある方は是非ご覧ください。


取得したCGPathを使ってアニメーションを実行する

// CABasicAnimationは通常このようなイニシャライザは持っていませんので、
// extensionで自作しています。
// duration, timingFunction, delegateは自分で設定。
let animation = CABasicAnimation(keyPath: "path", fromValue: startPath, toValue: endPath, duration: duration, timingFunction: timingFunction, delegate: delegate)

target.layer.mask.addAnimation(animation, forKey: "circular")

CABasicAnimationのキー値を"path"として、上で求めたpathをそれぞれfromValue, toValueに設定しています。
そして、target.layer.maskに対してこのアニメーションを実装します。

デモ

実際に作ったものが以下のようになります。

Preview

このように、GitHubにあげたものには、今回とは逆の、円が小さくなって遷移するようなアニメーションも実装しています。

105
99
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
105
99