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

