3
2

More than 3 years have passed since last update.

SwiftのCoreAnimationでカードを裏返すアニメーションを実装する

Last updated at Posted at 2020-05-03

はじめに。

Swiftを使って簡単なトランプゲームを作ろうとしたところ、トランプカードを裏返す実装が必要になりました。

アニメーションにも色々ありますがUIViewAnimationOptionsでは細かい設定が難しそうなので、CoreAnimationを使うことにしました。開発環境はXcode11,Swift5です。

実装方法

0.1秒かけてラベルをY軸を中心に90度回した後に、ラベルの文字を変更し、さらに0.1秒かけてラベルを90度回転させます。

ChatView.swift
import Foundation
import UIKit

class ChatView: UIView {

    let myCardImageView = UIImageView()
    let cardAnimateDuration = 0.1
    let zPosition: CGFloat = 10.0
    var myCardNumber: Int!

    required override init(frame:CGRect){
        super.init(frame:frame)
        addSubview(myCardImageView)
        myCardImageView.translatesAutoresizingMaskIntoConstraints = false     
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        _ = self.initViewLayout
    }

    private lazy var initViewLayout : Void = {
        //layoutSubViewの呼び出しを初回のみにする実装 このあたりは適当
        myCardImageView.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
        myCardImageView.heightAnchor.constraint(equalToConstant: 150.0).isActive = true
        myCardImageView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10.0).isActive = true
        myCardImageView.topAnchor.constraint(equalTo: timeResultLabel.bottomAnchor, constant: 20.0).isActive = true
        myCardImageView.image = UIImage(named: "hogehoge")
        myCardImageView.layer.masksToBounds = true
        myCardImageView.layer.cornerRadius = 10.0

    }()

    public func setNumbers(myNumber: Int) {
            //ここの関数を呼び出してアニメーションを実行させる
            self.myCardNumber = myNumber
            //keyPathは文字列ですが指定できる値が決まっています。
            let animation1 : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.y")
            animation1.duration = cardAnimateDuration
            //初期状態と終了状態を宣言します
            animation1.fromValue = 0
            animation1.toValue = CGFloat(Double.pi/2)
            //以下の2行はアニメーションを終えた後の状態を維持するおまじない
            animation1.isRemovedOnCompletion = false
            animation1.fillMode = CAMediaTimingFillMode.forwards
            animation1.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
            animation1.delegate = self
            animation1.setValue(layer, forKey: "myCardFront")
            myCardImageView.layer.add(animation1, forKey: "myCardFront")

            let animation2 : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.y")
            animation2.duration = cardAnimateDuration
            animation2.fromValue = CGFloat(Double.pi*3/2)
            animation2.toValue = CGFloat(Double.pi*2)
            animation2.isRemovedOnCompletion = false
            animation2.fillMode = CAMediaTimingFillMode.forwards
            animation2.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
            //遅延実行する
            animation2.beginTime = CACurrentMediaTime() + cardAnimateDuration
            animation2.delegate = self
            myCardImageView.layer.add(animation2, forKey: nil)

    }

extension ChatView: CAAnimationDelegate {

    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {   
        if let _ = anim.value(forKey: "myCardFront") as? CALayer  {
            let path = "daiamond" + String(myCardNumber)
            myCardImageView.image = UIImage(named: path)          
        }            
    }        
}


Double.pi*/2〜Double.pi*3/2の間を飛ばす実装にすることで、あたかもカードが裏返っているように見えます。逆回りにするにはfromValue,toValueの値を全て負にします。

詰まったポイント

durationを小さくして回転スピードを速めると、周りの要素が消えたり、回転したいラベルの半分が見えなくなる現象がおきました。zPositionを指定することでこの問題は解決します。

beginTimeを指定して遅延実行を行うことで複数のアニメーションを連続させて動かすことができます。ネストが深くならないのが嬉しいですね。

最初に書いた時はCABasicAnimationインスタンスのkeyPathを勝手に指定したせいでアニメーションが動かずに苦労しました。

delegateメソッドで、一つ目のアニメーション(90度回転)が終了したのを検知してテキストを変えたり、画像を入れ替えたりします。

実行結果

裏返すと?から5になります。
画面収録-2020-05-03-18.04.41.gif

最後に

CoreAnimationは先例があまりなくて調べるのが大変ですね。他に面白そうな実装があれば記事に書いていきたいです。

3
2
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
3
2