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
相关产品推荐
相关产品推荐

