如何避免在SwiftUI中更新父视图时在工作表中调用init()

如何避免在SwiftUI中更新父视图时在工作表中调用init(),swiftui,Swiftui,TestView3可以显示两个工作表:TestView4和TestView5。TestView4具有@State值,该值从TestView3初始化。TestView5具有@ObservedObject值,该值也从TestView3初始化 当我切换全局值(global.on)时,它将更新TestView3,然后导致TestView4或TestView5初始化() 问题1。当TestView3更新时,如何避免在TestView4或TestView5中调用init()?如何将带有图纸的视图与视图完全隔

TestView3可以显示两个工作表:TestView4和TestView5。TestView4具有@State值,该值从TestView3初始化。TestView5具有@ObservedObject值,该值也从TestView3初始化

当我切换全局值(global.on)时,它将更新TestView3,然后导致TestView4或TestView5初始化()

问题1。当TestView3更新时,如何避免在TestView4或TestView5中调用init()?如何将带有图纸的视图与视图完全隔离?或者视图中的任何更新都会影响图纸,这太糟糕了

问题2。TestView4或TestView5中init()的行为不同。TestView4中的本地@State在init()中不会更改。但是本地@ObservedObject将在init()中更改。为什么?太奇怪了。如何使@ObservedObject在init()中不会因为TestView3更新而更改

您可以测试以下代码,跟踪日志非常奇怪

class Global: ObservableObject {
    static let shared = Global()
    @Published var on: Bool = false
}

class Local: ObservableObject {
    @Published var on: Bool = false
    init(_ on: Bool) {
        self.on = on
    }
}

struct TestView3: View {
    @ObservedObject var global = Global.shared
    @State private var sheetShowing = false
    @State private var sheetShowingId = 4
    var body: some View {
        print("test view3 body, glabel on = \(global.on)"); return
        VStack {
            Text("Global").foregroundColor(global.on ? .red : .gray).onTapGesture {
                self.global.on.toggle()
            }.padding()
            Button(action: {
                self.sheetShowing = true
                self.sheetShowingId = 4
                print("test view3, will show test view4")
            }) { Text("Show TestView4") }.padding()
            Button(action: {
                self.sheetShowing = true
                self.sheetShowingId = 5
                print("test view3, will show test view5")
            }) { Text("Show TestView5") }.padding()
        }.sheet(isPresented: $sheetShowing) {
            if self.sheetShowingId == 4 {
                TestView4(on: self.global.on)
            } else {
                TestView5(on: self.global.on)
            }
        }
    }
}

struct TestView4: View {
    @State private var on: Bool
    init(on: Bool) {
        self._on = State(initialValue: on)
        print("test view4 init, glabel on = \(Global.shared.on), local on = \(self.on)")
    }
    var body: some View {
        print("test view4 body, glabel on = \(Global.shared.on), local on = \(on)"); return
        VStack {
            Text("TestView4").padding()
            Text("Local").foregroundColor(on ? .red : .gray).onTapGesture {
                self.on.toggle()
                print("test view4, local toggle")
            }.padding()
            Text("Global").foregroundColor(Global.shared.on ? .red : .gray).onTapGesture {
                Global.shared.on.toggle()
                print("test view4, global toggle")
            }.padding()
        }
    }
}

struct TestView5: View {
    @ObservedObject var local: Local
    init(on: Bool) {
        self._local = ObservedObject(initialValue: Local(on))
        print("test view5 init, glabel on = \(Global.shared.on), local on = \(self.local.on)")
    }
    var body: some View {
        print("test view5 body, glabel on = \(Global.shared.on), local on = \(local.on)"); return
        VStack {
            Text("TestView5").padding()
            Text("Local").foregroundColor(local.on ? .red : .gray).onTapGesture {
                self.local.on.toggle()
                print("test view5, local toggle")
            }.padding()
            Text("Global").foregroundColor(Global.shared.on ? .red : .gray).onTapGesture {
                Global.shared.on.toggle()
                print("test view5, global toggle")
            }.padding()
        }
    }
}

