视图是一个 struct ,因此它的属性是不可变的,所以视图不能更改它自己的属性.这就是为什么要从视图体内部更改名为number
的属性需要使用101属性包装器对该属性进行注释.多亏了SWIFT和SwiftUI,透明的读写回调让所看到的值发生了变化.因此,在调用fullScreenCover()
时,不能将number
作为参数SomeView()
传递,而是传递对number
的引用,以便系统地调用回调:106.由于不再传递整数来构造struct SomeView,因此此 struct 中名为number
的属性的类型不能再是整数,而必须是对整数(即绑定)的引用:为此使用@Binding
批注.
因此,将SomeView(number: number)
替换为SomeView(number: $number)
,将let number: Int
替换为@Binding var number: Int
即可完成这项工作.
以下是正确的源代码:
import SwiftUI
struct NumberView: View {
@State var number: Int = 1
@State private var showNumber = false
var body: some View {
NavigationStack {
VStack(spacing: 40) {
// Text("\(number)")
Button {
number = 99
print(number)
} label: {
Text("Change Number")
}
Button {
showNumber = true
} label: {
Text("Show Number")
}
}
.fullScreenCover(isPresented: $showNumber) {
SomeView(number: $number)
}
}
}
}
struct SomeView: View {
@Binding var number: Int
var body: some View {
Text("\(number)")
}
}
毕竟,为了获得有效的源代码,他们的一个小诀窍到目前为止还没有得到解释:如果你简单地将源代码中的Text("Change Number")
替换为Text("Change Number \(number)")
,而不使用$
引用或@Binding
关键字,你将看到问题也自动解决了!不需要在SomeView
中使用@binding
!这是因为SwiftUI在构建视图树时进行了优化.如果它知道显示的视图发生了更改(不仅是它的属性),它将使用更新的@State
值计算该视图.将number
添加到按钮标签使SwiftUI跟踪number
状态属性的更改,并且现在它更新其缓存值以显示文本按钮标签,因此这个新值将被正确地用于创建SomeView.所有这一切可能会被认为是奇怪的事情,但这仅仅是因为SwiftUI的优化.苹果没有完全解释它是如何实现优化构建视图树的,在WWDC活动期间给出了一些信息,但源代码没有开放.因此,您需要严格遵循基于@State
和@Binding
的设计模式,以确保整个工作正常进行.
话虽如此,有人可能会说,Apple说,如果子视图只想访问值:share the state with any child views that also need access, either directly for read-only access, or as a binding for read-write access(https://developer.apple.com/documentation/swiftui/state),那么您不必使用@Binding
将值传递给子视图.这是对的,但苹果在同一篇文章中说,你需要place [state] in the highest view in the view hierarchy that needs access to the value.对于苹果来说,需要访问一个值意味着你需要它来显示视图,而不仅仅是进行其他对屏幕没有影响的计算.正是这种解释允许Apple在需要更新NumberView时优化状态属性的计算,例如在计算Text("Change Number \(number)")
行的内容时.你可能会发现这真的很棘手.但有一种方法可以理解这一点:取您编写的初始代码,删除var number: Int = 1
前面的@State
.要编译它,您需要将此行从 struct 内部移到外部,例如,在源文件的第一行,恰好在导入声明之后.你会看到它起作用了!这是因为您不需要此值即可显示NumberView.因此,将值设置得更高是完全合法的,即构建名为SomeView的视图.请注意,这里您不想更新SomeView,因此没有边框效果.但如果你必须更新SomeView,它就不会起作用.
下面是最后一个技巧的代码:
import SwiftUI
// number is declared outside the views!
var number: Int = 1
struct NumberView: View {
// no more state variable named number!
// No more modification: the following code is exactly yours!
@State private var showNumber = false
var body: some View {
NavigationStack {
VStack(spacing: 40) {
// Text("\(number)")
Button {
number = 99
print(number)
} label: {
Text("Change Number")
}
Button {
showNumber = true
} label: {
Text("Show Number")
}
}
.fullScreenCover(isPresented: $showNumber) {
SomeView(number: number)
}
}
}
}
struct SomeView: View {
let number: Int
var body: some View {
Text("\(number)")
}
}
This is why you should definitely follow the 100 and 101 design pattern, taking into account that if you declare a state in a view that does not use it to display its content, you should declare this state as a @Binding in child views even if those children do not need to make changes to this state.使用@State
的最好方法是在需要它来显示某些东西的最高视图中声明它:不要忘记必须在这个变量的视图中声明@State
;创建一个拥有变量但不必使用它来显示其内容的视图是一种反模式.