如何在现有的基于 Storyboard 的项目中设置 @EnvironmentObject?

Posted

技术标签:

【中文标题】如何在现有的基于 Storyboard 的项目中设置 @EnvironmentObject?【英文标题】:How to set @EnvironmentObject in an existing Storyboard based project? 【发布时间】:2021-04-01 08:59:52 【问题描述】:

我有一个使用 StoryboardUIKit 构建的 ios 项目。现在我想使用 SwiftUI 开发新屏幕。我在现有的 Storyboard 中添加了一个 Hosting View Controller,并用它来显示我新创建的 SwiftUI 视图。

但我不知道如何创建一个可以在整个应用程序的任何地方使用的 @EnvironmenetObject。我应该能够在任何基于 UIKit 的 ViewController 以及我的 SwiftUI 视图中访问/设置它。

这可能吗?如果有怎么办?在纯 SwiftUI 应用中,我们如下设置环境对象,

@main
struct myApp: App 
    @StateObject var item = Item()

    var body: some Scene 
        WindowGroup 
            MainView()
                .environmentObject(item)
        
    

但就我而言,没有这样的功能,因为它是一个现有的 iOS 项目,带有 AppDelegateSceneDelegate。并且初始视图控制器在 Storyboard 中被标记。 如何设置它并在应用程序的任何位置访问该对象?

【问题讨论】:

我不相信你可以直接在uikit中使用环境属性修饰符。您可以将相同的对象引用传递给您的 nuikit 视图并将其注入到您的 SwiftUI 视图的环境中 您能详细说明一下吗?我无法设置 @EnvironmentObject 本身,因为我不知道如何将它设置为我的第一个 SwiftUI 视图。这里要注意的是 SwiftUI 视图不是我的根视图。 您创建 SwiftUI 视图的地方,可能在 UIHostingController。只需将.environmentObject 附加到您指定为rootView 的视图 edit你的问题来显示你的代码 不要强行投射你的观点。并尝试在 AnyView 中使用修饰符包装视图 【参考方案1】:

.environmentObject 修饰符将视图的类型从 ItemDetailView 更改为其他内容。强制转换它会导致错误。相反,请尝试将其包装到 AnyView 中。

class OrderObservable: ObservableObject 
    
    @Published var order: String = "Hello"


struct ItemDetailView: View 
    
    @EnvironmentObject var orderObservable: OrderObservable
    
    var body: some View 
        
        EmptyView()
            .onAppear(perform: 
                print(orderObservable.order)
            )
    


class ItemDetailViewHostingController: UIHostingController<AnyView> 
    let appDelegate = UIApplication.shared.delegate as! AppDelegate

    required init?(coder: NSCoder) 
        super.init(coder: coder,rootView: AnyView(ItemDetailView().environmentObject(OrderObservable())))
    

这对我有用。这是你需要的吗?

编辑: 好的,所以我通过 View 设置了 ViewController 中的属性。它不像使用属性包装器或视图修饰符那么容易,但它确实有效。我试了一下。请让我知道这是否满足您的要求。另外,我必须摆脱 HostingController 子类。

class ViewController: UIViewController 
    
    var orderObservable = OrderObservable()
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) 
        guard let myVC = (segue.destination as? MyViewController) else  return 
        myVC.orderObservable = orderObservable
    


class MyViewController: UIViewController 
    
    var orderObservable: OrderObservable!
    var anycancellables = Set<AnyCancellable>()
    @IBAction @objc func buttonSegueToHostingVC() 
        let detailView = ItemDetailView().environmentObject(orderObservable)
        present(UIHostingController(rootView: detailView), animated: true)
        
        orderObservable.$order.sink  newVal in
            print(newVal)
        
        .store(in: &anycancellables)
    



class OrderObservable: ObservableObject 
    
    @Published var order: String = "Hello"
    
    init() 
        DispatchQueue.main.asyncAfter(deadline: .now() + 10) 
            self.order = "World"
        
    


struct ItemDetailView: View 
    
    @EnvironmentObject var orderObservable: OrderObservable
    
    var body: some View 
        
        Text("\(orderObservable.order)")
    

基本上我在 ViewController 类中创建 observable 对象,将其传递给 MyViewController 类,最后使用 ItemDetailView 创建一个托管控制器,并将其设置为 environmentObject 并呈现它。

【讨论】:

