简体   繁体   中英

Setting a SwiftUI @EnvironmentObject from outside a view

I'd like to have a worker task update a SwiftUI view.

The worker task is busy doing the procedural work of the application - playing sounds, and firing timer-based events. I'd like to to flash several icons in a SwiftUI view during those timer events. So I want to trigger a view refresh in those icon views.

So, I created an environmentObject called Settings. It's instantiated in the App Delegate, and attached to the root view in the SceneDelegate.

The Settings object works just fine inside the SwiftUI View hierarchy.

The problem is the dreaded:

Fatal error: No ObservableObject of type Settings found. A View.environmentObject(_:) for Settings may be missing as an ancestor of this view.

I think that the problem is that the worker class is instantiated in the AppDelegate, and Settings is not yet an ObservableObject yet when it's instantiated. But I'm confused.

The environment object is straightforward:

import SwiftUI
import Combine

final class Settings: ObservableObject {
   @Published var showMenu: Bool = true
   @Published var lessonNum: Int = 0

   @Published var arrowsOn: Bool = false {
       willSet {
           willChange.send(self)
       }
   }
}

let willChange = PassthroughSubject<Settings, Never>()

It's instantiated, along with the worker class, in the AppDelegate:

let settings = Settings()
...
var workerClass  = WorkerClass()
var leftArrow = LeftArrowView()

And it's passed to the SceneDelegate:

            window.rootViewController = UIHostingController(rootView: contentView
                .environmentObject(settings)

The sub view that uses settings looks at the environment object to draw the icon in either the on or off state:

import SwiftUI

struct LeftArrowView: View {
@EnvironmentObject var settings: Settings

    let leftArrowOnImage = Image("Arrow Left On").renderingMode(.original)
    let leftArrowOffImage = Image("Arrow Left Off").renderingMode(.original)

    var body: some View {
        ZStack {
            if settings.arrowsOn {
                leftArrowOnImage
            } else {
                leftArrowOffImage
            }
        }
    }
}

The worker class is called as a Button action from higher up in the SwiftUI view hierarchy.

Inside the worker class I attempt to attach to the settings environment object:

import Combine

public class WorkerClass : NSObject, ObservableObject {

    @EnvironmentObject var settings: Settings

and inside a method that's invoked via a timer, I attempt to update a variable in the environment object:

       settings.arrowsOn = !settings.arrowsOn
        print("Arrows are \(settings.arrowsOn)")

... which is when I discover that I failed to actually attach properly to the environment object.

What did I miss?

Thanks in advance for any insights...

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

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.

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