画面遷移処理を各画面から切り離したり、カスタムURLスキームなどを使って任意の画面に遷移できるようにする処理をSwiftUIでもやりたい。でもSwiftUIだけで実現する方法がわからない。。。
難しそうなところは今後のSwiftUIの進化に期待するということで、画面遷移は無理せずUIKitベースでやってしまえば良さそうだなと思い始めました。
環境
- Xcode 12.0
実装
各画面のレイアウトはSwiftUIでさくっと作ってしまって画面遷移に関連する部分はUIKitベースで処理するために、遷移先はUIHostingControllerを使う。ViewControllerを見つける処理は従来通り。
var window: UIWindow? {
guard let window = (UIApplication.shared.connectedScenes.first?.delegate as? UIWindowSceneDelegate)?.window else { return nil }
return window
}
/// 全面に表示されているViewControllerを見つける
func topViewController(_ vc: UIViewController? = nil) -> UIViewController? {
guard let vc = vc ?? window?.rootViewController else { return nil }
if let presented = vc.presentedViewController {
return topViewController(presented)
}
return vc
}
/// NavigationControllerを見つける
func navigationController(_ vc: UIViewController) -> UINavigationController? {
if let result = vc as? UINavigationController {
return result
}
for child in vc.children {
if let result = navigationController(child) {
return result
}
}
return nil
}
@main
struct MainApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { _ in
guard let topVC = topViewController() else { return }
if let navVC = navigationController(topVC) {
navVC.show(UIHostingController(rootView: TestView()), sender: nil)
} else {
topVC.present(UIHostingController(rootView: NavigationView(content: { TestView() })),
animated: true,
completion: nil)
}
})
}
}
}
SwiftUIで作った画面をXcodeのDebug View Hierarchyで見てみると、ViewControllerらしきコンポーネントがたくさん使われているようだったので、上記の実装は「NavigationViewを使えばUINavigationControllerが内部的には使われているかもしれない」とか、「sheetでモーダル表示したらpresentedViewControllerで遷移先のViewControllerを見つけられるかもしれない」という思い込みで実装してみました。
SwiftUIのNavigationViewを使っている場合にUINavigationControllerを探索可能かどうか不明でしたが、UINavigationControllerを継承していそうなクラスが使われているようでした。