Balbas Code

SwiftでPopupアニメーションの共通化

公開日: 2024-10-27 09:15:49
更新日: 2024-10-27 09:20:41

前回書いたPopupの記事の続きで、アニメーションを共通で管理したので記載していきます。
こちらにPopupのアニメーションを記載しました。
こちらを採用するとアニメーションをそれぞれのPopup用のクラスで書かなくても良くなります。
コードのポイントは
・ViewDidAppearで開始時のアニメーションを共通化から設定
・Popupを開く処理を共通化から参照
・閉じる時にアニメーションを共通化から参照

となっています。

CommonAnimation


/// Popupの表示時のアニメーションを管理するクラス
class CommonAnimation {

// ポップアップをスライドインする(下から)
static func animateSlideIn(view: UIView, fromViewController vc: UIViewController, completion: (() -> Void)? = nil) {
view.transform = CGAffineTransform(translationX: 0, y: vc.view.frame.height)
UIView.animate(withDuration: 0.3, animations: {
view.transform = .identity
}, completion: { _ in
completion?()
})
}

// ポップアップをスライドアウトする(下へ)
static func animateSlideOut(view: UIView, fromViewController vc: UIViewController, completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
view.transform = CGAffineTransform(translationX: 0, y: vc.view.frame.height)
}, completion: { _ in
completion()
})
}

// ポップアップを横にスライドインする(右から)
static func animateSlideInFromRight(view: UIView, fromViewController vc: UIViewController, completion: (() -> Void)? = nil) {
view.transform = CGAffineTransform(translationX: vc.view.frame.width, y: 0)
UIView.animate(withDuration: 0.3, animations: {
view.transform = .identity
}, completion: { _ in
completion?()
})
}

// ポップアップを横にスライドアウトする(右へ)
static func animateSlideOutToRight(view: UIView, fromViewController vc: UIViewController, completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
view.transform = CGAffineTransform(translationX: vc.view.frame.width, y: 0)
}, completion: { _ in
completion()
})
}

// フェードインアニメーション
static func animateFadeIn(view: UIView, duration: TimeInterval = 0.3, completion: (() -> Void)? = nil) {
view.alpha = 0
UIView.animate(withDuration: duration, animations: {
view.alpha = 1
}, completion: { _ in
completion?()
})
}

// フェードアウトアニメーション
static func animateFadeOut(view: UIView, duration: TimeInterval = 0.3, completion: @escaping () -> Void) {
UIView.animate(withDuration: duration, animations: {
view.alpha = 0
}, completion: { _ in
completion()
})
}

// スケールインアニメーション(拡大しながら表示)
static func animateScaleIn(view: UIView, duration: TimeInterval = 0.3, completion: (() -> Void)? = nil) {
view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: duration, animations: {
view.transform = .identity
}, completion: { _ in
completion?()
})
}

// スケールアウトアニメーション(縮小しながら非表示)
static func animateScaleOut(view: UIView, duration: TimeInterval = 0.3, completion: @escaping () -> Void) {
UIView.animate(withDuration: duration, animations: {
view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
}, completion: { _ in
completion()
})
}

// 共通のポップアップ表示処理
static func presentPopup(currentViewController: UIViewController, nextPopup: UIViewController, completion: (() -> Void)? = nil) {
nextPopup.modalPresentationStyle = .overCurrentContext
currentViewController.present(nextPopup, animated: false, completion: completion)
}

// ポップアップを閉じる処理
static func closePopup(viewController: UIViewController, popupView: UIView, completion: @escaping () -> Void) {
animateSlideOut(view: popupView, fromViewController: viewController) {
viewController.dismiss(animated: false, completion: {
completion()
})
}
}
}



共通化しない場合の記載(TestPopupView1)


    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// ポップアップを画面下端よりも外側に初期位置を設定(画面の高さ分だけ下に移動)
popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)

// ポップアップをスライドイン(元の位置へ移動)
UIView.animate(withDuration: 0.3) {
self.popupView.transform = .identity
self.view.layoutIfNeeded()
}
}

@objc private func okButtonTapped() {
// TestPopupView2 を重ねて表示
let popup2 = TestPopupView2()
popup2.modalPresentationStyle = .overCurrentContext

// TestPopupView2が閉じられた際に、自分も閉じるようにする ■■■completion{}に実行後に行う処理を記載する
popup2.onComplete = { [weak self] in
self?.animateClose {
self?.dismiss(animated: false, completion: {
self?.onComplete?() // 遷移元(ViewController)に完了通知
})
}
}
present(popup2, animated: false, completion: nil)
}

// 閉じるアニメーション(画面外にスライド)
private func animateClose(completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
// ポップアップを画面外(下端よりも下)に移動
self.popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)
}, completion: { _ in
completion()
})
}



共通化した記載(TestPopupView1)
右から出現するアニメーションを使用


    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// スライドインアニメーションを適用 初期設定として必要
CommonAnimation.animateSlideInFromRight(view: popupView, fromViewController: self)
}

