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

SwiftUI中基于可选Item绑定的删除确认对话框连续点击同一列表项仅显示一次的问题

SwiftUI中基于可选Item绑定的删除确认对话框连续点击同一列表项仅显示一次的问题

这个问题的核心原因很明确:你的自定义Binding没有在对话框关闭后重置可选绑定的状态!

问题分析

当你第一次点击某个Plan项时,selectedPlanForDeletion被设置为对应的实例,此时你自定义的binding的get闭包返回true,对话框正常弹出。但问题在于:

  • 你自定义的binding的set闭包是空的,对话框关闭后selectedPlanForDeletion没有被重置为nil
  • 当你再次点击同一个Plan项时,selectedPlanForDeletion的值并没有发生变化(还是之前的那个实例),SwiftUI的状态更新机制没有被触发——因为binding的get闭包一直返回true,但对话框已经处于关闭状态,自然不会再次弹出。
  • 而点击不同的项时,selectedPlanForDeletion被设置为新的实例,状态发生了变化,所以对话框会再次显示。

修复方案:在对话框关闭时自动重置可选绑定

我们需要修改自定义的confirmDeletion扩展,确保对话框关闭时(无论点击删除还是取消),都把selectedPlanForDeletion重置为nil。这样下次点击同一个项时,状态会从nil变为对应实例,触发对话框的显示。

修改后的完整扩展代码:

extension View {
    // 保留原有的基于isPresented的实现
    func confirmDeletion(isPresented: Binding<Bool>, title: String? = nil, action: @escaping ()-> Void) -> some View {
        confirmationDialog("", isPresented: isPresented) {
            Button("Delete", role: .destructive) {
                action()
            }
            // 新增明确的取消按钮,确保关闭逻辑被正确触发
            Button("Cancel", role: .cancel) {}
        } message: {
            Text(title ?? "")
        }
    }
    
    // 修复后的基于Item绑定的实现
    func confirmDeletion<Item>(item: Binding<Item?>, title: String? = nil, action: @escaping (Item)-> Void) -> some View {
        // 重新定义Binding,在set时处理状态重置
        let isPresentedBinding = Binding<Bool>(
            get: { item.wrappedValue != nil },
            set: { shouldPresent in
                // 当对话框关闭时(shouldPresent变为false),重置可选绑定为nil
                if !shouldPresent {
                    item.wrappedValue = nil
                }
            }
        )
        
        return confirmDeletion(isPresented: isPresentedBinding, title: title) {
            guard let selectedItem = item.wrappedValue else { return }
            action(selectedItem)
            // 执行删除动作后,手动重置状态(双重保险)
            item.wrappedValue = nil
        }
    }
}

为什么这样能解决问题?

  1. 对话框关闭时自动重置:在自定义的isPresentedBinding的set闭包里,当对话框关闭(shouldPresent变为false),会强制把item.wrappedValue设为nil,确保下次点击同一个项时,selectedPlanForDeletion会从nil变为对应实例,触发SwiftUI的状态更新。
  2. 删除动作后手动重置:在删除动作的闭包里也手动重置状态,避免因为系统对话框的关闭逻辑差异导致状态没有被正确重置。
  3. 明确的取消按钮:给confirmationDialog添加明确的取消按钮,不仅提升用户体验,还能确保对话框的关闭逻辑被正确触发。

你的视图代码无需修改

保持你原来的视图使用逻辑即可,状态重置的逻辑已经被封装在confirmDeletion扩展里了:

@State var selectedPlanForDeletion: Plan?

var body: some View {
    VStack {
        ForEach(plans) { plan in
            PlanElementView(plan: plan)
                .onTapGesture {
                    selectedPlanForDeletion = plan
                }
        }
    }
    .confirmDeletion(
        item: $selectedPlanForDeletion,
        title: "Are you sure you want to delete this plan?"
    ) { plan in
        print("Deleting plan: \(plan)")
        // 在这里添加实际的删除逻辑,比如从plans数组中移除该plan
    }
}

额外小建议

如果想让点击同一个项时,即使当前已经处于选中状态也能触发对话框,可以在onTapGesture里加个小判断(不过上面的修复已经从根源解决了问题,这个是可选优化):

.onTapGesture {
    if selectedPlanForDeletion == plan {
        selectedPlanForDeletion = nil
        // 用DispatchQueue.async确保状态更新完成后再设置
        DispatchQueue.main.async {
            selectedPlanForDeletion = plan
        }
    } else {
        selectedPlanForDeletion = plan
    }
}

不过这个方案不如前面的优雅,因为它需要在视图层处理状态逻辑,而前面的方案把状态重置逻辑封装在扩展里,更符合SwiftUI的封装原则。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 09:08:05