SwiftUI:如果@ObservedObject是UIViewController的子类,则视图内容不会重新加载。这是错误还是我错过了什么?

时间:2019-08-29 02:15:38

标签: swift swiftui

我有一个SwiftUI视图,用于显示和更新源自@ObservedObject的数据。下面的代码示例按预期方式工作,除非@ObservedObject恰好是UIViewController的子类。在这种情况下,将对@ObservedObject进行数据更新,但它们不会触发View内容的重新加载。

这是SwiftUI视图:

struct ContentView : View {
    @ObservedObject var scene: SceneViewController

    var body: some View {
        VStack {
            Text("The time is: \(scene.currentSimTimestamp.description(with: .current))")
            Button(action: {self.scene.currentSimTimestamp = Date()},
                   label: {Text("Update")})
        }
    }
}

这是@ObservedObject:

class SceneViewController: UIViewController, ObservableObject {

    @Published var currentSimTimestamp: Date = Date()

}

按下“更新”按钮将导致Scene.currentSimTimestamp中存储的值更新,但是ContentView不会重新加载(屏幕不会更新以反映数据更改)。

class SceneViewController: UIViewController, ObservableObject {更改为class SceneViewController: ObservableObject {将导致更新按预期显示。

这似乎是一个错误,因为我看过的Apple文档和视频似乎暗示任何Class都可以采用ObservableObject,并且确实没有编译器问题产生。但是我想念什么吗?

(下面添加了示例SceneDelegate代码以将示例代码重现到项目中)

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    let sceneController: SceneViewController = SceneViewController()

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

        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView(scene: sceneController)

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

2 个答案:

答案 0 :(得分:1)

另外两种获取通知的方式:

最简单的方法就是调用发布者(AFAIK scene.currentSimTimestamp不必是@Published):

Button(action: {
  self.scene.currentSimTimestamp = Date()
  self.scene.objectWillChange.send()
},
       label: {Text("Update")})

稍微复杂一些,但恕我直言,合并方式稍微更清洁: https://stackoverflow.com/a/60126962/301790

答案 1 :(得分:0)

作为一种解决方法,我发现我可以从View Controller中拉出@Published属性包装器,并将其移动到新的(非UIViewController)ObservableObject类中,该类仅用于发布此属性。仅有此更改,它才能按预期运行。显然,解决方法很麻烦,但是它确实允许现有UIViewController拥有的数据在SwiftUI视图中按预期使用。这是更新:

class NewClass: ObservableObject {

    @Published var timestamp: Date = Date()

}

class SceneViewController: UIViewController {
 var currentSimTimestamp: NewClass()

 override func viewDidLoad() {
    super.viewDidLoad()

    // Shown here with our SwiftUI view as a child in our ViewController's hierarchy
    let contentView = ContentView(newClass: currentSimTimestamp)
    let contentViewController = UIHostingController(rootView: contentView)
    addChild(contentViewController)
    view.addSubview(contentViewController.view)
    ...
 }

}

struct ContentView : View {
    @ObservedObject var newClass: NewClass

    var body: some View {
        VStack {
            Text("The time is: \(newClass.timestamp.description(with: .current))")
            Button(action: {self.newClass.timestamp = Date()},
                   label: {Text("Update")})
        }
    }
}