发布的数据更新子视图但不更新父视图

Posted

技术标签:

【中文标题】发布的数据更新子视图但不更新父视图【英文标题】:Published data updates child view but not parent view 【发布时间】:2020-07-13 19:30:18 【问题描述】:

我创建了这个示例来演示我的应用程序遇到的问题。我有一个带有属性的视图,父视图及其本身需要使用它来更新 UI。发生的情况是,当属性更改时子视图会更新,而父视图不会。

我试图用下面的代码实现的是,当您点击文本时,如果状态为真,则会出现蓝色轮廓,否则没有轮廓。子视图更新(文本更改)但父视图保持不变(蓝色轮廓保持不变)的实际情况。

/********************Child View***************************/
class ExampleChildViewModel: ObservableObject 
    var id = UUID()
    @Published var showOptions: Bool
    
    init( _ show: Bool ) 
        showOptions = show
    


struct ExampleChildView: View 
    @ObservedObject var model: ExampleChildViewModel
    
    var body: some View
        Text("\(String(model.showOptions))")
        .onTapGesture 
            self.model.showOptions.toggle()
        
    


/********************Parent View***************************/
class ExampleParentViewModel: ObservableObject 
    @Published var children = [ExampleChildViewModel]()
    
    init()
        children.append( ExampleChildViewModel( true ) )
        children.append( ExampleChildViewModel( false ) )
    


struct ExampleParentView: View 
    @ObservedObject var parent: ExampleParentViewModel
    
    var body: some View
        VStack
            ForEach( parent.children, id: \.id ) m in
                ExampleChildView( model: m )
                    .frame(width: 60, height: 30)
                    .overlay(
                        RoundedRectangle(cornerRadius: 6)
                            .stroke(Color.blue, lineWidth: 3)
                            .opacity((m.showOptions) ? 1 : 0) // <---- Doesn't update
                )
            
        
    


/********************Main View*******************************/
struct ExampleView: View 
    @ObservedObject var model = ExampleParentViewModel()

    var body: some View 
        VStack
            ExampleParentView( parent: model )
        
    

为什么设置了ExampleChildViewModel 中的showOptionsExampleParentView 中的parent 没有得到更新?

编辑:

这是实际应用试图显示/隐藏的内容。带有锁符号的每一行都是子视图。

如果我在子视图中有叠加层,我最终会得到以下结果

【问题讨论】:

我对 SwiftUI 不是很熟悉,但我认为这是因为 ExampleParentViewModel children 只会发布对数组本身的浅层更改,而不是对其包含的对象的深层更改。跨度> 【参考方案1】:

您希望父视图负责管理每个子视图的大纲的任何原因?似乎他们应该确定自己的布局,您可以通过将叠加层移动到子视图中来做到这一点:

struct ExampleChildView: View 

    @ObservedObject var model: ExampleChildViewModel
    
    var body: some View
        HStack 
            Text("\(String(model.showOptions))")
        
        .frame(width: 60, height: 30)
        .overlay(
            RoundedRectangle(cornerRadius: 6)
                .stroke(Color.blue, lineWidth: 3)
                .opacity((model.showOptions) ? 1 : 0))
        .onTapGesture 
            self.model.showOptions.toggle()
        
    

【讨论】:

覆盖最终位于父视图中,因为实际上它是一个自定义上下文菜单,需要覆盖所有其他视图。当它在子视图中时,它最终在父视图中的一些其他视图下。父母最终有 N 个孩子,我希望每个孩子都能显示他们的上下文菜单。 要将上下文菜单放在所有视图之上,您可以将其放在 ZStack 中并更改其 Z 位置。 也许这很愚蠢,但我对如何更改 VStack 中的事物的 z 轴感到困惑。所以想象一个有 5 个视图的 VStack,视图 3 和 4 是这些子视图之一,其中覆盖层需要位于视图 #3、4 和 5 的顶部。我如何保持它们彼此垂直对齐并且然后在运行时将视图 3 或视图 4 移动到 ZStack 的顶部?这可能吗?【参考方案2】:

这有点奇怪,为什么要在另一个视图中定义视图的外观。通常你在 View(本例中是 ChildView)本身中进行所有“调整”,然后在 ParentView 中实现它。

现在解决问题。 @Published@State 一样工作。更改后,它会通知视图进行更新。这就是你的问题。它通知 ChildView 更新,但不通知 ParentView。如下修改ParentView时可以看到:

var body: some View
    VStack
        ForEach( self.parent.children, id: \.id ) m in
            
            ExampleChildView( model: m )
                .opacity( m.showOptions ? 1 : 0)
                .frame(width: 60, height: 30)
                .overlay(
                    RoundedRectangle(cornerRadius: 6)
                        .stroke(Color.blue, lineWidth: 3)
                        .opacity( m.showOptions ? 1 : 0)// <---- Doesn't update
                )
        
        
        

        
        Text("Current State?").onTapGesture 
            for m in self.parent.children 
                print(m.showOptions)
            
            print("-----------")
        
    

您可以看到变量正在发生变化,但由于您的 ParentView 没有收到更新通知,因此它不会更新。

当在 ChildView 中定义时,false/true 文本会更新。也在 ChildView 中实现 Overlay,它的工作方式与您期望的一样:

struct ExampleChildView: View 
    
    @ObservedObject var model: ExampleChildViewModel
    
    var body: some View
        Text("\(String(model.showOptions))")
            .onTapGesture 
                self.model.showOptions.toggle()
            .overlay(
                RoundedRectangle(cornerRadius: 6)
                    .stroke(Color.blue, lineWidth: 3)
                    .opacity( self.model.showOptions ? 1 : 0)
            )
    

我建议您在视图本身中实现视图样式的全部或至少主要(更改)部分。

另外:这篇文章解释得很好 https://www.hackingwithswift.com/books/ios-swiftui/sharing-swiftui-state-with-observedobject

【讨论】:

是的,我想这个例子没有解释为什么它是这样设置的。父视图中的叠加层是一个自定义上下文菜单,它也应该位于父视图中的所有其他视图之上。大纲只是示例中查看父视图状态的一种方式。 有没有办法将状态通知从孩子“转发”给父母?

以上是关于发布的数据更新子视图但不更新父视图的主要内容,如果未能解决你的问题,请参考以下文章

如何将数据从父模型类传递到 SwiftUI 中的子视图

SwiftUI ObservedObject 不更新父视图

SwiftUI MVVM:父视图更新时重新初始化子视图模型

从父视图控制器再次推送时,子视图框架发生变化

在 SwiftUI MVVM 中的子视图和父视图之间共享值

删除子视图中的核心数据后更新表视图