SwiftUI:如何从NavigationView访问UINavigationController

问题描述 投票:0回答:4

我正在使用 SwiftUI 开发一个应用程序。该应用程序基于

NavigationView

我正在使用提供

UIKit
组件的第三方框架,并且该框架尚未更新以支持 SwiftUI。

一个框架方法需要

UINavigationController

类型的参数

我如何向这个框架提供由 SwiftUI 创建的

NavigationControlle
r ?或者我如何创建一个
UINavigationController
来替换 SwiftUI 的默认值?

我读了https://developer.apple.com/tutorials/swiftui/interface-with-uikithttps://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

uinavigationcontroller uikit swiftui
4个回答
12
投票

感谢 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 
                // ...
            }
        }
    }
}

11
投票

我认为你现在做不到。查看视图调试器的

NavigationView
我得到下面的图像。

所以看来你必须反其道而行之:

UINavigationController
开始,并将 SwiftUI 视图包装在
UIHostingController
中。


0
投票

我尝试做同样的事情,因为我想让 InteractivePopGestureRecognizer 在整个视图上工作。

我设法使用 UINavigationController 扩展并覆盖 viewDidAppear 访问当前的导航控制器,检查是否启用了 InteractivePopGestureRecognizer 并更改了它(https://stackoverflow.com/a/58068947/1745000

最终我的努力毫无意义。当导航视图呈现 DetailHostingController 时,它关闭了 InteractivePopGestureRecognizer.isEnabled!

通过 topViewController.view 的托管视图确实包含私有类型 SwiftUI.UIGestureRecognizer 的手势识别器。但没有设定目标...

嵌入传统的 UINavigationController 也可能是首选,因为导航视图自己的弹出手势不可取消(如果您稍微拖动视图并停止,它会弹回来,然后关闭详细视图。


0
投票

获取 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)
© www.soinside.com 2019 - 2024. All rights reserved.