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

Posted

技术标签:

【中文标题】SwiftUI:如果 @ObservedObject 是 UIViewController 的子类,则不会重新加载视图内容。这是一个错误还是我错过了啥?【英文标题】:SwiftUI: View content not reloading if @ObservedObject is a subclass of UIViewController. Is this a bug or am I missing something?SwiftUI:如果 @ObservedObject 是 UIViewController 的子类,则不会重新加载视图内容。这是一个错误还是我错过了什么? 【发布时间】:2019-08-29 02:15:38 【问题描述】:

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

这是 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 文档和视频似乎表明任何类都可以采用 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()
        
    
...

【问题讨论】:

您如何使用UIViewController?我没有看到像UIViewControllerRepresentable 这样的参考。我认为这是在 SwiftUI 项目中使用 UIViewController 的唯一方法。 (如果没有,请发布足够的代码让我们重现您的问题。) 在实际项目中,UIViewController 包含 SwiftUI 视图(使用 UIHosingtController)。然而,在我的测试中,我发现这并不重要——即使 UIViewController 与 SwiftUI 视图不在同一个视图层次结构中,行为仍然与描述的一样。例如,在一个最简单的示例中,您可以使用默认的 Swift“使用 Swift UI 项目”SceneDelegate,如下所示: 【参考方案1】:

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

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


【讨论】:

事实上,只要你的 ObservableObject 是一个子类,它就不起作用,我对 viewModel 的子类有完全相同的问题。 我不觉得这个笨重。看起来非常接近 ViewModel。您甚至可以将其声明为 struct 并使用 @State/Binding【参考方案2】:

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

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

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

涉及更多,但恕我直言,Combine 方式稍微干净一些: https://***.com/a/60126962/301790

【讨论】:

以上是关于SwiftUI:如果 @ObservedObject 是 UIViewController 的子类,则不会重新加载视图内容。这是一个错误还是我错过了啥?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:如果满足条件,如何为文本添加边框?

SwiftUI 如果让内部视图

如果它不可见,SwiftUI 如何使 NavigationLink 工作?

SwiftUI - 如果在 ForEach 循环内

SwiftUI:如果不在列表中,NavigationLink 不起作用

SwiftUI:可以访问祖先自定义`@EnvironmentObject`?如果是,如何?