はじめに
アプリでモーダル遷移するときにデフォルトのアニメーションがいくつか用意されていますが、それ以外のアニメーションを使いたいときは、自分でカスタムすることができるようです。
今回は、UIViewControllerAnimatedTransitioning
を使って、横スワイプしたときのようなアニメーションを実装してみました。
実装イメージ
コード
モーダルを呼び出す側のコード
BaseViewController.swift
import UIKit
class BaseViewController: UIViewController {
@IBOutlet weak var buttonShow: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func showModalView(_ sender: Any) {
guard let modalVC = UIStoryboard(name: String(describing: CustomModalViewController.self), bundle: nil)
.instantiateInitialViewController() as? CustomModalViewController else { return }
modalVC.modalPresentationStyle = .fullScreen
present(modalVC, animated: true)
}
}
モーダルとして呼び出される側のコード
CustomModalViewController.swift
import UIKit
class CustomModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
transitioningDelegate = self
}
@IBAction func touchCloseButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
// モーダルとして呼び出す側に「UIViewControllerTransitioningDelegate」を継承させる。
// MARK: UIViewControllerTransitioningDelegate
extension CustomModalViewController: UIViewControllerTransitioningDelegate {
// presentのときに呼ばれる
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CustomAnimater(animationMode: .present)
}
// dismissのときに呼ばれる
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CustomAnimater(animationMode: .dismiss)
}
}
アニメーションを定義するクラス
CustomAnimeter.swift
class CustomAnimater: NSObject {
// 開くと閉じるしかないので、enumで定義しておくと楽
enum AnimationMode {
case present, dismiss
}
private let animationMode: AnimationMode
private let duration = 0.3
init(animationMode: AnimationMode) {
self.animationMode = animationMode
}
}
extension CustomAnimater: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if animationMode == .present {
guard let toView = transitionContext.view(forKey: .to) else { return }
guard let fromView = transitionContext.view(forKey: .from) else { return }
transitionContext.containerView.addSubview(toView)
toView.alpha = 0.0
toView.transform = CGAffineTransform(translationX:fromView.bounds.size.width * 0.8 , y: 0)
UIView.animate(withDuration: 0.5, delay: 0) { [weak self] in
guard let _ = self else { return }
toView.transform = CGAffineTransform(translationX: 0, y: 0)
toView.alpha = 1.0
} completion: { finish in
toView.transform = .identity
transitionContext.completeTransition(finish)
}
} else {
guard let toView = transitionContext.view(forKey: .to) else { return }
guard let fromView = transitionContext.view(forKey: .from) else { return }
transitionContext.containerView.addSubview(toView)
transitionContext.containerView.addSubview(fromView)
fromView.alpha = 1
UIView.animate(withDuration: 0.5, delay: 0) {
fromView.alpha = 0.0
fromView.transform = CGAffineTransform(translationX:-fromView.bounds.size.width * 0.8 , y: 0)
} completion: { finish in
// transformをもとに戻さないとバグの要因になることがあるみたい
fromView.transform = .identity
transitionContext.completeTransition(finish)
}
}
}
}