はじめに
Lottie アニメーションを UIScrollView
がスクロールしている最中のみ動かしたいときがあったので、その実装を紹介します。
具体的に言うと、
- スクロール中: アニメーション停止
- スクロール終了: アニメーション再開
という要求です。
準備: Lottie を動かす ViewController
ScrollView の動作関係なしに、ただ Lottie を動かす ViewController のコードを紹介します。
今回はこのコードを元に実装してみます。
コメント多めに書いてますので、ある程度は追えるかと思います。
今回はなんとなくランダムな位置に複数の Lottie アニメーションを配置してみました。
import UIKit
import Lottie
class ViewController: UIViewController {
// MARK: - IBOutlet
// scrollView の子要素で、 Lottie オブジェクトが配置される UIView
@IBOutlet weak var contentView: UIView!
// MARK: - Private properties
var animations = [AnimationView]()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// ランダムな位置に 3 個の Lottie オブジェクトを生成・配置・再生する
// 複数配置していることに特に意味はありません。
for _ in 1...3 {
let x = CGFloat.random(in: 1.0...5.0) * 200.0
let y = CGFloat.random(in: 1.0...6.0) * 100.0
let animation = generateAnimation(x: x, y: y)
animations.append(animation)
contentView.addSubview(animation)
animation.play()
}
}
// MARK: - Private methods
// 引数で受け取った座標を持つ Lottie オブジェクトを生成するメソッド
// see: https://qiita.com/ngo275/items/c9e94bad7a7afc85e4f4
private func generateAnimation(x: CGFloat, y: CGFloat) -> AnimationView {
let animationView = AnimationView(name: "sample_animation")
animationView.frame = CGRect(x: x, y: y, width: view.bounds.width * 0.2, height: view.bounds.height * 0.2)
animationView.loopMode = .loop
animationView.contentMode = .scaleAspectFit
animationView.animationSpeed = 1
return animationView
}
}
スクロール中だけアニメーションを止めてみよう
このままでは viewDidLoad
ライフサイクルで play()
されっぱなしで、止めようがありません。
今回の要求として、「スクロール中はアニメーションを止める」というのがありますので、まずは UIScrollView
を Outlet 接続しましょう。
// MARK: - IBOutlet
// scrollView の子要素で、 Lottie オブジェクトが配置される UIView
@IBOutlet weak var contentView: UIView!
@IBOutlet weak var backgroundScrollView: UIScrollView! // 追加
次に、追加した backgroundScrollView
の delegate 先を self
つまり ViewController
自身に設定します。
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
backgroundScrollView.delegate = self // 追加
これで ScrollView の delegate メソッドが使えるようになりました。
UIScrollView
の delegate メソッドは沢山ありますが、今回は
- スクロールのし初め
- スクロールの終わり
を検知することで
- スクロールのし初め: アニメーションを一時停止
- スクロールの終わり: アニメーションを再開
という風にして実装していきます。
沢山ある delegate メソッドの解説は 公式ドキュメント や 他の方がまとめてくださった記事 に譲って割愛します。
今回使うのは、以下の 2 つです。
// 1.
// 指が画面に触れ、スクロールが開始した瞬間に呼ばれるメソッド
func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
// 2.
// 指が画面から離れ、慣性のスクロールが完全に止まった瞬間に呼ばれるメソッド
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
これらのメソッドを使えば、以下のようにして、簡単にスクロール中にアニメーションを止めて、スクロールが終わったらアニメーションを再開するということが可能になります。
ちなみに、 stop()
メソッドを使用していますが、 pause()
メソッドでも大丈夫です。
アニメーションが最後まで行って止まるか、 pause()
された瞬間に止まるかの違いです。
// 指が画面に触れ、スクロールが開始した瞬間に呼ばれるメソッド
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print(#function) // どの関数が呼ばれているか確認用に表示
// スクロール開始と同時にアニメーションをストップ
animations.forEach { $0.stop() }
}
// 指が画面から離れ、慣性のスクロールが完全に止まった瞬間に呼ばれるメソッド
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print(#function) // どの関数が呼ばれているか確認用に表示
// スクロール終了と同時にアニメーションをスタート
animations.forEach { $0.play() }
}
あとは、これを実装して終わりです。
ViewController に UIScrollViewDelegate
を継承させます。
僕は extension して書いていますが、 class ViewController: UIViewController, UIScrollViewDelegate
としても何も問題ありません。
// MARK: - UIScrollViewDelegate
extension ViewController: UIScrollViewDelegate {
// 指が画面に触れ、スクロールが開始した瞬間に呼ばれるメソッド
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print(#function) // どの関数が呼ばれているか確認用に表示
// スクロール開始と同時にアニメーションをストップ
animations.forEach { $0.stop() }
}
// 指が画面から離れ、慣性のスクロールが完全に止まった瞬間に呼ばれるメソッド
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print(#function) // どの関数が呼ばれているか確認用に表示
// スクロール終了と同時にアニメーションをスタート
animations.forEach { $0.play() }
}
}
これでビルドしてみると、スクロールしている間だけアニメーションが止まることを確認できるはずです。
最後に、完成した ViewController を載せます。
import UIKit
import Lottie
class ViewController: UIViewController {
// MARK: - IBOutlet
// scrollView の子要素で、 Lottie オブジェクトが配置される UIView
@IBOutlet weak var contentView: UIView!
@IBOutlet weak var backgroundScrollView: UIScrollView!
// MARK: - Private properties
var animations = [AnimationView]()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
backgroundScrollView.delegate = self
// ランダムな位置に 3 個の Lottie オブジェクトを生成・配置・再生する
// 複数配置していることに特に意味はありません。
for _ in 1...3 {
let x = CGFloat.random(in: 1.0...5.0) * 200.0
let y = CGFloat.random(in: 1.0...6.0) * 100.0
let animation = generateAnimation(x: x, y: y)
animations.append(animation)
contentView.addSubview(animation)
animation.play()
}
}
// MARK: - Private methods
// 引数で受け取った座標を持つ Lottie オブジェクトを生成するメソッド
// see: https://qiita.com/ngo275/items/c9e94bad7a7afc85e4f4
private func generateAnimation(x: CGFloat, y: CGFloat) -> AnimationView {
let animationView = AnimationView(name: "sample_animation")
animationView.frame = CGRect(x: x, y: y, width: view.bounds.width * 0.2, height: view.bounds.height * 0.2)
animationView.loopMode = .loop
animationView.contentMode = .scaleAspectFit
animationView.animationSpeed = 1
return animationView
}
}
// MARK: - UIScrollViewDelegate
extension ViewController: UIScrollViewDelegate {
// 指が画面に触れ、スクロールが開始した瞬間に呼ばれるメソッド
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print(#function) // どの関数が呼ばれているか確認用に表示
// スクロール開始と同時にアニメーションをストップ
animations.forEach { $0.stop() }
}
// 指が画面から離れ、慣性のスクロールが完全に止まった瞬間に呼ばれるメソッド
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print(#function) // どの関数が呼ばれているか確認用に表示
// スクロール終了と同時にアニメーションをスタート
animations.forEach { $0.play() }
}
}