App Delegate Accessing Environment Object

SwiftUI: Access @EnvironmentObject from AppDelegate

An @EnvironmentObject doesn't need to be instantiated directly in your SwiftUI objects; rather, it can be allocated somewhere else (for example, your UISceneDelegate) and then passed through using the .environment(…) function.

You could also allocate it on your AppDelegate, and pass that object though to your views in UISceneDelegate.scene(_:willConectTo:options:) method.

Paul Hudson has a good description of all of this at https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views

App Delegate Accessing Environment Object

A better approach to solve these kind of problems wherein you need to make the same instance globally available you should use Singleton design pattern. Also, according to best coding practices we should refrain from overloading AppDelegate with multiple responsibilities and variables. It is always better to decouple your code by dividing responsibility.

class MusicManager: NSObject, ObservableObject {
let sharedMusicManager = MusicManager()
var playlistLabel: String = ""

private init() {}

func playPlaylistNow(chosenPlaylist: String?) {
playlistLabel = "Playlist: \(chosenPlaylist!)"
}
}

AppDelegate

 class AppDelegate: UIResponder, UIApplicationDelegate, AVAudioPlayerDelegate {

func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
var playlistName: String = ""
if let userInfo = notification.userInfo {
playlistName = userInfo["soundName"] as! String
}
sharedMusicManager.playPlaylist(chosenPlaylist: playlistName)
}
}

Similarly you can update the variable from other views. Keeping a private init() would insure that no other instance of that class is creating again. Also, it will always display the most updated value.

Trying to access the environment object created inside scene delegate by another scenedelegate function

You have assigned a DataFetcher instance to the data property, presumably to set its type and eliminate the error that your property isn't initialised.

Then in willConnectTo you get the DataFetcher instance from your AppDelegate. Your dataproperty and the localdatavariable inwillConnectTo(That you subsequently add to the environment) now reference different instances ofDataFetcher`, but you didn't realise.

I am not a big fan of assigning "throw away" instances to properties for this reason.

A better approach is to use an implicitly unwrapped optional. That way if you don't assign a value to the property you will get a crash and quickly work out that something isn't right.

Then in willConectTo you can assign the instance from your AppDelegate to the property and put it in the environment.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?
//@EnvironmentObject var data: DataFetcher
var data: DataFetcher!

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

if let windowScene = scene as? UIWindowScene {

let window = UIWindow(windowScene: windowScene)
self.data = (UIApplication.shared.delegate as! AppDelegate).self.data
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(data))
self.window = window
window.makeKeyAndVisible()
}
}

Setting a SwiftUI @EnvironmentObject from outside a view

The @EnvironmentObject wrapper is only to be used inside SwiftUI view, in other places you can use reference types in regular way, so here is possible solution

public class WorkerClass : NSObject, ObservableObject {
var settings: Settings // reference property

and when create

let settings = Settings()
...
var workerClass = WorkerClass(settings: settings) // << same settings as in view

How can I access an EnvironmentObject from within a NSViewRepresentable / UIViewRepresentable 's Coordinator class?

Here is how I solved this:

AppState.swift:

class AppState: ObservableObject {

static let shared = AppState()
init () {} // TODO: was private init, find out if this has benefits

@Published var currentView: String = "login"
// add rest of shared stuff below

AppDelegate.swift:

func applicationDidFinishLaunching(_ aNotification: Notification) {
let appState = AppState.shared

Access from SwiftUI Views:

struct ContentView: View {
@EnvironmentObject var appState: AppState

Access from NSViewRepresentable / UIViewRepresentable Coordinator Class:

class Coordinator: NSObject, MTKViewDelegate {
...
func draw(in view: MTKView) {
...
context.render((AppState.shared.rawImage ?? AppState.shared.rawImageOriginal)!,
to: drawable.texture,
commandBuffer: commandBuffer,
bounds: AppState.shared.rawImageOriginal!.extent,
colorSpace: colorSpace)
}
...

This took me a lot of time to figure out, so I hope it can help some fellow beginning SwiftUI programmer...

If advanced programmers can improve on this, please do so I can learn from it.

How to use @EnvironmentObject to share data between AppDelegate and SceneDelegate/Views in SwiftUI

Maybe you can use the UIApplication.shared.delegate to get the AppDelegate an access the user data:

class AppDelegate: UIResponder, UIApplicationDelegate {

var userData: UserData!

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
userData = UserData()
return true
}
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

let userData = (UIApplication.shared.delegate as! AppDelegate).userData

// Use a UIHostingController as window root view controller
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(userData))
self.window = window
window.makeKeyAndVisible()
}
}

How to write to a SwiftUI environment object from a class (-extension)

Here is possible approach...

class AppDelegate: UIResponder, UIApplicationDelegate {
//creating the variable in AppDelegate to have it shared
var userState = UserState()
...

so, then you can ...

extension AppDelegate {

func something(loggedIn: Bool) {
// just use it here as now it is own property
self.userState.loggedIn = loggedIn
}
}

and use it in scene delegate via shared application instance

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
...

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// safe as it is known the one AppDelegate
let appDelegate = UIApplication.shared.delegate as! AppDelegate
window.rootViewController = UIHostingController(rootView:
ContentView().environmentObject(appDelegate.userState))
}

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

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.



Related Topics



Leave a reply



Submit