问题1:您无法避免这种情况。为什么?每次在状态或ObservedObject中包装的TestView3属性的值发生更改时,它都会重新计算其主体。即使TestView3或TestView4将依赖于此值,声明的这一部分

if self.sheetShowingId == 4 {
    TestView4(on: self.global.on)
} else {
    TestView5(on: self.global.on)
}
表示一个视图,必须重新计算其主体

问题2:这没有简单的答案,只是因为我们对彼此的实施细节了解不多。至少我建议你不要依赖你的调查:-)。大致如此,很容易看出您在TestView5.init中创建了模型local()的新(本地副本)

init(on: Bool) {
    self._local = ObservedObject(initialValue: Local(on))
    print("test view5 init, glabel on = \(Global.shared.on), local on = \(self.local.on)")
}

您真的希望每次SwiftUI需要刷新其视图时都有不同的(独立的)模型吗?我不这么认为…

最后,我找到了原因

@ObservedObject与@State不同。苹果在@State上做了一些特别的工作@状态将保持在独立于视图的特殊空间中

@每次刷新UI时,将初始化ObservedObject。它无法恢复@State这样的值


因此,使用@State而不是@ObservedObject。

苹果在iOS14中为此提供了一个新的解决方案:@StateObject,它与@State for类相同,后者使observeObject舒适。

是的,TestView3依赖于全局,但TestView4(或TestView5)不依赖于全局。您可以说TestView4(或TestView5)根本不知道全局。但是当TestView4(或TestView5)作为TestView3的一个工作表时,它将受到您想要的任何影响。将代码更改为“TestView4(on:false)”,TestView4.init()仍会调用。日志:
test view4,全局切换test view3 body,glabel on=false test view4 init,glabel on=false,local on=false
Ok,不用担心,只要找到并阅读@ViewBuilder是什么,谢谢你的回答。我对Q1很清楚。对于第二季度,是的,我不想创建新副本。这是一个用户案例:我将为一本书选择标记,该书在BookView中已经有标记a\B\C,然后我显示该书的标记选择器工作表(TagSelectionView),我希望标记a\B\C已经在工作表中选择,我可以修改选择(ObservedObject),例如作为\C\G。同时,全局值已更改,BookView主体已更新-TagSelectionView init()-TagSelectionView主体已更新。因此,选择(ObservedObject)将在TagSelectionView init()中重置为\B\C。如何避免此问题?有解决方案吗?您需要做的(和理解的)就是将业务逻辑和/或数据与UI(演示文稿)分离.是的,我知道,几乎所有的SwiftUI教程都向我们展示了相反的一面,但是请记住,如果你发现这样的“麻烦”就像你的情况一样,是时候重新考虑你在做什么了,最好还是回去。否则你的代码很快就会很难维护并且充满bug。你错了!@ObservedObject实际上是指ObservedObject,正如“@State”是指State。事实上,State和ObservedObject是不同的,但这是唯一的错误后悔你发布的信息。请删除你的答案…不要让读者像以前一样更加困惑。欢迎讨论。有什么问题吗?我在你的评论中找不到。请澄清你的观点。也许我错了,但你的话对我或其他读者没有帮助。观察对象和状态都是结构,它们都是conform to DynamicProperty protocol(更新视图外部属性的存储变量)。视图在重新计算视图主体之前为这些属性提供值。您编写的内容非常混乱,抱歉。是的,您所说的是众所周知的().不仅仅是阅读,请尝试。我的观点是它们并不完全相同。ObservedObject有副作用,它非常危险。请小心使用它,甚至不要使用它。你在谈论doc的哪一部分?你在谈论哪些副作用?请,我不喜欢与任何人争论,但不要混淆a。使用ObservedObject是常见的nd建议练习,危险的是你的“调查”和解释。