我正在使用 SwiftUI 开发一个应用程序。该应用程序基于
NavigationView
。
我正在使用提供
UIKit
组件的第三方框架,并且该框架尚未更新以支持 SwiftUI。
一个框架方法需要
UINavigationController
类型的参数
我如何向这个框架提供由 SwiftUI 创建的
NavigationControlle
r ?或者我如何创建一个 UINavigationController
来替换 SwiftUI 的默认值?
我读了https://developer.apple.com/tutorials/swiftui/interface-with-uikit和https://sarunw.com/posts/uikit-in-swiftui但这些似乎解决了另一个问题:他们解释了如何在 SwiftUI 应用程序中使用 UIKit 组件。我的问题是相反的,我想使用 SwiftUI App 并访问底层的 NavigationController 对象。
[更新] 本研讨会提供了实现我的解决方案的代码:https://amplify-ios-workshop.go-aws.com/30_add_authentication/20_client_code.html#loginviewcontroller-swift
感谢 Yonat 的解释,我明白了如何做到这一点,这是我的解决方案,希望对其他人有帮助。
第 1 部分:将从 Swift UI 使用的 UI 视图控制器。它调用第三方身份验证库,并传递
UINavigationControler
作为参数。 UINavigationController
是一个空视图,只是为了让第三方身份验证库有一个导航控制器来弹出登录屏幕。
struct LoginViewController: UIViewControllerRepresentable {
let navController = UINavigationController()
func makeUIViewController(context: Context) -> UINavigationController {
navController.setNavigationBarHidden(true, animated: false)
let viewController = UIViewController()
navController.addChild(viewController)
return navController
}
func updateUIViewController(_ pageViewController: UINavigationController, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject {
var parent: LoginViewController
init(_ loginViewController: LoginViewController) {
self.parent = loginViewController
}
}
func authenticate() {
let app = UIApplication.shared.delegate as! AppDelegate
let userData = app.userData
userData.authenticateWithDropinUI(navigationController: navController)
}
}
第 2 部分:Swift UI 视图正在显示(空)UINavigationControler 并在其顶部覆盖 SwiftUI 视图。
import SwiftUI
struct LandingView: View {
@ObservedObject public var user : UserData
var body: some View {
let loginView = LoginViewController()
return VStack {
// .wrappedValue is used to extract the Bool from Binding<Bool> type
if (!$user.isSignedIn.wrappedValue) {
ZStack {
loginView
// build your welcome view here
Button(action: { loginView.authenticate() } ) {
UserBadge().scaleEffect(0.5)
}
}
} else {
// my main app view
// ...
}
}
}
}
我尝试做同样的事情,因为我想让 InteractivePopGestureRecognizer 在整个视图上工作。
我设法使用 UINavigationController 扩展并覆盖 viewDidAppear 访问当前的导航控制器,检查是否启用了 InteractivePopGestureRecognizer 并更改了它(https://stackoverflow.com/a/58068947/1745000)
最终我的努力毫无意义。当导航视图呈现 DetailHostingController 时,它关闭了 InteractivePopGestureRecognizer.isEnabled!
通过 topViewController.view 的托管视图确实包含私有类型 SwiftUI.UIGestureRecognizer 的手势识别器。但没有设定目标...
嵌入传统的 UINavigationController 也可能是首选,因为导航视图自己的弹出手势不可取消(如果您稍微拖动视图并停止,它会弹回来,然后关闭详细视图。
获取 UINavigationController 的可能解决方案是使用 NavigationView 后由 SwiftUI 在后台创建的,如下所示。
首先创建一个视图修饰符,该修饰符创建一个用作钩子的视图,以获取当前视图层次结构中存在的 UIViewController:
class VCHookViewController: UIViewController {
var onViewWillAppear: ((UIViewController) -> Void)?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
onViewWillAppear?(self)
}
}
struct VCHookView: UIViewControllerRepresentable {
typealias UIViewControllerType = VCHookViewController
let onViewWillAppear: ((UIViewController) -> Void)
func makeUIViewController(context: Context) -> VCHookViewController {
let vc = VCHookViewController()
vc.onViewWillAppear = onViewWillAppear
return vc
}
func updateUIViewController(_ uiViewController: VCHookViewController, context: Context) {
}
}
struct VCHookViewModifier: ViewModifier {
let onViewWillAppear: ((UIViewController) -> Void)
func body(content: Content) -> some View {
content
.background {
VCHookView(onViewWillAppear: onViewWillAppear)
}
}
}
extension View {
func onViewWillAppear(perform onViewWillAppear: @escaping ((UIViewController) -> Void)) -> some View {
modifier(VCHookViewModifier(onViewWillAppear: onViewWillAppear))
}
}
然后在 SwiftUI 代码中使用此修饰符来获取现有的 UINavigationController,如下所示:
NavigationView {
VStack {
Text("hello")
}
.onViewWillAppear(perform: { vc in
print("UINavigationController: \(vc.navigationController)") // and here you have it
})
.navigationTitle("Test")
}
.navigationViewStyle(.stack)