Lottieでアプリにアニメーションを組み込む話(iOSプログラマー編)
この記事について
この記事は Lottieでアプリにアニメーションを組み込む話(デザイナー編) を受けての iOSプログラマー編 になります。 デザイナー編では実際にアニメーションを作る具体的な方法を含め解説されていますので是非ご参照ください。
Lottieとは
LottieとはAdobe After Effectsで作ったアニメーションをそのままクライアントアプリで表示するためのライブラリです。 iOSやAndroidのネイティブアプリの他、React Nativeでも利用できます。
iOS用のライブラリは、
https://github.com/airbnb/lottie-ios
です。
なにができるの?
- 作成されたアニメーション用JSONファイルをアプリに埋め込んでわずかなコードで再生することができる
- インターネット上に設置したJSONファイルを読み込んでアニメーションを再生することもできる
- アニメーションはリピート再生のほか、逆転再生やアニメーションスピードの調整もできる
- プログラムで任意のフレームまで、もしくは任意のフレームから再生することもできる
- 動的にアニメーション内の要素の色や位置を変更することができる
- 動的にアニメーション内にUIViewサブクラスを埋め込める
- UIViewControllerのトランジッションでも利用できる
- GIFアニメなどより軽くて綺麗
iOSアプリでプログラムで作るよりもいいの?
もちろん、同じことをiOSアプリ内でプログラムで実現しても良いとは思います。しかし、
- これまでアプリプログラマーが実装していた部分をデザイナーさんにお任せするという選択肢ができる
- Androidや他のプラットフォーム上で同じアニメーションファイルをそのまま利用できる
- プログラム内のアニメーション(View)のための複雑なコードを省略できる
ことは、多くのチームでメリットとなり得るでしょう。
事前準備
CarthageやCocoaPodsでlottie-iosをプロジェクトに追加します(方法については省略します)。
アニメーションを表示してみる

JSONファイルの埋め込み
再生したいアニメーションJSONをアプリに埋め込むには、単純にXcodeのプロジェクトにドラッグ&ドロップなどして追加するだけでOKです。1
再生するコード
import Lottie
let animationView = LOTAnimationView(name: "yes")
animationView.frame = view.bounds
view.addSubview(animationView)
animationView.play()
再生するのは本当に簡単で、Lottieをimportし、LOTAnimationViewをJSONファイル名指定で作成し、addSubviewしてplay()するだけです。
なお、LOTAnimationViewのframeは適切な大きさに設定する必要があり、デフォルトでは設定したframeの大きさでアニメーションが拡縮されて再生されてしまいます。2
上のサンプルはサイズを考えずにaddSubviewしており、

