从 SwiftUI 推送到 UIViewController

Posted

技术标签:

【中文标题】从 SwiftUI 推送到 UIViewController【英文标题】:Push to UIViewController from SwiftUI 【发布时间】:2021-07-09 21:17:01 【问题描述】:

我的应用是UIKitSwiftUI 的混合体。我有一个用例,用户可能需要从SwiftUI.View 中点击Button,但推送到UIViewController 或另一个UIHostingController

我的项目使用Storyboard

我正在使用UINavigationControllerUITabBarController

我正在研究两种情况。

1.) 从我在主屏幕上首次启动时,我可以点击一个按钮,在它的操作中我有:

let vc = UIHostingController(rootView: RootView())
self.navigationController?.pushViewController(vc, animated: true)

这按预期工作。

2.) 我点击另一个选项卡,它默认为我的SwiftUI RootView,它托管在我在Storyboard 中使用的自定义UIHostingController 中。在这里,如果我点击一个按钮来触发推送,它不会推送。我刚刚看到View 我正在更新。

我也在使用自定义UINavigationController。从我的Storyboard,制表关系转到自定义UINavigationController,然后它的根是适当的UIViewController。在一种情况下,虽然它是我的自定义 UIHostingController,所以我可以从选项卡选择中加载 SwiftUI 视图。

这是我尝试处理从我的 SwiftUI 视图推送到视图控制器的操作:

final class AppData: ObservableObject 
    weak var window: UIWindow? // Will be nil in SwiftUI previewers

    init(window: UIWindow? = nil) 
        self.window = window
    
    
    public func pushViewController(_ viewController: UIViewController, animated: Bool = true) 
        let nvc = window?.rootViewController?.children.first?.children.first as? UINavigationController
        nvc?.pushViewController(viewController, animated: animated)
    


// This is what is triggered from the Button action.
func optionSelected(option: String) 
    if let optionId = Common.getIDByOption(option: option) 
        let vc = UIHostingController(rootView: RootView())                
        appData.pushViewController(vc, animated: true)
    

会发生什么:

我确实看到数据发生了变化,但它们都在我已经使用的同一个视图上。 我需要推送到新的UIHostingController

【问题讨论】:

您能提供更多上下文吗?此视图是否由UINavigationControllerNavigationLink 控制? @Błażej 我在上面添加了更多上下文。如果需要任何额外的上下文,请告诉我。 【参考方案1】:

如果您以 UITabBarControllerUINavigationController 处理导航的方式混合 UIKitSwiftUI。我建议你剪掉NavigationViewNavigationLink。背后的原因很简单。 SwiftUI.View 将在每次切换到选项卡时重新创建。因此,您将从头开始。你可以绕着它走,但在这种情况下,使用UINavigationController 会更容易。

假设您的应用有两个标签。我会将SwiftUI.View 放入UIHostingController,然后放入UINavigationController。在SwiftUI.View 中,我将关闭let onTap: () -> Void,只要您需要将下一个UIHostingControllerUIViewController 推送到UINavigationController,就会调用它。

例子

//: A UIKit based Playground for presenting user interface

import UIKit
import SwiftUI
import PlaygroundSupport

final class MainViewController: UITabBarController 
    let firstTab: UINavigationController = 
        let navigationController = UINavigationController()
        navigationController.tabBarItem = UITabBarItem(title: "First", image: nil, tag: 1)
        return navigationController
    ()

    let secondTab: UINavigationController = 
        let navigationController = UINavigationController()
        navigationController.tabBarItem = UITabBarItem(title: "Second", image: nil, tag: 1)
        return navigationController
    ()

    override func viewDidLoad() 
        super.viewDidLoad()
        setUpFirstTab()
        setUpSecondTab()
        viewControllers = [firstTab, secondTab]
    

    func setUpFirstTab() 
        let firstView = FirstView  number in
            let secondViewHostingController = UIHostingController(rootView: SecondView(number: number))
            self.firstTab.pushViewController(secondViewHostingController, animated: true)
        
        let firstViewHostingController = UIHostingController(rootView: firstView)
        firstTab.tabBarItem = UITabBarItem(title: "First", image: nil, tag: 1)
        firstTab.viewControllers = [firstViewHostingController]
    

    func setUpSecondTab() 
        secondTab.tabBarItem = UITabBarItem(title: "Second", image: nil, tag: 2)
        secondTab.viewControllers = [FirstViewController()]
    


// Views for the first tab
struct FirstView: View 
    let onTap: (Int) -> Void
    @State var number: Int = 0
    var body: some View 
        VStack 
            Stepper("Number \(number)", value: $number)
            Button 
                onTap(number)
             label: 
                Text("Go to the next view")
            
        
    


struct SecondView: View 
    let number: Int
    var body: some View 
        Text("Final view with number \(number)")
    


// Views controllers for the second tab
final class FirstViewController: UIViewController 

    override func viewDidLoad() 
        super.viewDidLoad()
        let button: UIButton = UIButton(type: .roundedRect, primaryAction: UIAction(title: "show next view controller")  _ in
            self.navigationController?.pushViewController(SecondViewController(), animated: true)
        )
        button.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    


final class SecondViewController: UIViewController 
    override func viewDidLoad() 
        super.viewDidLoad()
        view.backgroundColor = .systemPink
    

【讨论】:

以上是关于从 SwiftUI 推送到 UIViewController的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在 SwiftUI 中提供服务并将数据推送到视图并更新 UI?

在 SwiftUI 中,为啥 Spacer 会将文本推送到 TextEditor 框架之外?

如何在 SwiftUI 中将文本推送到边缘

SwiftUI:从工作表导航到新视图

SwiftUI:NavigationView/NavigationLink:启动并推送目的地?

SwiftUI @Published 属性正在 DetailView 中更新