繁体   English   中英

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

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

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

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

这可能吗? 如果是这样怎么办? 在纯SwiftUI应用程序中,我们设置环境 object,如下所示,

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

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

但就我而言,没有像这样的 function,因为它是一个现有的 iOS 项目,带有AppDelegateSceneDelegate 而初始视图controller标记在Storyboard中。如何设置并在应用程序的任何位置访问object?

.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)")
    }
}

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.

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

当前state

我有一个 Main.storyboard 文件,其中一个视图 controller 场景设置为初始视图 controller 和自定义 class ViewController 这是自定义 class 实现:

import UIKit

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

目标

在 SwiftUI 应用程序生命周期中使用此 class 并使其与@EnvironmentObject实例(在本例中我们称其为主题管理器)做出反应和交互。

解决方案

我将定义一个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 {}

接下来,我创建了一个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
        }
    }
}

现在,最后要做的是创建主题管理器的实例并将其作为环境 object 传递给视图 controller 表示:

import SwiftUI

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

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

运行该应用程序会显示我们的视图 controller 以及一个 label 和一个按钮。 点击按钮会触发IBAction ,这会更改themeManager.theme ,这会触发对表示的updateUIViewController(_:, context:)的调用:

解决方案演示

暂无
暂无

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

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