のように意図しない大きさで再生されてしまいます。
アニメーションのサイズを知る
アニメーションのサイズを知るには、
- 作成したデザイナーさんに聞く
- アニメーションのJSONファイルを覗いて調べる
他、プログラムで取得することもできます。
animationView.frame = animationView.sceneModel?.compBounds ?? view.bounds
LOTAnimationViewにはsceneModelプロパティがあり、このプロパティからアニメーションに関する情報を参照できます。
サイズに関してはcompBoundsプロパティを見ればOKです。
インターネット上にJSONを設置する
必要なら、アプリに埋め込まずにインターネット上のアニメーションJSONを参照し、後からアプリのバージョンアップなしでアニメーションを変更することもできます。
let animationJSON = "https://example.com/example.json"
guard let url = URL(string: animationJSON) else {
    return
}
downloadAnimationJSON(from: url) { [weak self] filePath in
    self?.setupAnimation(with: filePath)
    self?.animationView?.play()
}
private func downloadAnimationJSON(from url: URL, completion: @escaping (String) -> Void) {
    let task = URLSession.shared.downloadTask(with: url) { url, _, error in
        guard let filePath = url?.path else {
            print("handle error: \(String(describing: error))")
            return
        }
        DispatchQueue.main.async {
            completion(filePath)
        }
    }
    task.resume()
}
private func setupAnimation(with filePath: String) {
    guard let view = animationArea else {
        return
    }
    let animationView = LOTAnimationView(filePath: filePath)
    animationView.frame = animationView.sceneModel?.compBounds ?? view.bounds
    view.addSubview(animationView)
    self.animationView = animationView
}
こちらも特に難しいことはなく、URLSessionのdownloadTaskなどでダウンロードしたファイルを利用するだけです。
ダウンロードした後は、LOTAnimationView(filePath:)というinitializerにダウンロードしたJSONファイルのfilePathを渡してあげます。あとはアプリに組み込まれたファイルを利用するのと同じです。
Asset CatalogにJSONを入れる
なお、普段からAsset Catalog(xcassets)を使われているかたは、このアニメーションJSONをAsset Catalogで管理したいと感じるかと思います。
version 2.5.0時点では、LOTAnimationViewにはAsset CatalogのJSONファイルを楽に読み込むインターフェースがありません。
以下のコードにて無理やりAsset Catalogから読み込ませることはできるのですが、無駄に複雑になるのでおすすめしません。3
guard let data = NSDataAsset(name: "assetName")?.data else {
    assertionFailure("Invalid data asset")
    return
}
let jsonObject: Any
do {
    jsonObject = try JSONSerialization.jsonObject(with: data)
} catch let error {
    assertionFailure(error.localizedDescription)
    return
}
guard let validJSON = jsonObject as? [AnyHashable: Any] else {
    assertionFailure("Invalid json object")
    return
}
let model = LOTComposition(json: validJSON)
let animationView = LOTAnimationView(model: model, in: nil)
再生コントロール
animationView.play()
でアニメーションを開始できるのは前述の通りですが、この他、
animationView.pause()
で停止、
animationView.stop()
で終了(アニメーション開始時の状態で止まる)もできます。
ループ
animationView.loopAnimation = true
とloopAnimationにtrueを設定することでアニメーション終了後に自動的に最初から繰り返し再生されるようになります。
逆転再生
animationView.autoReverseAnimation = true
とautoReverseAnimationにtrueを設定することでアニメーション終了後に自動的に逆転再生されるようになります。
loopAnimationとセットで利用すると、再生 => 逆転再生 => 再生 => 逆転再生… とループされます。
アニメーションスピードの変更
animationView.animationSpeed = 0.5
とanimationSpeedに0.5を設定するとアニメーションスピードは50%になります。また、2.0を設定すれば2倍の速度で再生されます。
アニメーションの終了をハンドリング
animationView?.completionBlock = { finished in
    print("### finished: \(finished)")
}
と、completionBlockにclosureを設定することでアニメーションの終了をハンドリングできます。
loopAnimationでループさせている場合には、アニメーションは終了しないとみなされて終了は通知されません。
また、lottie-ios 2.5.0 の時点ではcompletionBlockは一度通知されると解除されるようで、必要ならplay()するごとにこれを設定する必要があります。
アニメーションの色を動的に変更する
ここまでは用意されたアニメーションを再生するだけでしたが、プログラムでアニメーションをカスタマイズすることもできます。
今年(2018年)の1月末にリリースされた lottie-ios 2.5.0 で
Adds a new API for dynamically changing animation properties at run time
実行時にアニメーションプロパティを動的に変更するための新しいAPIが追加されました
とアナウンスされ、まだ日本語での紹介記事はあまりないですが、アニメーションの色の変更などが簡単にできるようになっています。
例えば、

と全体的にピンク色のアニメーションを、
let color: UIColor = //< 任意のUIColorを指定
let keypath = LOTKeypath(string: "**.Fill 1.Color")
let tintColorValue = LOTColorValueCallback(color: color.cgColor)
lottie.setValueDelegate(tintColorValue, for: keypath)
と数行のコードで、

