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

SwiftUI List的插入、删除、移动、选择及平滑动画问题

SwiftUI List 删除、插入、移动及选择问题解决方案

核心问题修复:Locations的ID稳定性

你的Locations实现Identifiable时,每次访问id都会生成新UUID,这会导致SwiftUI无法正确追踪列表行的身份,是选择、编辑功能异常的根本原因。先修复这个问题:

struct Locations {
    var name: String
    let id: String // 改为初始化时生成,固定不变
    
    init(name: String) {
        self.name = name
        self.id = UUID().uuidString
    }
}

// 移除原计算属性,无需重复实现Identifiable
extension Locations: Hashable { }

问题1:行选择失效与Edit按钮异常

原因

默认EditButton触发的是删除/移动编辑模式,而非选择模式;同时List的selection绑定需要配合手动的编辑状态管理。

解决方案

手动管理EditMode状态,区分选择模式和编辑(删除/移动)模式:

struct ContentView: View {
    @StateObject private var model = Model()
    @State private var editMode: EditMode = .inactive // 手动控制编辑模式
    
    var body: some View {
        NavigationView {
            List(selection: $model.selectedLocation) {
                ForEach(model.identifiableLocations) { location in
                    Text(location.name)
                }
                .onDelete(perform: delete(of:))
                .onMove(perform: move) // 启用行移动功能
            }
            .listStyle(.plain)
            .navigationTitle("Places")
            .toolbar {
                // 自定义Edit按钮,切换编辑状态并添加过渡动画
                Button(editMode == .inactive ? "Edit" : "Done") {
                    withAnimation {
                        editMode = editMode == .inactive ? .active : .inactive
                    }
                }
                Button {
                    model.addLocation(name: "Test")
                } label: {
                    Image(systemName: "plus")
                }
            }
            .environment(\.editMode, $editMode) // 将编辑模式注入环境
        }
    }
    
    func delete(of indexSet: IndexSet){
        withAnimation(.easeInOut(duration: 0.3)) {
            indexSet.forEach { index in
                model.delete(itemAt: index)
            }
        }
    }
    
    func move(from source: IndexSet, to destination: Int) {
        withAnimation {
            model.identifiableLocations.move(fromOffsets: source, toOffset: destination)
        }
    }
}

现在点击Edit按钮会进入可选择的编辑状态,点击行即可选中;再次点击Done退出编辑。


问题2:将selectedObject移至Model中

完全可以,只需在Model中添加@Published属性即可,符合MVVM架构:

extension ContentView {
    @MainActor class Model: ObservableObject {
        @Published private(set) var identifiableLocations = [Locations(name: "USA"), Locations(name: "Switzerland")]
        @Published var selectedLocation: Locations? // 把选择状态移至Model
    }
}

之后在ContentView的List中直接绑定$model.selectedLocation即可,选择状态会与Model同步。


问题3:实现类似UITableView的绿色插入按钮

SwiftUI没有内置的绿色插入按钮,但可以通过两种方式实现类似效果:

方案1:编辑模式下显示底部插入行

List(selection: $model.selectedLocation) {
    ForEach(model.identifiableLocations) { location in
        Text(location.name)
    }
    .onDelete(perform: delete(of:))
    .onMove(perform: move)
    
    // 仅在编辑模式下显示插入行
    if editMode == .active {
        Button {
            model.addLocation(name: "New Place")
        } label: {
            HStack {
                Image(systemName: "plus.circle.fill")
                    .foregroundColor(.green)
                Text("Add New Place")
            }
        }
    }
}

方案2:自定义行内插入按钮

在每行右侧添加绿色插入按钮,点击可在当前行下方插入新项:

ForEach(model.identifiableLocations) { location in
    HStack {
        Text(location.name)
        Spacer()
        if editMode == .active {
            Button {
                guard let index = model.identifiableLocations.firstIndex(of: location) else { return }
                model.identifiableLocations.insert(Locations(name: "New Place"), at: index + 1)
            } label: {
                Image(systemName: "plus.circle.fill")
                    .foregroundColor(.green)
            }
        }
    }
}

问题4:编辑/删除动画不流畅

默认动画时长较短,可通过withAnimation指定自定义时长和曲线优化:

// 删除时的动画控制
func delete(of indexSet: IndexSet){
    withAnimation(.easeInOut(duration: 0.3)) {
        indexSet.forEach { index in
            model.delete(itemAt: index)
        }
    }
}

// 添加位置时也可添加动画
extension ContentView.Model {
    func addLocation(name: String) {
        withAnimation(.easeInOut(duration: 0.2)) {
            identifiableLocations.append(Locations(name: name))
        }
    }
}

同时确保Locations的id稳定(前面已修复),否则SwiftUI会因无法追踪行身份导致动画异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 03:30:58