UIHositingController
でラップしたSwiftUIのView
を画面破棄する時のアニメーションを無効化にする方法を調べたので記載します。
環境
- Xcode 14
はじめに
今回はUIViewController
のViewController
からSwiftUIのView
のSwiftUIView
をUIHostingController
でラップしたものを表示して試しました。
最初の画面(ViewController)
Storyboard
コード
import UIKit
import SwiftUI
class ViewController: UIViewController {
// ボタンを押すと、UIHostingControllerでラップしたSwiftUIViewに遷移します
@IBAction private func presentNextView() {
let swiftUIViewController = UIHostingController(rootView: SwiftUIView())
swiftUIViewController.modalPresentationStyle = .overFullScreen
present(swiftUIViewController, animated: false)
}
}
次の画面(SwiftUIView)
プレビュー
コード
import SwiftUI
struct SwiftUIView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
ZStack {
Rectangle()
.fill(.black)
.ignoresSafeArea()
Button {
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
dismiss()
}
} label: {
Text("dismiss")
}
}
}
}
トランザクションを使用して画面破棄のアニメーションを無効化にする(失敗)
SwiftUIのViewから.sheet
等で表示させたViewを画面破棄する際にアニメーションを無効化させるには、withTransaction
メソッドにTransaction
を使用してアニメーションを無効化にしたカスタムアニメーションを渡すことで画面破棄のアニメーションを無効化に出きます。
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
dismiss()
}
しかし、この方法だとUIHositingController
でラップしたSwiftUIのView
を画面破棄する時のアニメーションを無効化に出来ませんでした。
デモ
しっかり画面破棄のアニメーションが表示されています。
推測
前回の背景色を透明にする時でもそうだったのですが、UIHositingController
を使用する場合は、そのラップをしているUIHositingController
側のデフォルト値が変更出来ておらず、変更が反映できていないのではないかと推測しました。
解決策
親側のViewControllerで画面破棄の処理を行う
SwiftUIView
画面破棄を行いたい箇所をdismiss()
から、dismissHandler()
に変更しています。
struct SwiftUIView: View {
let dismissHandler: () -> Void
var body: some View {
ZStack {
Rectangle()
.fill(.black)
.ignoresSafeArea()
Button {
dismissHandler()
} label: {
Text("dismiss")
}
}
}
}
ViewController
UIHostingController
のrootViewとしてSwiftUIView
を渡す際にdismissHandler
内でアニメーションを実行しないdismiss
を実行するようにしました。
import UIKit
import SwiftUI
class ViewController: UIViewController {
@IBAction private func presentNextView() {
let swiftUIViewController = UIHostingController(rootView: SwiftUIView(dismissHandler: {
self.dismiss(animated: false)
}))
swiftUIViewController.modalPresentationStyle = .overFullScreen
present(swiftUIViewController, animated: false)
}
}
デモ
無事にアニメーションを無効化に出来ました。
UIViewControllerTransitioningDelegateでアニメーションをカスタマイズする
上記の方法でdismiss
のアニメーションの無効化に成功したのですが、別の方法も試しました。
UIViewControllerTransitioningDelegate
に準拠させることで遷移アニメーションをカスタマイズ出来ます。また、UIHostingController
はUIViewController
を継承しているので、UIViewControllerTransitioningDelegate
に準拠させることが出来ます。
今回はSwiftUIView
をラップしたSwiftUIViewController
にUIViewControllerTransitioningDelegate
を準拠させました。
SwiftUIViewController
import UIKit
import SwiftUI
class SwiftUIViewController: UIHostingController<SwiftUIView> {
init() {
super.init(rootView: SwiftUIView())
self.transitioningDelegate = self
}
@MainActor required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - UIViewControllerTransitionDeleate
extension SwiftUIViewController: UIViewControllerTransitioningDelegate {
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimatedTransition()
}
}
class AnimatedTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
transitionContext.completeTransition(true)
}
}
まず、init()
実行時にtransitioningDelegate
にself
を渡します。
init() {
super.init(rootView: SwiftUIView())
self.transitioningDelegate = self
}
UIViewControllerTransitionDeleate
のanimationController(forDismissed:)
メソッドを呼び出して、画面破棄時のUIViewControllerAnimatedTransitioning
に準拠したアニメーターオブジェクトを返すように要求します。
// MARK: - UIViewControllerTransitionDeleate
extension SwiftUIViewController: UIViewControllerTransitioningDelegate {
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimatedTransition()
}
}
アニメータオブジェクトの、transitionDuration(using:)
で画面遷移の時間間隔を設定します。今回はアニメーションを無しにしたい為、0
にしています。
animateTransition(using:)
では、実行したカスタムアニメーションを記述するのですが、今回は特にアニメーションは必要ない為、completeTransition(true)
でアニメーションが終了したことのみを伝えるようにしています。
class AnimatedTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
transitionContext.completeTransition(true)
}
}
デモ
この方法でも無事に画面破棄時のアニメーションを無効化にすることが出来ました!
おわりに
SwiftUIのView
をUIViewController
と合わせて使用することもあると思いますが、UIHostingController
でラップしたView
が意図した動きをしない時はUIHostingController
側の設定が変わっていない可能性もある為、UIHostingController
の設定変更を試してみるのも良いかもしれませんね。
他に何か良い方法がありましたら教えていただければと思います。