在 SwiftUI 中从视图外部设置 @EnvironmentObject

5
我希望有一个工作任务可以更新SwiftUI视图。
工作任务正在忙于应用程序的过程性工作 - 播放声音和触发基于计时器的事件。在这些计时器事件期间,我想在SwiftUI视图中闪烁几个图标。因此,我希望触发视图刷新来更新这些图标视图。
因此,我创建了一个名为Settings的environmentObject。它在App Delegate中被实例化,并附加到SceneDelegate中的根视图上。
Settings对象在SwiftUI View层次结构内运行良好。
问题是可怕的:
Fatal error: No ObservableObject of type Settings found. A View.environmentObject(_:) for Settings may be missing as an ancestor of this view.

我认为问题在于工作类是在AppDelegate中实例化的,而在实例化Settings时它还不是一个ObservableObject。但我很困惑。

环境对象很直观:

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>()


它与工作类一起在AppDelegate中实例化:
let settings = Settings()
...
var workerClass  = WorkerClass()
var leftArrow = LeftArrowView()

然后它被传递给了SceneDelegate:

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

使用设置的子视图查看环境对象,以在打开或关闭状态下绘制图标:

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
            }
        }
    }
}

这个工作类是由SwiftUI视图层次结构中更高级别的按钮动作调用的。

在工作类内部,我尝试连接到设置环境对象:

import Combine

public class WorkerClass : NSObject, ObservableObject {

    @EnvironmentObject var settings: Settings

在通过定时器调用的方法内部,我尝试更新环境对象中的一个变量。
       settings.arrowsOn = !settings.arrowsOn
        print("Arrows are \(settings.arrowsOn)")

...这时,我才发现自己未能正确地连接到环境对象。

我错过了什么?

提前感谢任何见解...

1个回答

6
@EnvironmentObject 包装器只能在 SwiftUI 视图内部使用,在其他地方,您可以以常规方式使用引用类型,因此这里提供了可能的解决方案。
public class WorkerClass : NSObject, ObservableObject {
    var settings: Settings    // reference property

创建时

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

1
谢谢!我当然可以做到,但更新设置对象的目的是导致SwiftUI视图更新。现在我可能很错,但我理解包装器的目的是导致Combine发布对变量的更改,而SwiftUI已经订阅了它。这有意义吗? - Andromeda
@Andromeda,包装器的目的是观察已发布的值并更新视图,这就是为什么它只需要在视图中使用。而且,无论何时(以及从任何地方)修改了已发布的属性,它都将被发布,这就是为什么在WorkerClass情况下仅需要对其进行引用就足够了。 - Asperi
啊,这就是我没有看完Combine书的后果 - 谢谢!当我这样做时,我会收到一个有用的警告,不要在后台线程上更新已发布的变量(我没有这样做)...但是SwiftUI视图仍然没有被触发以进行视图更新。你有什么想法可能还缺少了什么吗? - Andromeda
非常感谢你 - 我一定会请你喝你喜欢的美味饮料! - Andromeda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接