简体   繁体   English

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

[英]How to set @EnvironmentObject in an existing Storyboard based project?

I have an iOS project built with Storyboard and UIKit .我有一个用StoryboardUIKit构建的 iOS 项目。 Now I want to develop the new screens using SwiftUI .现在我想使用SwiftUI开发新屏幕。 I added a Hosting View Controller to the existing Storyboard and used it to show my newly created SwiftUI view.我向现有的Storyboard添加了一个托管视图 Controller,并用它来显示我新创建的 SwiftUI视图。

But I couldn't figure out how to create an @Environm.netObject that can be used anywhere throughout the application.但是我不知道如何创建一个可以在整个应用程序的任何地方使用的@Environm.netObject I should be able to access/set it in any of my UIKit based ViewController as well as my SwiftUI views.我应该能够在我的任何基于 UIKit 的 ViewController 以及我的SwiftUI视图中访问/设置它。

Is this possible?这可能吗? If so how to do it?如果是这样怎么办? In a pure SwiftUI app, we set the environment object like below,在纯SwiftUI应用程序中,我们设置环境 object,如下所示,

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

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

But in my case, there is no function like this since it is an existing iOS project with AppDelegate and SceneDelegate .但就我而言,没有像这样的 function,因为它是一个现有的 iOS 项目,带有AppDelegateSceneDelegate And the initial view controller is marked in Storyboard. How to set this and access the object anywhere in the app?而初始视图controller标记在Storyboard中。如何设置并在应用程序的任何位置访问object?

The.environmentObject modifier changes the type of the view from ItemDetailView to something else. .environmentObject 修饰符将视图的类型从 ItemDetailView 更改为其他内容。 Force casting it will cause an error.强制转换它会导致错误。 Instead, try wrapping it into an AnyView.相反,请尝试将其包装到 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())))
    }
}

This works for me.这对我有用。 Is this what you require?这是你需要的吗?

EDIT : Ok, so I gave the setting the property from a ViewController all through the View.编辑:好的,所以我通过 View 设置了 ViewController 中的属性。 It wasn't as easy as using a property wrapper or a view modifier, but it works.它不像使用属性包装器或视图修饰符那么容易,但它确实有效。 I gave it a spin.我试了一下。 Please let me know if this satisfies your requirement.请让我知道这是否满足您的要求。 Also, I had to get rid of the HostingController subclass.此外,我必须摆脱 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)")
    }
}

Basically I'm creating the observable object in the ViewController class, passing it to the MyViewController class and finally create a hosting controller with the ItemDetailView and setting it's environmentObject and presenting it. Basically I'm creating the observable object in the ViewController class, passing it to the MyViewController class and finally create a hosting controller with the ItemDetailView and setting it's environmentObject and presenting it.

Here's my take on tackling this problem.这是我对解决这个问题的看法。 My app targets iOS 14 or above:我的应用程序针对 iOS 14 或更高版本:

The current state当前state

I have a Main.storyboard file with one view controller scene set as the initial view controller with custom class ViewController .我有一个 Main.storyboard 文件,其中一个视图 controller 场景设置为初始视图 controller 和自定义 class ViewController Here's the custom class implementation:这是自定义 class 实现:

import UIKit

class ViewController: UIViewController {
    @IBOutlet var label: UILabel!
}

The goal目标

To use this class in a SwiftUI app life cycle and make it react and interact to @EnvironmentObject instance (In this case let's call it a theme manager).在 SwiftUI 应用程序生命周期中使用此 class 并使其与@EnvironmentObject实例(在本例中我们称其为主题管理器)做出反应和交互。

Solution解决方案

I will define a ThemeManager observable object with a Theme published property like so:我将定义一个ThemeManager observable object 和Theme published 属性,如下所示:

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 {}

Next, I created a ViewControllerRepresentation to be able to use the ViewController in SwiftUI:接下来,我创建了一个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)
    }
}

I then updated ViewController to accept a themeManager instance:然后我更新了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
        }
    }
}

Now, the last thing to do is create an instance of the theme manager and pass it as an environment object to the view controller representation:现在,最后要做的是创建主题管理器的实例并将其作为环境 object 传递给视图 controller 表示:

import SwiftUI

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

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

Running the app shows our view controller with a label and a button.运行该应用程序会显示我们的视图 controller 以及一个 label 和一个按钮。 Tapping the button triggers the IBAction , which changes the themeManager.theme , which triggers a call to the representation's updateUIViewController(_:, context:) :点击按钮会触发IBAction ,这会更改themeManager.theme ,这会触发对表示的updateUIViewController(_:, context:)的调用:

解决方案演示

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

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