[英]Bug with NavigationLink and @Binding properties causing unintended Interactions in the app
I've encountered a bug in SwiftUI that could cause unintended interaction with the app without the user's knowledge .我在 SwiftUI 中遇到了一个错误,它可能会导致在用户不知情的情况下与应用程序进行意外交互。
The problem seems to be related to using @Binding properties on the View structs when used in conjunction with NavigationStack and NavigationLink .该问题似乎与在与 NavigationStack 和 NavigationLink 结合使用时在View 结构上使用@Binding 属性有关。 If you use NavigationView with NavigationLink to display a DetailView that accepts a $Binding parameter, and that parameter is used in some sort of condition in the DetailView, it will result in unexpected behavior.
如果您将 NavigationView 与 NavigationLink 一起使用来显示接受 $Binding 参数的 DetailView,并且该参数在 DetailView 中的某种条件下使用,则会导致意外行为。
To clearly show the problem, I'm using a DetailView where the "Blue" or "Red" view is shown depending on the @Binding property.为了清楚地显示问题,我使用了 DetailView,其中根据 @Binding 属性显示“蓝色”或“红色”视图。 Each of those views has a.onTapGesture() modifier that prints some text when tapped.
这些视图中的每一个都有一个.onTapGesture() 修饰符,可以在点击时打印一些文本。 The problem is that if the Red view is shown, it detects and triggers the action on the Blue view, which could lead to unintended changes in many apps without the user's knowledge.
问题在于,如果显示红色视图,它会检测并触发蓝色视图上的操作,这可能会导致许多应用程序在用户不知情的情况下发生意外更改。
You can easily copy and paste this code into your own file to replicate the bug.您可以轻松地将此代码复制并粘贴到您自己的文件中以复制错误。 To see the unexpected behavior, run the code below and follow these steps on the simulator:
要查看意外行为,请运行以下代码并在模拟器上执行以下步骤:
I tested this behavior on: XCode 14.1, iPhone 13 Pro 16.1 iOS Simulator, and on a real iPhone with iOS 16. The result was always the same.我在以下设备上测试了此行为:XCode 14.1、iPhone 13 Pro 16.1 iOS 模拟器,以及带有 iOS 16 的真实 iPhone。结果始终相同。
import SwiftUI
struct ContentView: View {
var body: some View {
NavView()
}
}
struct NavView: View {
@State private var colourShowed: Int = 1
var body: some View {
// If the DetailView() was shown directly, (without the NavigationLink and NavigationStack) there would be no such a bug.
// DetailView(colourShowed: $colourShowed)
// The bug is obvious when using the NavigationStack() with the NavigationLink()
NavigationStack {
Form {
NavigationLink(destination: { DetailView(colourShowed: $colourShowed) },
label: { Text("Detail View") })
}
}
}
}
struct DetailView: View {
// It seems like the problem is related to this @Binding property when used in conjunction
// with the NavigationLink in "NavView" View above.
@Binding var colourShowed: Int
var body: some View {
ScrollView {
VStack(spacing: 20){
HStack {
Button("BLUE BUTTON", action: {colourShowed = 1})
Spacer()
Button("RED BUTTON", action: {colourShowed = 2})
}
if colourShowed == 1 {
Color.blue
.frame(height: 500)
// the onTapeGesture() is stillActive here even when the "colourShowed" property is set to '2' so this
// view should therefore be deinitialized.
.onTapGesture {
print("BLUE tapped")
}
// The onAppear() doesn't execute when switching from the Red view to the Blue view.
// It seems like the "Blue" View does not deinitialize itself after being previously shown.
.onAppear(perform: {print("Blue appeared")})
}
else {
Color.red
.frame(height: 100)
.onTapGesture {
print("RED tapped")
}
.onAppear(perform: {print("Red appeared")})
}
}
}
}
}
Is there any solution to prevent this?有什么解决方案可以防止这种情况发生吗?
This is a common problem encountered by those new to Swift and value semantics, you can fix it by using something called a "capture list" like this:这是 Swift 和值语义的新手遇到的常见问题,您可以使用称为“捕获列表”的东西来解决它,如下所示:
NavigationLink(destination: { [colourShowed] in
It occurred because DetailView wasn't re-init with the new value of colourShowed
when it changed.发生这种情况是因为 DetailView 在更改时没有使用
colourShowed
的新值重新初始化。 Nothing in body was using it so SwiftUI's dependency tracking didn't think body had to be recomputed. body 中没有任何内容使用它,因此 SwiftUI 的依赖项跟踪并不认为 body 必须重新计算。 But since you rely on DetailView being init with a new value you have to add it to the capture list to force body to be recomputed and init a new DetailView.
但是由于您依赖于使用新值初始化 DetailView,因此您必须将其添加到捕获列表以强制重新计算 body 并初始化一个新的 DetailView。
Here are other questions about the same problem with .sheet
and .task
.以下是关于
.sheet
和.task
相同问题的其他问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.