UIViewControllerRepresentable:NavigationView中导航标题和栏按钮项被忽略

28
  • 我有一个UITableViewController的子类,我已经用UIViewControllerRepresentable进行了包装。
  • 我在我的视图控制器中设置了navigationItem.titlenavigationItem.leftBarButtonItems
  • 我将我的UIViewControllerRepresentable实例呈现为SwiftUI NavigationView中的SwiftUI NavigationLink的目标。
  • 当视图被推入导航栏堆栈时,表格视图出现了,但标题和导航栏按钮项没有出现。

这里发生了什么?


你能否提供一些代码以便复现吗? - krjw
5个回答

62

解决了!

问题和期望 SwiftUI 在底层使用 UINavigationController。因此,如果我使用 UIViewControllerRepresentable 将一个 UIViewController 推入 SwiftUI NavigationView 中,那么我期望该视图控制器的导航项和工具栏项将被导航控制器使用。正如我上面提到的,它们被忽略了。

根本原因 事实证明,标题和项目被忽略是因为我的视图控制器的父级不是预期的 UINavigationController。相反,它的父级是由 SwiftUI 在底层使用的中间包装视图控制器,而该包装视图控制器又被推入导航控制器中。它忽略了标题和项目,因为导航控制器要求包装器提供其项目(其中没有任何项目),而不是我的视图控制器。

UIKit 解决方案 因此,如果您想要从 UIKit 视图控制器设置标题或工具栏项或 toolbarItems,则需要在其父级上设置它们,如下所示:

self.parent?.navigationItem.title = "My Title"

此外,你不能在viewDidLoad中这样做,因为在那个时候,视图控制器似乎还没有被SwiftUI父视图包装。你必须在viewWillAppear中执行。

SwiftUI解决方案 您还可以从SwiftUI设置标题和工具栏按钮。在您的UIViewControllerRepresentable实例上,只需添加.navigationBarTitle和leading/trailing项目,就像通常一样。然后,您可以从您的UIViewControllerRepresentable实现中让按钮与您的视图控制器交互。


我正在使用你提到的SwiftUI方式,但我的返回按钮不起作用。 - user832
使用 didMove(toParent)/willMove(toParent) 可以吗? - Lorenzo Fiamingo
5
你真的很厉害,能够找出问题所在。我无论如何都想不出为什么我们使用SwiftUI中的视图控制器时,自定义导航项会消失。 - Xaxxus
2
只对那些感兴趣的人,我通过更新我的 UIViewControllerRepresentable 对象内部的父级导航项来使它工作,如下所示: vc.parent?.navigationItem.title = vc.navigationItem.title vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems }``` - Santiago Carmona González
很棒的解决方案!我需要将这个移到viewWillAppear,因为在viewDidLoad中父级还没有设置。现在完美地运行了! - undefined

19

这里是一个UIKit的解决方案,不需要对您的UIViewController进行内部更改,并且只应在将包装器添加为父级时调用一次。

struct MyViewControllerRepresentable: UIViewControllerRepresentable {

    class Coordinator {
        var parentObserver: NSKeyValueObservation?
    }

    func makeUIViewController(context: Self.Context) -> MyViewController {
        let viewController =  MyViewController()
        context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in
            vc.parent?.title = vc.title
            vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems
        })
        return viewController
    }

    func updateUIViewController(_ uiViewController: MyViewController, context: Self.Context) {}

    func makeCoordinator() -> Self.Coordinator { Coordinator() }
}

11

来自 josephap 的好建议,但他的 UIKit 解决方案让我的导航栏标题出现了“闪烁”,不像通常那样平滑。

我的解决方案只是从我的 SwiftUI 中添加导航标题:

NavigationLink(destination: <YourUIViewControllerRepresentable>()
                                    .edgesIgnoringSafeArea([.top,.bottom])
                                    .navigationTitle(item.name)) {
                        Text(item.name)
                    }) 

1
并不完全是这样,因为在旧版SDK中,导航标题需要在UIViewController内部进行一些工作后才能设置,并且不能事先完成。 - TruMan1
1
谢谢你提供了.edgesIgnoringSafeArea的提示,我一直在想为什么我的UIKit视图没有完全覆盖屏幕。 - Jeremy

4

当leftBarButtonItems在运行时发生变化时,我使用一个键值观察者来额外处理。例如,当您使用UIActions进行状态更改以显示和隐藏复选标记时,您需要重新加载leftBarButtonItems。

context.coordinator.leftBarButtonItemsObserver = viewController.observe(\.navigationItem.leftBarButtonItems, changeHandler: { vc, _ in
    vc.parent?.navigationItem.leftBarButtonItems = vc.navigationItem.leftBarButtonItems
})

所以完整代码应该是这样的:
struct MyViewControllerRepresentable: UIViewControllerRepresentable {

    class Coordinator {
        var parentObserver: NSKeyValueObservation?
        var leftBarButtonItemsObserver: NSKeyValueObservation?
    }

    func makeUIViewController(context: Self.Context) -> MyViewController {
        let viewController =  MyViewController()
        context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in
            vc.parent?.title = vc.title
            vc.parent?.navigationItem.leftBarButtonItems = vc.navigationItem.leftBarButtonItems
        })
        
        context.coordinator.leftBarButtonItemsObserver = viewController.observe(\.navigationItem.leftBarButtonItems, changeHandler: { vc, _ in
            vc.parent?.navigationItem.leftBarButtonItems = vc.navigationItem.leftBarButtonItems
        })
        
        return viewController
    }

    func updateUIViewController(_ uiViewController: MyViewController, context: Self.Context) {}

    func makeCoordinator() -> Self.Coordinator { Coordinator() }
}

1
当您从SwiftUI NavigationView中呈现一个UIViewController(包装在UIViewControllerRepresentable中),父级SwiftUI视图的导航栏可能不会自动转移到UIKit上下文。为了正确呈现带有导航栏的UIKit视图控制器,您需要将其嵌入到UIViewControllerRepresentable包装器中的UINavigationController中。
import SwiftUI
import UIKit

struct UIKitViewControllerWrapper: UIViewControllerRepresentable {
    typealias UIViewControllerType = UINavigationController
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<UIKitViewControllerWrapper>) -> UINavigationController {
        let viewController = CustomUIKitViewController() // Your custom UIViewController
        let navigationController = UINavigationController(rootViewController: viewController)
        return navigationController
    }
    
    func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext<UIKitViewControllerWrapper>) {
        // Update the UIViewController if needed
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: UIKitViewControllerWrapper()) {
                Text("Go to UIKit View controller")
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接