I have an iOS project built with Storyboard and UIKit . Now I want to develop the new screens using SwiftUI . I added a Hosting View Controller to the existing Storyboard and used it to show my newly created SwiftUI view.
But I couldn't figure out how to create an @Environm.netObject that can be used anywhere throughout the application. I should be able to access/set it in any of my UIKit based ViewController as well as my SwiftUI views.
Is this possible? If so how to do it? In a pure SwiftUI app, we set the environment object like below,
@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 . And the initial view controller is marked in Storyboard. How to set this and access the object anywhere in the app?
The.environmentObject modifier changes the type of the view from ItemDetailView to something else. Force casting it will cause an error. Instead, try wrapping it into an 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. 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.
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.
Here's my take on tackling this problem. My app targets iOS 14 or above:
I have a Main.storyboard file with one view controller scene set as the initial view controller with custom class ViewController
. Here's the custom class implementation:
import UIKit
class ViewController: UIViewController {
@IBOutlet var label: UILabel!
}
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).
I will define a ThemeManager
observable object with a Theme
published property like so:
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:
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:
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:
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. Tapping the button triggers the IBAction
, which changes the themeManager.theme
, which triggers a call to the representation's updateUIViewController(_:, context:)
:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.