简体   繁体   English

从 SwiftUI 推送到 UIViewController

[英]Push to UIViewController from SwiftUI

My app is a hybrid of UIKit & SwiftUI .我的应用程序是UIKitSwiftUI的混合SwiftUI I have a use case where a user may need to tap on a Button from a SwiftUI.View but push to a UIViewController or another UIHostingController .我有一个用例,用户可能需要点击SwiftUI.ViewButton但推送到UIViewController或另一个UIHostingController

My project uses Storyboard .我的项目使用Storyboard

I'm using UINavigationController & UITabBarController .我正在使用UINavigationControllerUITabBarController

There are two scenarios I am looking at.我正在考虑两种情况。

1.) From my initial launch on my home screen, I can tap a button and within its action I have: 1.) 从我在主屏幕上的初始启动开始,我可以点击一个按钮,并在其操作中:

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

This works as expected.这按预期工作。

2.) I tap on a different tab and it defaults to my SwiftUI RootView which is hosted in a custom UIHostingController which I use in my Storyboard . 2.) 我点击一个不同的选项卡,它默认为我的SwiftUI RootView,它托管在我在Storyboard使用的自定义UIHostingController中。 Here, if I tap on a button to trigger the push, it doesn't push.在这里,如果我点击一个按钮来触发推送,它不会推送。 I just see the View I am on update.我只看到我正在更新的View

I'm also using a custom UINavigationController .我也在使用自定义UINavigationController From my Storyboard , the tabs relationship goes to the custom UINavigationController & then its root is the appropriate UIViewController .从我的Storyboard ,选项卡关系转到自定义UINavigationController & 然后它的根是适当的UIViewController In one scenario though it's my custom UIHostingController so I can load a SwiftUI View initially from the tab selection.在一种情况下,虽然它是我的自定义UIHostingController因此我可以最初从选项卡选择加载SwiftUI视图。

Here is what I have tried doing to handle push to a View Controller from my SwiftUI View:这是我尝试从我的 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)
    }
}

What happens:怎么了:

  • I do see the data change, but it's all on the same View I am already on.我确实看到数据发生了变化,但它们都在我已经使用的同一个视图中。
  • I need to push to the new UIHostingController .我需要推送到新的UIHostingController

If you're mixing UIKit and SwiftUI in a way where UITabBarController and UINavigationController are handling the navigation.如果您以UITabBarControllerUINavigationController处理导航的方式混合UIKitSwiftUI I advise you to cut NavigationView and NavigationLink .我建议你剪掉NavigationViewNavigationLink The reason behind it is simple.背后的原因很简单。 SwiftUI.View will be recreated on each switch to tab. SwiftUI.View将在每次切换到选项卡时重新创建。 Hence, you would start from the begining.因此,您将从头开始。 You could walk around it, but in this situation, easier will be use UINavigationController .您可以四处走动,但在这种情况下,使用UINavigationController会更容易。

Let's assume you're app has two tabs.假设您的应用程序有两个选项卡。 I would put SwiftUI.View in UIHostingController and then in UINavigationController .我会把SwiftUI.ViewUIHostingController ,然后在UINavigationController In SwiftUI.View I would put closure let onTap: () -> Void which would be called whenever you need to push next UIHostingController or UIViewController to UINavigationController .SwiftUI.View我将关闭let onTap: () -> Void每当您需要将下一个UIHostingControllerUIViewController推送到UINavigationController时都会调用它。

Example例子

//: 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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM