I have a SwiftUI project that uses EnvironmentObject
to update a label accordingly. In the same project, I have a UIViewController
, that I am hosting in my SwiftUI view by conforming that view controller to the UIViewControllerRepresentable
protocol. I want to be able to control the label in my SwiftUI view from my UIKit code. Is there a way I can pass the EnvironmentObject from my UIKit code? If not, what would be the ideal way to pass data from my UIKit based view to the SwiftUI view.
Please note, I have simplified the project for the sake of posting the question here. Here is the code:
MyApp.swift
import SwiftUI
@MainActor class Options: ObservableObject {
@Published var option = 1
}
@main
struct MyApp: App {
@StateObject var options = Options()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(options)
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject var options: Options
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
if options.option == 0 {
Text("Hello, world!")
} else {
Text("Other Options")
}
MyUIView()
}
.padding()
}
}
#Preview {
ContentView().environmentObject(Options())
}
MyUIViewController.swift
import UIKit
class MyUIViewController: UIViewController {
var btn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var config = UIButton.Configuration.filled()
config.title = "Change Option"
btn = UIButton(configuration: config)
btn.addAction(UIAction(handler: { UIAction in
/* Here, I need to change the option
and pass it to update the label in my SwiftUI view. */
}), for: .touchUpInside);
btn.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
self.view.addSubview(btn)
}
}
MyUIView.swift
import SwiftUI
struct MyUIView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return MyUIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
Naturally, you should add a Options
property in your view controller:
class MyUIViewController: UIViewController {
var btn: UIButton!
var options: Options?
override func loadView() { // loadView is where you should set up your views
view = UIView()
var config = UIButton.Configuration.filled()
config.title = "Change Option"
btn = UIButton(configuration: config)
btn.addAction(UIAction(handler: { UIAction in
// set the published property here
self.options?.option = 0
}), for: .touchUpInside);
btn.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
view.addSubview(btn)
}
}
Then in the UIViewControllerRepresentable
, get the EnvironmentObject
the same way as usual. Then you can assign it to the options
property in the view controller:
struct MyUIView: UIViewControllerRepresentable {
@EnvironmentObject var options: Options
func makeUIViewController(context: Context) -> MyUIViewController {
return MyUIViewController()
}
func updateUIViewController(_ uiViewController: MyUIViewController, context: Context) {
uiViewController.options = options
}
}