SwiftUI中LazyVStack内的confirmationDialog出现异常行为
问题现象
- 用嵌套
LazyVStack的ScrollView承载100个子视图,每个子视图内有触发confirmationDialog的按钮,对话框在子视图内部创建 - 滚动浏览3个左右子视图后,点击按钮无法立即弹出对话框;等待或继续滚动时,之前所有未触发的对话框会批量弹出
测试代码
struct ContentView: View { var body: some View { ScrollView { LazyVStack(spacing: 50) { ForEach(0...100, id: \.self) { _ in SubView() } } } .padding() } } struct SubView: View { @State var flag = false var body: some View { ZStack(alignment: .bottom) { RoundedRectangle(cornerRadius: 30) .frame(height: 500) .foregroundColor(.gray) .overlay { Button("Press me") { flag.toggle() } .confirmationDialog("", isPresented: $flag, actions: { Button(role: .none) { print("option 1") } label: { Text("option 1") } Button(role: .cancel) { flag = false } label: { Text("cancel") } }) } } } }
问题原因
LazyVStack会延迟加载非可视区域的视图,并且会复用已加载的视图。当滚动时,离开可视范围的子视图会被标记为待复用状态,其内部的@State变量flag的状态不会被自动重置。后续复用这些视图时,之前触发的flag = true状态被保留,当视图重新进入可视范围,对应的对话框就会批量弹出。
解决方案
方案一:给子视图绑定唯一标识,避免状态复用
为每个SubView添加唯一id并绑定到视图上,确保每个子视图的状态独立,不会被复用:
struct ContentView: View { var body: some View { ScrollView { LazyVStack(spacing: 50) { ForEach(0...100, id: \.self) { index in SubView(id: index) } } } .padding() } } struct SubView: View { let id: Int @State var flag = false var body: some View { ZStack(alignment: .bottom) { RoundedRectangle(cornerRadius: 30) .frame(height: 500) .foregroundColor(.gray) .overlay { Button("Press me") { flag.toggle() } .confirmationDialog("", isPresented: $flag, actions: { Button(role: .none) { print("option 1") } label: { Text("option 1") } Button(role: .cancel) { flag = false } label: { Text("cancel") } }) } } .id(id) // 绑定唯一id,确保每个视图状态独立 } }
方案二:用VStack替代LazyVStack(适合数据量较小场景)
如果子视图数量不多(比如少于50个),可以直接替换成VStack,所有视图会被一次性加载,不会出现复用导致的状态问题:
struct ContentView: View { var body: some View { ScrollView { VStack(spacing: 50) { // 替换为VStack ForEach(0...100, id: \.self) { _ in SubView() } } } .padding() } }
方案三:将状态提升到父视图管理
如果必须使用LazyVStack,可以把每个子视图的对话框状态提升到父视图,用字典存储每个子视图的状态,精准控制对话框显示:
struct ContentView: View { @State private var dialogFlags: [Int: Bool] = [:] var body: some View { ScrollView { LazyVStack(spacing: 50) { ForEach(0...100, id: \.self) { index in SubView(isPresented: $dialogFlags[index, default: false]) } } } .padding() } } struct SubView: View { @Binding var isPresented: Bool var body: some View { ZStack(alignment: .bottom) { RoundedRectangle(cornerRadius: 30) .frame(height: 500) .foregroundColor(.gray) .overlay { Button("Press me") { isPresented.toggle() } .confirmationDialog("", isPresented: $isPresented, actions: { Button(role: .none) { print("option 1") } label: { Text("option 1") } Button(role: .cancel) { isPresented = false } label: { Text("cancel") } }) } } } }
内容的提问来源于stack exchange,提问作者Javier Heisecke
相关产品推荐
相关产品推荐

