Lottieを使ってアプリのUXを高める

[GameWith Advent Calendar 2018](https://qiita.com/advent-calendar/2018/gamewith) 16日目担当の[kyam](https://qiita.com/kyam_)です。
今回は自分が好きなライブラリであるLottieを紹介します。
Lottieの存在を知っている人は多いと思いますが、イマイチ何に使えばいいのか分からない、どこに使えばいいのか分からないといった方向けに今回は気軽に導入できる実例を紹介しようと思います。

# 概要

- アニメーションライブラリ Lottie について
- Lottie導入と利用方法
- 実例紹介

## Lottieについて

- AirBnB社が開発したアニメーションライブラリ
- デザイナーがAfter Effectsで作成したアニメーションを簡単に組み込める
- <b>iOS, Android, React Native</b>に対応
- Facebook社のライブラリ[Keyframes](https://github.com/facebookincubator/Keyframes)よりも機能が豊富

![Examples2.gif](https://qiita-image-store.s3.amazonaws.com/0/66235/acb2c068-bcff-11c3-3bb6-457535f6ed08.gif)

## 導入と利用方法

### デザイナー側
- After Effectsのプラグイン[Bodymovin](https://www.adobeexchange.com/creativecloud.details.12557.html)を用いてアニメーションのjsonファイルを出力
- そのファイルをエンジニア側に渡す

### エンジニア側
- Cocoapods or Carthageでライブラリのインストール
- [lottie-ios](https://github.com/airbnb/lottie-ios)のライブラリを用いてjsonファイルを読み込んで表示
 
デザイナーから画像ファイルなどを貰う必要はありません。
jsonファイルのダウンロードにも対応しているので、外部にファイルを置いてA/Bテストなども容易にできます。


## 実例紹介

### UIRefreshControlをlottie製に

<img src="https://qiita-image-store.s3.amazonaws.com/0/66235/12f7cec9-3139-fe48-73ab-ba4b2f6a01bf.gif" width="200">

```swift
final class RefreshControl: UIRefreshControl {
    private lazy var loadingView: LOTAnimationView = {
        let loadingView = LOTAnimationView(name: "loading")
        loadingView.contentMode = .scaleAspectFill
        self.addSubview(loadingView)
        return loadingView
    }()
    override init(frame: CGRect) {
        super.init(frame: .zero)
        setup()
        makeConstraints()
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func setup() {
        backgroundColor = .clear
        tintColor = .clear
    }
    private func makeConstraints() {
        loadingView.snp.makeConstraints({ (make) in
            make.center.equalToSuperview()
            make.size.equalTo(150)
        })
    }
}

extension RefreshControl {
    func startLoading() {
        loadingView.play()
    }
    func stopLoading() {
        loadingView.stop()
        endRefreshing()
    }
}
```

### UIActivityIndicatorの代わりにLOTAnimationViewを使う

<img src="https://qiita-image-store.s3.amazonaws.com/0/66235/e35435ee-85ae-bbb2-4a99-42e08a6b7255.gif" width="200">

```swift
private let spinnerTag = 1000

extension UIViewController {
    func startActivityIndicator() {
        run_main(sync: false) {
            let loadingView = LOTAnimationView(name: "loading")
            loadingView.contentMode = .scaleAspectFill
            loadingView.tag = spinnerTag
            self.view.addSubview(loadingView)
            loadingView.play()
            loadingView.snp.makeConstraints({ (make) in
                make.size.equalTo(150)
                make.center.equalToSuperview()
            })
        }
    }
    func stopActivityIndicator() {
        run_main(sync: false) {
            if let loadingView = self.view.subviews.filter({$0.tag == spinnerTag}).first as? LOTAnimationView {
                loadingView.stop()
                loadingView.removeFromSuperview()
            }
        }
    }
}
```

※ AutoLayoutの部分は`SnapKit`のライブラリを用いた書き方をしています。
※ `run_main` はよくある`DispathQueue.main`を扱いやすくしたものです。

```swift
func run_main(sync: Bool, delay: Double = 0, block: @escaping () -> Void) {
    if sync {
        if Thread.isMainThread {
            block()
        } else {
            DispatchQueue.main.sync(execute: block)
        }
    } else {
        if delay == 0 {
            DispatchQueue.main.async(execute: block)
        } else {
            let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
            DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: block)
        }
    }
}
```

## まとめ
- 導入や実装が容易のため<b>エンジニアサイドの実装コストが激減</b>します。
- デザイナーさんが作ったアニメーションが再現できる

エンジニアのレベル感にもよりますが、デザイナーさんが作ったアニメーションをフルスクラッチで再現するのは物によっては結構大変です。
ですがlottie-iosを使えばデザイナーさんの作業コストはかかりますがデザイナーさんにとっても希望のアニメーションをエンジニア側がそのまま利用できるのでwinwinかもしれません。

まだ全てのAfterEffectsの機能に対応しているわけではないため、必ずしも理想のアニメーションが作れるわけではないですが是非一度デザイナーさんと一緒に触って見てください！

[lottiefiles](https://www.lottiefiles.com/)を見ているだけでも楽しいです:cat2: