SwiftUI: просмотр содержимого без перезагрузки, если @ObservedObject является подклассом UIViewController. Это ошибка или я что-то упустил?

У меня есть SwiftUI View, который представляет и обновляет данные, происходящие из @ObservedObject. Приведенный ниже пример кода работает должным образом, ЕСЛИ @ObservedObject не является подклассом UIViewController. В этом случае обновления данных выполняются в @ObservedObject, но они не вызывают перезагрузку просмотра содержимого, как ожидалось.

Вот SwiftUI View:

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, которые я видел, похоже, предполагают, что любой класс может принять 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()
        }
    }
...

1 ответ

Еще 2 способа получить уведомление:

Самый простой - просто вызвать издателя (AFAIK scene.currentSimTimestamp не должно быть @Published):

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

Немного сложнее, но IMHO немного чище способ Combine: /questions/53161032/swiftui-rasprostranenie-uvedomlenij-ob-izmeneniyah-cherez-vlozhennyie-ssyilochny/53161043#53161043

В качестве обходного пути я обнаружил, что могу извлечь оболочку свойства @Published из View Controller и переместить ее в новый (не 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")})
        }
    }
}

Другие вопросы по тегам