[iOS] UIViewPropertyAnimatorに良い感じのイージングを使いたい!

2019.07.05

こんにちは。きんくまです。

アニメーションのキモはイージング!ということで、今回はカスタムイージングのお話です。

まずはデモを見てください。

これは、左から右に1秒で移動するだけのアニメーションなのですが、1点だけ違いがあって、それはイージングです。

イージングとは?

昔書いた記事があるのですが、時間を等間隔に分割して、その位置を変更すると、アニメーションの動きが全然変わって見えるのです。

UIViewのアニメーションでやりたい

UIViewのアニメーションはこんな感じにやります。 オプションのところが curveEaseIn のように指定できるのですが種類が少なくてちょっと寂しいところでした。

UIView.animate(withDuration: 1.0,
                       delay: 0,
                       options: [.curveEaseIn],
                       animations: {

        }, completion: nil)

UIViewPropertyAnimatorでカスタムイージングを指定できる!

そこでUIViewPropertyAnimatorの登場です。これには、自分で設定したイージングを設定できるのです。すばらしいっ!

今回作ったパラメータ

/// ベジェタイミングタイプ
///
/// オリジナルパラメータ
/// https://github.com/zz85/cubic-bezier-approximations
enum CubicTimingParametersType {
    case quadIn
    case quadOut
    case quadInOut
    case cubicIn
    case cubicOut
    case cubicInOut
    case quartIn
    case quartOut
    case quartInOut
    case quintIn
    case quintOut
    case quintInOut
    case sineIn
    case sineOut
    case sineInOut
    case expoIn
    case expoOut
    case expoInOut
    case circIn
    case circOut
    case circInOut

    var controlPointData: [CGFloat] {
        switch self {
        case .quadIn: return [ 0.26, 0, 0.6, 0.2 ]
        case .quadOut: return [ 0.4, 0.8, 0.74, 1 ]
        case .quadInOut: return [ 0.48, 0.04, 0.52, 0.96 ]
        case .cubicIn: return [ 0.4, 0, 0.68, 0.06 ]
        case .cubicOut: return [ 0.32, 0.94, 0.6, 1 ]
        case .cubicInOut: return [ 0.66, 0, 0.34, 1 ]
        case .quartIn: return [ 0.52, 0, 0.74, 0 ]
        case .quartOut: return [ 0.26, 1, 0.48, 1 ]
        case .quartInOut: return [ 0.76, 0, 0.24, 1 ]
        case .quintIn: return [ 0.64, 0, 0.78, 0 ]
        case .quintOut: return [ 0.22, 1, 0.36, 1 ]
        case .quintInOut: return [ 0.84, 0, 0.16, 1 ]
        case .sineIn: return [ 0.32, 0, 0.6, 0.36 ]
        case .sineOut: return [ 0.4, 0.64, 0.68, 1 ]
        case .sineInOut: return [ 0.36, 0, 0.64, 1 ]
        case .expoIn: return [ 0.66, 0, 0.86, 0 ]
        case .expoOut: return [ 0.14, 1, 0.34, 1 ]
        case .expoInOut: return [ 0.9, 0, 0.1, 1 ]
        case .circIn: return [ 0.54, 0, 1, 0.44 ]
        case .circOut: return [ 0, 0.56, 0.46, 1 ]
        case .circInOut: return [ 0.88, 0.14, 0.12, 0.86 ]
        }
    }
}

/// UICubicTimingParametersを作成する
class CubicTimingParametersCreator {

    static func createParameters(timingType: CubicTimingParametersType) -> UICubicTimingParameters {

        let pointData = timingType.controlPointData
        if pointData.count != 4 {
            fatalError("the count of pointData must be 4")
        }
        let pt1 = CGPoint(x: pointData[0], y: pointData[1])
        let pt2 = CGPoint(x: pointData[2], y: pointData[3])
        return UICubicTimingParameters(controlPoint1: pt1, controlPoint2: pt2)
    }
}

これを使ってみます。

//UIViewPropertyAnimatorをイージングを指定して作成!
let params = CubicTimingParametersCreator.createParameters(timingType: timingCurve)
let animator = UIViewPropertyAnimator(duration: 1.0, timingParameters: params)

//アニメーションの内容
let halfSquareW = squareWidth / 2
let centerY = squareY + halfSquareW
animationSquare.center = CGPoint(x: halfSquareW, y: centerY)
animator.addAnimations { [weak self] in
    guard let self = self else { return }
    let screenW = UIScreen.main.bounds.width
    self.animationSquare.center = CGPoint(x: screenW - halfSquareW, y: centerY)
}
propertyAnimator = animator

//スタート
animator.startAnimation()

作ったリポジトリ
cm-tsmaeda/CubicVezierCurveSample

こんな感じにUIViewのアニメーションにもカスタムイージングが指定してできて嬉しい!という話でした。

余談

UIViewPropertyAnimatorで指定できるようになる前にも、カスタムイージングを使ってアニメーションすることができました。
ただ、それはCALayerに対してだけでした。

それで以前、それ用のクラスを書いたこともありました。
KinkumaDesign/CustomMediaTimingFunction

今回作ったイージングのパラメータは、こちらのリポジトリからいただいています。
zz85/cubic-bezier-approximations

その中に私の作ったパラメータが比較対象で載っけてもらっていました。

zz85さんのものは、たぶんプログラム的にコントロールポイントを求めたものだと思うので、私の作ったやつより正確だと思います。(私のは自前のツールを作って手動で値をとったものでしたw)

なので良かったなーと思った次第です。