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

SwiftUI中Alert触发EditMode切换并重置选中项问题求助

解决SwiftUI列表Alert弹窗导致EditMode闪跳及选中项丢失问题

问题核心

添加.alert组件后,弹窗显示时EditMode会快速从.active切换为.inactive再恢复,不仅视觉闪烁,还会清空已选中的项,被迫使用额外的@State存储选中内容。

原因分析

SwiftUI的Alert会临时修改环境中的EditMode状态,触发列表的选择绑定重置,导致选中项丢失。解决的关键是完全手动管理EditMode状态,脱离系统环境的自动干扰,同时确保Alert的展示流程不影响选中项的存储。

解决方案

  1. 手动管理EditMode状态,绑定到List的.editMode修饰符
  2. 优化Alert触发逻辑,提前保存选中项,避免依赖弹窗期间的selectedItems
  3. 修复选择状态的清空逻辑,确保操作后状态一致

修改后的完整代码

import SwiftUI
import SwiftData

struct ItemsView: View {
    @Environment(\.modelContext) private var context
    @Query(sort: \Item.datePurchased, order: .reverse) var items: [Item]
    
    // ✅ 手动管理EditMode状态,绑定到List
    @State private var editMode: EditMode = .inactive
    @State private var showingAddItemSheet = false
    @State private var showingDeleteConfirmation = false
    @State private var selectedItems = Set<Item>()
    @State private var showingSellerSelection = false
    @State private var itemsToDelete = Set<Item>()
    @State private var selectedSeller: Seller?
    @State private var itemsToAssign = Set<Item>()

    var body: some View {
        NavigationStack {
            List(selection: $selectedItems) {
                if items.isEmpty {
                    ContentUnavailableView(
                        "No Items added yet",
                        systemImage: "cart",
                        description: Text("Please add new items")
                    )
                }
                
                ForEach(items) { item in
                    LabeledContent {
                        Text(item.price, format: .currency(code: "EUR"))
                            .fontWeight(.bold)
                    } label: {
                        Text(item.itemName.name)
                            .font(.headline)
                        Text("\(item.color), \(item.size)")
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                        Text(item.status)
                    }
                    .tag(item)
                }
            }
            // ✅ 将List的EditMode绑定到本地状态
            .editMode($editMode)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    if !items.isEmpty {
                        // ✅ 使用自定义EditButton,绑定本地状态
                        Button(editMode == .active ? "Done" : "Edit") {
                            withAnimation {
                                editMode = editMode == .active ? .inactive : .active
                            }
                        }
                    }
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    HStack {
                        if editMode == .active || !selectedItems.isEmpty {
                            Menu {
                                Button("Assign to Seller", systemImage: "person.crop.circle.badge.plus") {
                                    assignToSeller()
                                }
                                Button("Mark as Sold", systemImage: "checkmark.seal") {
                                    updateStatus(to: "Sold")
                                }
                                Button("Mark as Reserved", systemImage: "clock") {
                                    updateStatus(to: "Reserved")
                                }
                                Divider()
                                Button(role: .destructive) {
                                    itemsToDelete = selectedItems
                                    showingDeleteConfirmation = true
                                } label: {
                                    Label("Delete Selected", systemImage: "trash")
                                }
                            } label: {
                                Label("Actions", systemImage: "ellipsis.circle")
                            }
                        }
                        
                        Button("Add Item") {
                            showingAddItemSheet = true
                        }
                    }
                }
            }
            .sheet(isPresented: $showingAddItemSheet) {
                AddItemView()
            }
            .sheet(isPresented: $showingSellerSelection) {
                SellerSelectionView { seller in
                    confirmSellerAssignment(seller: seller)
                }
            }
            .alert("Delete Selected Items?", isPresented: $showingDeleteConfirmation) {
                Button("Delete", role: .destructive) {
                    withAnimation {
                        deleteSelectedItems()
                        clearSelection()
                    }
                }
                Button("Cancel", role: .cancel) {
                    itemsToDelete.removeAll()
                }
            }
        }
    }
    
    // MARK: - Helper Methods
    private func updateStatus(to status: String) {
        for item in selectedItems {
            item.status = status
        }
        try? context.save()
        clearSelection()
    }
    
    private func deleteSelectedItems() {
        guard !itemsToDelete.isEmpty else { return }
        for item in itemsToDelete {
            context.delete(item)
        }
        try? context.save()
        itemsToDelete.removeAll()
    }
    
    private func assignToSeller() {
        guard !selectedItems.isEmpty else { return }
        itemsToAssign = selectedItems
        showingSellerSelection = true
    }

    private func confirmSellerAssignment(seller: Seller) {
        for item in itemsToAssign {
            let sellerItem = SellerItem(seller: seller, item: item, salePrice: item.price, status: "With Seller")
            context.insert(sellerItem)
        }
        try? context.save()
        clearSelection()
    }
    
    // ✅ 修复清空选择的逻辑,同时重置EditMode
    private func clearSelection() {
        selectedItems.removeAll()
        if editMode == .active {
            editMode = .inactive
        }
    }
}

@Model
class Item: Identifiable {
    var id: UUID = UUID()
    var itemName: ItemName
    var color: String
    var price: Double
    var datePurchased: Date
    var status: String // Example: "Available", "Sold", "With Seller"
    var size: String
    var comments: String
    
    init(
        id: UUID = UUID(),
        itemName: ItemName,
        color: String,
        price: Double,
        datePurchased: Date = Date(),
        status: String = "Available",
        size: String = "",
        comments: String = ""
    ) {
        self.id = id
        self.itemName = itemName
        self.color = color
        self.price = price
        self.datePurchased = datePurchased
        self.status = status
        self.size = size
        self.comments = comments
    }
}

#Preview {
    let container = try! ModelContainer(for: Item.self, ItemName.self, configurations: ModelConfiguration(isStoredInMemoryOnly: true))
    
    let itemName = ItemName(name: "Shirt")
    let sampleItems = [
        Item(itemName: itemName, color: "Blue", price: 29.99, size: "M"),
        Item(itemName: itemName, color: "Red", price: 49.99, size: "L"),
        Item(itemName: itemName, color: "Green", price: 39.99, size: "S")
    ]
    
    for item in sampleItems {
        container.mainContext.insert(item)
    }
    
    return ItemsView().modelContainer(container)
}

关键修改点说明

  • 替换系统EditButton为自定义按钮,直接绑定本地editMode状态,避免系统环境干扰
  • 给List添加.editMode($editMode)修饰符,完全接管编辑状态的控制
  • 修复clearSelection方法,确保选中项清空后同步重置编辑状态
  • 保持itemsToDelete的预存储逻辑,确保Alert操作时使用独立的选中项副本,不受EditMode变化影响

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 03:07:04