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

SwiftUI中如何保持SwipeActions处于展开状态?

SwiftUI 保持SwipeActions展开至确认弹窗完成

问题原因

SwiftUI的系统.alert会在isPresented状态变化时触发父视图(如List)的重渲染,导致已展开的swipeActions自动收起,用户无法明确当前操作的条目。

解决方案

核心思路是避免触发List的视图刷新,从而保留swipeActions的展开状态,以下两种方案均可实现:

方案1:自定义SwiftUI弹窗(推荐)

通过ZStack将自定义弹窗叠加在List上方,完全控制弹窗显示逻辑,不影响List的渲染状态。

步骤1:创建自定义弹窗组件

struct ConfirmationPopup: View {
    let title: String
    let message: String
    let onCancel: () -> Void
    let onConfirm: () -> Void
    
    var body: some View {
        ZStack {
            // 半透明背景遮罩,点击可取消
            Color.black.opacity(0.3)
                .ignoresSafeArea()
                .onTapGesture(perform: onCancel)
            
            // 弹窗主体
            VStack(spacing: 16) {
                Text(title)
                    .font(.headline)
                Text(message)
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                    .multilineTextAlignment(.center)
                
                HStack(spacing: 24) {
                    Button("Cancel", role: .cancel, action: onCancel)
                    Button("Delete", role: .destructive, action: onConfirm)
                }
            }
            .padding()
            .background(Color.white)
            .cornerRadius(12)
            .padding(.horizontal, 20)
        }
    }
}

步骤2:修改主视图使用自定义弹窗

struct ContentView: View {
    @State private var numbers = Array(1...10)
    @State private var itemToDelete: Int?
    
    var body: some View {
        ZStack {
            List {
                ForEach(numbers, id: \.self) { number in
                    Text("\(number)")
                        .swipeActions {
                            Button(role: .none) {
                                itemToDelete = number
                            } label: {
                                Label("Delete", systemImage: "trash")
                            }
                            .tint(.red)
                        }
                }
            }
            
            // 仅当有待删除条目时显示弹窗
            if let targetItem = itemToDelete {
                ConfirmationPopup(
                    title: "Confirm",
                    message: "Are you sure you want to delete \(targetItem)?",
                    onCancel: {
                        itemToDelete = nil
                    },
                    onConfirm: {
                        deleteItem(targetItem)
                    }
                )
                .transition(.opacity.combined(with: .scale))
                .animation(.easeInOut, value: itemToDelete)
            }
        }
    }
    
    private func deleteItem(_ item: Int) {
        guard let index = numbers.firstIndex(of: item) else { return }
        numbers.remove(at: index)
        itemToDelete = nil
    }
}

方案2:封装UIKit原生弹窗

通过UIViewControllerRepresentable封装UIKit的UIAlertController,在UIKit层级展示弹窗,避免SwiftUI视图重渲染。

步骤1:创建UIKit弹窗封装

struct UIKitAlert: UIViewControllerRepresentable {
    let title: String
    let message: String
    let onCancel: () -> Void
    let onConfirm: () -> Void
    
    func makeUIViewController(context: Context) -> UIViewController {
        UIViewController()
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        // 避免重复弹窗
        guard context.coordinator.alert == nil else { return }
        
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
            onCancel()
            context.coordinator.alert = nil
        })
        alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in
            onConfirm()
            context.coordinator.alert = nil
        })
        
        uiViewController.present(alert, animated: true)
        context.coordinator.alert = alert
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator {
        var alert: UIAlertController?
    }
}

步骤2:在主视图中使用

struct ContentView: View {
    @State private var numbers = Array(1...10)
    @State private var showAlert = false
    @State private var itemToDelete: Int?
    
    var body: some View {
        List {
            ForEach(numbers, id: \.self) { number in
                Text("\(number)")
                    .swipeActions {
                        Button(role: .none) {
                            itemToDelete = number
                            showAlert = true
                        } label: {
                            Label("Delete", systemImage: "trash")
                        }
                        .tint(.red)
                    }
            }
        }
        .background(
            UIKitAlert(
                title: "Confirm",
                message: "Are you sure you want to delete this item?",
                onCancel: {
                    showAlert = false
                    itemToDelete = nil
                },
                onConfirm: {
                    deleteItem()
                    showAlert = false
                }
            )
            .opacity(showAlert ? 1 : 0)
        )
    }
    
    private func deleteItem() {
        guard let item = itemToDelete, let index = numbers.firstIndex(of: item) else { return }
        numbers.remove(at: index)
        itemToDelete = nil
    }
}

效果说明

两种方案均能实现:点击swipeActions的删除按钮后,弹窗显示期间保持swipeActions展开状态,用户可清晰看到待删除条目;操作完成后弹窗关闭,swipeActions自动收起(或条目被删除)。

内容的提问来源于stack exchange,提问作者Kngw Kngw

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:41:00