簡単に他の色に変えることが可能です。4
この変更はアニメーション再生中でも反映されます。
LOTKeypath
具体的には、LOTAnimationViewのsetValueDelegateメソッドにLOTColorValueCallbackとLOTKeypathを渡すだけでこれが実現できます。
LOTColorValueCallbackはCGColorを指定するだけのシンプルなものですが、LOTKeypathのKeypathにはなにを指定したら良いでしょうか?
LottieでいうKeypathとはアニメーションを構成する要素の階層を辿るもので、この階層は、
animationView.logHierarchyKeypaths()
でデバッグ出力できます。
上のアニメーションのlogHierarchyKeypaths()の出力結果は、
heart 2 Outlines
mini heart 3 Outlines.Group 1.Path 1
heart 2 Outlines.Group 1.Path 1
man Outlines.Group 1
man Outlines.Group 3
man Outlines.Group 5
circle 4 Outlines.Group 1
circle 4 Outlines
woman Outlines
mini heart 2 Outlines.Group 1.Fill 1
circle 1 Outlines.Group 1.Fill 1
woman Outlines.Group 4.Path 1
circle 2 Outlines.Group 1.Path 1
circle 4 Outlines.Group 1.Fill 1
woman Outlines.Group 4.Fill 1
//...(以下省略)
といったものでした。
例えば、この中の1つの要素 mini heart 2 Outlines.Group 1.Fill 1 を使って、
let keypath = LOTKeypath(string: "mini heart 2 Outlines.Group 1.Fill 1.Color")
とすれば対象となる要素の色を変更するKeypathができます。
KeypathはAfter Effectsで作成したアニメーションの構造をそのまま辿るものでもありますので、特定の要素のKeypathについてはアニメーションを作成したデザイナーさんに聞くほうが手っ取り早いかもしれません。
ワイルドカード
Keypathの指定にはワイルドカードも利用できます。上のサンプルでは、
let keypath = LOTKeypath(string: "**.Fill 1.Color")
とすべての要素のFill Colorを変更するためのKeypathを作成して使っています。
実際に利用する際にはこのようにワイルドカードを指定するのが現実的かと思います。
アニメーションの色を動的に変更する要件がある場合、アニメーションを作成するデザイナーさんに「ワイルドカードでKeypathを指定したい」旨をあらかじめ伝え、これをやりやすい構造でアニメーションを作ってもらうことをおすすめします。
アニメーション内に動的に画像を当てはめる
アプリが取得したユーザーのプロフィールアイコンをアニメーション内で使うなども可能です。
例えば、

この「誰かに電話をかけている…」ときのアニメーションに、実際に電話をかける相手のアイコンを当てはめる、といったことができます。
これも以下のように簡単なコードで実現できます。
let image: UIImage = //< 選択されたユーザーのプロフィールアイコン
let iconView = UIImageView(image: image)
let keypath = LOTKeypath(string: "avatarLayer")
animationView.addSubview(iconView, toKeypathLayer: keypath)

このコードを見ていただければ分かるように、実際には画像を当てるというよりは任意のUIViewサブクラスをアニメーション内に埋め込むことをします。
この例では、ユーザーのプロフィールアイコンのUIImageを持ったUIImageViewを作り、それを貼り付ける先のアニメーションのLayerのKeypathを指定してaddSubviewしています。
デザイナーさんがこういった構造のアニメーションを作成することに慣れてさえいれば、このように動的に変更可能なアニメーションを驚くほど簡単に作れます。
サンプルコード
今回この記事を書くにあたって作ったサンプルコードは、 GitHub からダウンロードいただけます。
その他
UIViewControllerのトランジッションでもLottieが使えるようですが、今のところ使う予定がなく未確認です。 また、機会があればその辺りも試して記事にしたいと思います。
![[HomeKit対応仕様] デロンギ マルチダイナミックヒーター](https://qiita-image-store.s3.amazonaws.com/0/7883/e2d9ecaa-7f81-761e-46ac-ca1555bdab71.png)
![[HomeKit対応仕様] Philips Hue モーションセンサー](https://qiita-image-store.s3.amazonaws.com/0/7883/a368e9a9-87fb-eb00-d425-0563e800440b.png)
![[HomeKit対応仕様] Koogeek Wi-Fiスマート LED](https://qiita-image-store.s3.amazonaws.com/0/7883/77fdcc8d-0c7a-b9d5-960d-9cb55319049f.jpeg)