@objc private func okButtonTapped() {
// TestPopupView2_を表示する処理
let popup2 = TestPopupView2_()
CommonAnimation.presentPopup(currentViewController: self, nextPopup: popup2)

// TestPopupView2が閉じられた際に自分を閉じる処理
popup2.onComplete = { [weak self] in
guard let self = self else { return }

// 右にスライドしてポップアップを閉じる
CommonAnimation.animateSlideOutToRight(view: self.popupView, fromViewController: self) {
// アニメーション終了後にポップアップを閉じる
self.dismiss(animated: false, completion: {
self.onComplete?() // 遷移元(ViewController)に完了通知を送る
})
}
}
}

これだけ省略できました。


共通化しない場合の記載(TestPopupView2)


    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// ポップアップを画面下端よりも外側に初期位置を設定(画面の高さ分だけ下に移動)
popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)

// ポップアップをスライドイン(元の位置へ移動)
UIView.animate(withDuration: 0.3) {
self.popupView.transform = .identity
self.view.layoutIfNeeded()
}
}

@objc private func okButtonTapped() {
// TestPopupView3 を重ねて表示
let popup3 = TestPopupView3()
popup3.modalPresentationStyle = .overCurrentContext

// TestPopupView3が閉じられた際に、自分も閉じるようにする ■■■completion{}に実行後に行う処理を記載する
popup3.onComplete = { [weak self] in
self?.animateClose {
self?.dismiss(animated: false, completion: {
self?.onComplete?() // 遷移元(TestPopupView2)に完了通知
})
}
}
present(popup3, animated: false, completion: nil)
}

// 閉じるアニメーション(画面外にスライド)
private func animateClose(completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
// ポップアップを画面外(下端よりも下)に移動
self.popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)
}, completion: { _ in
completion()
})
}


共通化した記載(TestPopupView2)
拡大して表示、縮小して閉じるアニメーションを使用


    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// スライドインアニメーションを適用 初期設定として必要
CommonAnimation.animateScaleIn(view: popupView)
}

@objc private func okButtonTapped() {
// TestPopupView3_を表示する処理
let popup3 = TestPopupView3_()
CommonAnimation.presentPopup(currentViewController: self, nextPopup: popup3)

// TestPopupView3_が閉じられた際に自分を閉じる処理
popup3.onComplete = { [weak self] in
guard let self = self else { return }

// スケールアウトしてポップアップを閉じる
CommonAnimation.animateScaleOut(view: self.popupView, duration: 0.3) {
// アニメーション終了後にポップアップを閉じる
self.dismiss(animated: false, completion: {
self.onComplete?() // 遷移元(TestPopupView1_)に完了通知を送る
})
}
}
}


共通化しない場合の記載(TestPopupView3)


    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// ポップアップを画面下端よりも外側に初期位置を設定(画面の高さ分だけ下に移動)
popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)

// ポップアップをスライドイン(元の位置へ移動)
UIView.animate(withDuration: 0.3) {
self.popupView.transform = .identity
self.view.layoutIfNeeded()
}
}

@objc private func okButtonTapped() {
// 自分を閉じて、上に通知 ■■■completion{}に実行後に行う処理を記載する
animateClose {
self.dismiss(animated: false, completion: {
self.onComplete?() // 遷移元(TestPopupView2)に完了通知
})
}
}

// 閉じるアニメーション(画面外にスライド)
private func animateClose(completion: @escaping () -> Void) {
UIView.animate(withDuration: 0.3, animations: {
// ポップアップを画面外(下端よりも下)に移動
self.popupView.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height)
}, completion: { _ in
completion()
})
}


共通化した記載(TestPopupView3)
フェードインアニメーションを使用


    override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

// スライドインアニメーションを適用 初期設定として必要
CommonAnimation.animateFadeIn(view: popupView)
}

@objc private func okButtonTapped() {
// スケールアウトしてポップアップを閉じる
CommonAnimation.animateFadeOut(view: self.popupView, duration: 0.3) {
// アニメーション終了後にポップアップを閉じる
self.dismiss(animated: false, completion: {
self.onComplete?() // 遷移元(TestPopupView2_)に完了通知を送る
})
}
}

コード自体がすごくスッキリするようになったのと、こちらに登録しておけば色々なアニメーションを簡単に呼び出せるようになりました。

実行結果
swiftPopupAnimationswiftPopupAnimationswiftPopupAnimationswiftPopupAnimationswiftPopupAnimation
前回の続きで横や拡大縮小のアニメーションでポップアップが重ねられていき、最後の白いView (TestPopupView3)のOKボタンを押下すると設定したアニメーションで順番に閉じていき、最後にメッセージを表示します。

ViewDidAppearには書かずにボタンを押下した時の処理のみでいけないかを試みたのですが、グレーのViewがくっついて移動してしまうようになったり、アニメーションが動かなくなったりしたので、一番スッキリかけたこちらを採用しました。