SwiftUI列表点击不同单元格按钮时Popover无法正常关闭如何解决
问题原因
你当前的实现为每个列表单元格单独定义了Bool类型的showingPopover状态,点击不同单元格时会有多个状态同时为true。SwiftUI的弹出层规则同一时间仅允许展示一个popover,系统检测到已有弹出层在展示时就会抛出你遇到的already presenting报错,旧弹出层不会自动关闭,新弹出层也无法正常渲染。
推荐解决方案
使用全局唯一的可选类型状态记录当前激活的单元格ID,替代每个单元格单独维护的Bool状态,保证同一时间最多只有一个弹出层处于激活状态。
- 首先为你的列表数据模型遵守
Identifiable协议,确保每个单元格有唯一ID:
struct ListItem: Identifiable { let id = UUID() // 其他业务属性 }
- 替换原有的单单元格Bool状态,改为全局存储激活ID:
// 原代码每个单元格的 @State private var showingPopover = false 废弃 @State private var activePopoverItemID: UUID? = nil
- 调整按钮点击逻辑和popover绑定规则:
Button(action: { // 点击直接赋值当前单元格ID,旧ID会自动失效 activePopoverItemID = item.id }, label: { Image(systemName: "gearshape") }) .buttonStyle(BorderlessButtonStyle()) // 自定义绑定:当前单元格ID等于激活ID时展示popover .popover(isPresented: .init( get: { activePopoverItemID == item.id }, set: { newValue in if !newValue { activePopoverItemID = nil } } ), arrowEdge: .leading) { AccessoryIconView(viewModel: viewModel.iconViewModel()) .frame(width: 200, height: 300) }
这种实现方式会在你点击新单元格按钮时,自动让旧弹出层的展示条件变为false触发关闭,再弹出新的弹出层,完全符合系统弹出规则,不会产生报错。
临时兼容方案(不推荐)
如果你暂时不想调整状态结构,可以在按钮点击时先清空所有弹出层状态,延迟极短时间再激活当前按钮的弹出层:
Button(action: { // 先重置所有单元格的showingPopover为false showingPopover = false DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { showingPopover = true } }, label: { Image(systemName: "gearshape") }) // 其余popover代码保持不变
该方案依赖硬编码的延迟时间,极端情况下可能出现时序问题,仅作为临时修复使用。
内容的提问来源于stack exchange,提问作者AirXygène
相关产品推荐
相关产品推荐

