SwiftUI使用LazyVGrid时点击网格项仅打开数组首个元素DetailView
问题根因
你把sheet(isPresented:)修饰符放在了ForEach循环内部,所有网格项共用同一个@State var detailView = false布尔值控制弹窗状态。当任意按钮把detailView设为true时,SwiftUI会触发最先渲染完成的第一个网格项绑定的弹窗,因此永远加载数组第一个元素的详情。之前使用List时表现正常是List的懒加载渲染逻辑的巧合,本质写法不符合SwiftUI的弹窗规范,本身就存在隐患。
修复方案
推荐使用sheet(item:)重载实现多数据项的弹窗绑定,这是SwiftUI专门针对这类场景提供的原生方案:
- 替换原有的弹窗控制状态变量
把@State var detailView = false修改为@State var selectedItem: Item? - 修改按钮点击逻辑
把按钮的action改为self.selectedItem = item - 把
sheet修饰符从ForEach循环中移出,放到外层视图上,使用item参数绑定选中项 - 额外优化:
ListRowView和DetailView中的item不需要用@State修饰,改为普通let常量即可,@State仅用于管理视图自身的可变私有状态,不需要修改的传值属性直接用常量声明即可。
修复后的核心代码示例
struct ListView: View { @EnvironmentObject var listViewModel: ListViewModel @State var addItemView = false // 替换原有的detailView布尔值 @State var selectedItem: Item? let columns = [GridItem(.flexible()), GridItem(.flexible())] var body: some View { NavigationView { ZStack { Color(.white) VStack { Button(action: { self.addItemView = true }, label: { Text("Add") }).sheet(isPresented: $addItemView, content: { AddItemView() }) LazyVGrid(columns: columns, spacing: 20) { ForEach(listViewModel.item, id:\.id){ item in Button(action: { // 点击时给选中项赋值 self.selectedItem = item }, label: { ListRowView(item: item) }) // 移除循环内部的sheet修饰符 } } // sheet移到外层,绑定选中项 .sheet(item: $selectedItem, content: { item in DetailView(item: item) }) Spacer() } } } } } // 优化ListRowView的属性声明 struct ListRowView: View { // 去掉@State,改为let常量 let item: Item var body: some View{ VStack { Text(item.name).foregroundColor(.white) }.frame(width: 150, height: 150, alignment: .center) .background(Color.blue) .cornerRadius(10) } } // 优化DetailView的属性声明 struct DetailView: View { // 去掉@State,改为let常量 let item: Item var body: some View { Text(item.name) } }
内容的提问来源于stack exchange,提问作者Lloyd English
相关产品推荐
相关产品推荐

