SwiftUI - 子类视图模型不会触发视图刷新
Posted
技术标签:
【中文标题】SwiftUI - 子类视图模型不会触发视图刷新【英文标题】:SwiftUI - Subclassed viewModel doesn't trigger view refresh 【发布时间】:2020-09-12 20:11:26 【问题描述】:我有这种情况,我有一个包含一些常用元素的 BaseView
和一个包含一些常用功能的 BaseViewModel
,但是当它的 @Published
var 得到更新时,没有 BaseView
刷新发生。
设置是这样的:
class BaseViewModel: ObservableObject
@Published var overlayView: AnyView = EmptyView().convertToAnyView()
func forceViewRefresh()
self.objectWillChange.send()
func setOverlayView(overlayView: AnyView)
self.overlayView = overlayView
这个视图模型子类BaseViewModel
:
class FirstViewModel: BaseViewModel
func showOverlayView()
self.setOverlayView(overlayView: OverlayView().convertToAnyView())
我还有一个BaseView
,我使用overlayView
struct BaseView<Content: View>: View
let content: Content
@ObservedObject var viewModel = BaseViewModel()
init(content: () -> Content)
self.content = content()
var body: some View
ZStack
Color.green.edgesIgnoringSafeArea(.vertical)
content
viewModel.overlayView
显示的第一个视图是 FirstView
,它符合 BaseViewProtocol
并具有扩展 BaseViewModel
的 FirstViewModel
。
struct FirstView: BaseViewProtocol
@ObservedObject var viewModel = FirstViewModel()
var body: some View
BaseView()
Button("Show overlay")
viewModel.showOverlayView()
单击First View
中的Show overlay
按钮会调用FirstViewModel
上的showOverlayView()
函数,后者又会调用BaseViewModel
上的setOverlayView
。 BaseViewModel
中的 overlayView
的值按预期更改,但不会调用 FirstView
上的视图刷新。
我做错了什么?
非常感谢。
【问题讨论】:
您想通过使用@Published var overlayView: AnyView = EmptyView().convertToAnyView()
来达到什么目的?这不是 SwiftUI 应该如何工作的......请分享更多代码,以便我们为您提供更多帮助。
我在 BaseViewModel 中设置了一个覆盖视图,以便它可以由其视图模型子类 BaseViewModel 的任何其他视图显示在布局中。当发布的 var 从默认的空视图更改为另一个视图时,它应该触发视图刷新,但不会。我没有看到任何与 SwiftUI “工作方式”背道而驰的东西。 (顺便说一句,convertToAnyView 只是我写的一个小扩展,它基本上等同于 AnyView(overlayView))。
但是,我已经编辑了原始帖子,添加了更多细节以更好地关注问题。
【参考方案1】:
我刚刚测试了这个代码示例,并且在 Xcode 12 beta 6 和 ios 14 beta 8 上运行良好
struct ContentView: View
@StateObject private var viewModel = FirstViewModel()
var body: some View
ZStack
Button(action: viewModel.showOverlayView() )
Text("Press")
viewModel.overlayView
class BaseViewModel: ObservableObject
@Published var overlayView: AnyView = AnyView(EmptyView())
func forceViewRefresh()
self.objectWillChange.send()
func setOverlayView(overlayView: AnyView)
self.overlayView = overlayView
class FirstViewModel: BaseViewModel
func showOverlayView()
self.setOverlayView(
overlayView: AnyView(
Color.blue
.opacity(0.2)
.allowsHitTesting(false)
)
)
【讨论】:
【参考方案2】:通常在 SwiftUI 中,您不会在 body
之外创建视图。视图创建应该留给 SwiftUI - 相反,您可以定义一些其他控件来告诉 SwiftUI 如何 和 何时 创建视图。
这是一个简化的演示,如何为不同的视图呈现不同的叠加层。
您可以创建一个基本的 OverlayView:
enum OverlayType
case overlay1, overlay2
struct OverlayView: View
let overlayType: OverlayType
@ViewBuilder
var body: some View
if overlayType == .overlay1
Text("Overlay1") // can be replaced with any view you want
if overlayType == .overlay2
Text("Overlay1")
并在您的 BaseView 中使用它(如果 overlayType
是 nil
,则不会显示叠加层):
struct BaseView<Content>: View where Content: View
let overlayType: OverlayType?
let content: () -> Content
var body: some View
ZStack
Color.green.edgesIgnoringSafeArea(.vertical)
content()
if overlayType != nil
OverlayView(overlayType: overlayType!)
现在您可以在 ContentView 中使用 BaseView 并指定其 OverlayType。
struct ContentView: View
@State var overlayType: OverlayType?
var body: some View
BaseView(overlayType: overlayType)
Button("Show overlay")
overlayType = .overlay1
一些注意事项:
为简单起见,我使用@State
变量来控制覆盖。如果您的 ViewModel 有其他用例,您可能希望将逻辑移到那里。
请注意,最好使用@ViewBuilder
,而不是AnyView
。
另外,如果你想在视图中观察ObservableObject
,你需要使用@ObservedObject
,而不是@ObservableObject
。
【讨论】:
谢谢,听起来不错,我试试。还要感谢您的最终建议,即使@ObservedObject 问题只是代表我的错字:-) 无论如何,我的代码中的主要问题是当发布的覆盖变量更新时 BaseView 不会刷新。有什么想法吗? @Goblin 只是不要在主体外部手动创建视图(在 SceneDelegate 中除外),您就不会遇到这些问题 - 正如我在回答的第一段中解释的那样。 对,但是覆盖是动态的,在运行时是未知的,所以事先创建它们是不可行的。即使我认识它们,如果我有 50 种不同的叠加层怎么办?无尽的枚举? @Goblin 这只是一个例子。但是,如果您有 50 个不同的叠加层,您目前的方法会发生什么? 50 个视图模型? 50个功能?如果叠加层是动态的并且在运行时未知,那么您为什么不在问题中提及这一点?以上是关于SwiftUI - 子类视图模型不会触发视图刷新的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI:如果 @ObservedObject 是 UIViewController 的子类,则不会重新加载视图内容。这是一个错误还是我错过了啥?
从 @Binding var 修改 @State var 不会刷新 SwiftUI 中的视图