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