繁体   English   中英

NavigationLink 和 @Binding 属性的错误导致应用程序中出现意外交互

[英]Bug with NavigationLink and @Binding properties causing unintended Interactions in the app

我在 SwiftUI 中遇到了一个错误,它可能会导致在用户不知情的情况下与应用程序进行意外交互

描述

该问题似乎与在与 NavigationStack 和 NavigationLink 结合使用时在View 结构上使用@Binding 属性有关。 如果您将 NavigationView 与 NavigationLink 一起使用来显示接受 $Binding 参数的 DetailView,并且该参数在 DetailView 中的某种条件下使用,则会导致意外行为。

为了清楚地显示问题,我使用了 DetailView,其中根据 @Binding 属性显示“蓝色”或“红色”视图。 这些视图中的每一个都有一个.onTapGesture() 修饰符,可以在点击时打印一些文本。 问题在于,如果显示红色视图,它会检测并触发蓝色视图上的操作,这可能会导致许多应用程序在用户不知情的情况下发生意外更改。

问题的复制

您可以轻松地将此代码复制并粘贴到您自己的文件中以复制错误。 要查看意外行为,请运行以下代码并在模拟器上执行以下步骤:

  1. 点击 NavigationLink 中的 DetailView。
  2. 点击蓝色区域,控制台将打印“Blue Tapped”。
  3. 点击“红色按钮”切换到另一个视图。
  4. 点击红色区域,控制台将打印“Red Tapped”。
  5. 现在尝试点击红色区域下方的空白区域(蓝色区域之前所在的位置)。 控制台将打印“BLUE tapped”——这就是问题所在,蓝色视图似乎仍然处于活动状态。

我在以下设备上测试了此行为: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")})
                }
            }
        }
    }
}

有什么解决方案可以防止这种情况发生吗?

这是 Swift 和值语义的新手遇到的常见问题,您可以使用称为“捕获列表”的东西来解决它,如下所示:

NavigationLink(destination: { [colourShowed] in

发生这种情况是因为 DetailView 在更改时没有使用colourShowed的新值重新初始化。 body 中没有任何内容使用它,因此 SwiftUI 的依赖项跟踪并不认为 body 必须重新计算。 但是由于您依赖于使用新值初始化 DetailView,因此您必须将其添加到捕获列表以强制重新计算 body 并初始化一个新的 DetailView。

以下是关于.sheet.task相同问题的其他问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM