You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 01:25:40