这对我有用!谢谢。但问题的一部分是如何在我的 ViewController 类中设置这个 @EnvironmentObject 的值,然后再显示这个用 SwiftUI 创建的新视图? 我也收到警告Accessing StateObject's object without being installed on a View. This will create a new instance each time. 当在 init 方法中设置 rootView 时。 嗯,不能直接设置视图控制器的属性吗? 好的,这行得通。但是 orderObservable 值只能在 ItemDetailView 中访问。如果我再创建一个 SwiftUI 视图并尝试从 ItemDetailView 导航到它,然后访问相同的 orderObservable,它会崩溃,说它是 nil。你能帮我解决这个问题吗? 您是否也在创建新视图并将环境对象注入到下一个 SwiftUI 视图中? SiwftUI 不会自动为您执行此操作。例如: NavigationLink NextView().environmentObject(orderObservable) 我看不到代码,所以我的答案基于我对你可能做错了什么的猜测,你能用确切的代码更新问题,所以我敢肯定。【参考方案2】:

这是我对解决这个问题的看法。我的应用面向 iOS 14 或更高版本:

当前状态

我有一个 Main.storyboard 文件,其中一个视图控制器场景设置为具有自定义类 ViewController 的初始视图控制器。这是自定义类的实现:

import UIKit

class ViewController: UIViewController 
    @IBOutlet var label: UILabel!

目标

在 SwiftUI 应用程序生命周期中使用此类并使其对@EnvironmentObject 实例做出反应和交互(在这种情况下,我们称之为主题管理器)。

解决方案

我将定义一个带有Theme 已发布属性的ThemeManager 可观察对象,如下所示:

import SwiftUI

class ThemeManager: ObservableObject 
    @Published var theme = Theme.purple


struct Theme 
    let labelColor: Color


extension Theme 
    static let purple = Theme(labelColor: .purple)
    static let green = Theme(labelColor: .green)


extension Theme: Equatable 

接下来,我创建了一个 ViewControllerRepresentation 以便能够在 SwiftUI 中使用 ViewController

import SwiftUI

struct ViewControllerRepresentation: UIViewControllerRepresentable 
    @EnvironmentObject var themeManager: ThemeManager

    // Use this function to pass the @EnvironmentObject to the view controller 
    // so that you can change its properties from inside the view controller scope.
    func makeUIViewController(context: Context) -> ViewController 
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let viewController = storyboard.instantiateInitialViewController  coder in
            ViewController(themeManager: themeManager, coder: coder)
        
        return viewController!
    

    // Use this function to update the view controller when the @EnvironmentObject changes.
    // In this case I modify the label color based on the themeManager.
    func updateUIViewController(_ uiViewController: ViewController, context: Context) 
        uiViewController.label.textColor = UIColor(themeManager.theme.labelColor)
    

然后我更新了ViewController 以接受themeManager 实例:

import UIKit

class ViewController: UIViewController 
    @IBOutlet var label: UILabel!

    let themeManager: ThemeManager

    init?(themeManager: ThemeManager, coder: NSCoder) 
        self.themeManager = themeManager
        super.init(coder: coder)
    

    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    @IBAction func toggleTheme(_ sender: UIButton) 
        if themeManager.theme == .purple 
            themeManager.theme = .green
         else 
            themeManager.theme = .purple
        
    

现在,要做的最后一件事是创建主题管理器的实例并将其作为环境对象传递给视图控制器表示:

import SwiftUI

@main
struct ThemeEnvironmentApp: App 
    @StateObject private var themeManager = ThemeManager()

    var body: some Scene 
        WindowGroup 
            ViewControllerRepresentation()
                .environmentObject(themeManager)
        
    

运行应用程序会显示带有标签和按钮的视图控制器。点击按钮会触发IBAction,这会更改themeManager.theme,从而触发对表示的updateUIViewController(_:, context:)的调用:

【讨论】:

以上是关于如何在现有的基于 Storyboard 的项目中设置 @EnvironmentObject?的主要内容,如果未能解决你的问题,请参考以下文章

如何在现有的 ios 测试项目中启用 xcode 7 UI 测试

如何在现有的 Android 操作系统源中添加额外的项目?

如何在现有的Vue项目中嵌入 Blazor项目?

如何在现有的 Windows 应用程序中获得 ATL 支持

如何在现有的旧版Android项目中使用最新的API添加Google Map?

如何在现有的rails模型中使用mongoid来种植数据?