SwiftUI:在呈现Sheet时防止视图刷新

22

我注意到在添加sheet修饰符时,SwiftUI会完全刷新视图。

假设我有一个视图,显示随机数。我期望这个值是独立的,并且与sheet逻辑无关(每次打开/关闭sheet时都不会改变),但每次出现/解除sheet时,Text都会改变

它应该是这样工作的吗? 我错了,@State的主要作用是仅更新连接的视图而不是整个堆栈吗? 如何防止我的视图在呈现模态时刷新自身?

struct ContentView: View {

    @State var active = false

    var body: some View {
        VStack {
            Text("Random text: \(Int.random(in: 0...100))")

            Button(action: { self.active.toggle() }) {
                Text("Show pop up")
            }
        }
        .sheet(isPresented: $active) {
            Text("POP UP")
        }
    }
}

顺便提一下,ContentView仅在调用onAppear()/onDisappear()init()时执行一次。


2
是的,这是预期行为。View 是一个结构体,值类型,如果它的任何部分发生了改变,那么整个值都会改变。 - Asperi
@Asperi,我理解得对吗,即“如果它的任何部分发生更改,则整个值都会更改”之所以有效,是因为@State属性。而这与经典(非SwiftUI)Swift无关。 - Dmytro Pashkov
2个回答

27

它需要制作独立于条件的视图,以实现您所期望的行为,如下所示

struct RandomView: View {
    var body: some View {
        Text("Random text: \(Int.random(in: 0...100))")
    }
}

struct ContentView: View {

    @State var active = false

    var body: some View {
        VStack {
            RandomView()

            Button(action: { self.active.toggle() }) {
                Text("Show pop up")
            }
        }
        .sheet(isPresented: $active) {
            Text("POP UP")
        }
    }
}
在这种情况下,RandomView 不会被重建,因为它不依赖于 active 状态。

3
像往常一样,Asperi 是正确的。这篇文章提供了一些关于视图相互依存的明显例子,并介绍如何对它们进行分解因素:https://medium.com/flawless-app-stories/swiftui-why-to-write-custom-views-for-better-performance-561962f1c268 - Andrew Duncan
2
只是补充一下,对于更复杂的用例,在 SwiftUI 视图上使用 Equatable 协议和 .equatable() 修饰符。 - George
1
这是几天的努力结晶,试图找出为什么每次修改TextField值时我的UI都会完全更新,然后试图修复它。谢谢! - Nostradamus

5

Asperi说:

视图是结构体,值类型,如果其中任何一部分发生了变化,那么整个值都会改变。

他完全正确!但我们有状态属性来解决这个问题。当重新创建视图时,状态的值不会改变。

这应该可以正常工作,就像你期望的那样。

struct ContentView: View {

    @State var active = false
    @State var number = Int.random(in: 0 ... 100)
    var body: some View {
        VStack {
            Text("Random text: \(number)")

            Button(action: { self.active.toggle() }) {
                Text("Show pop up")
            }
        }
        .sheet(isPresented: $active) {
            Text("POP UP")
        }
    }
}

什么是优势?对于简单的事情,状态/绑定是最好的解决方案,毫无疑问。
import SwiftUI

struct SheetView: View {
    @Binding var randomnumber: Int
    var body: some View {
        Button(action: {
            self.randomnumber = Int.random(in: 0 ... 100)
        }) {
            Text("Generate new random number")
        }
    }
}
struct ContentView: View {

    @State var active = false
    @State var number = Int.random(in: 0 ... 100)
    var body: some View {
        VStack {
            Text("Random text: \(number)")

            Button(action: { self.active.toggle() }) {
                Text("Show pop up")
            }
        }
        .sheet(isPresented: $active) {
            SheetView(randomnumber: self.$number)
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

现在您可以关闭表格,无论是否生成新的随机数字。不需要外部模型...

是的,你说得完全正确,但这个问题稍微简化了一些。在实际项目中,我有一个自定义视图行的列表,并使用 @State 对象作为数据源。我的问题是多次运行行的 init() 方法。虽然数据源没有改变,但每次打开/关闭工作表时都不需要重新初始化单元格,因为我与单元格 init() 方法相关的一些逻辑不需要重新初始化。 - Dmytro Pashkov
在这种情况下,请不要使用状态。使用一些与用户界面分离的模型。一般而言,回答这个问题:我需要在我的视图中绑定吗?如果不需要,则不需要“state”属性包装器... - user3441734

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接