SwiftUI如何检测列表项跨Section移动并阻止报错?
解决SwiftUI列表跨分区拖拽排序的问题
问题描述
我有两个列表分别存放数字和字母,长按列表行可拖拽重排序,但SwiftUI没有阻止列表项在不同ForEach间移动,将字母拖拽到数字分区会引发错误。示例代码如下:
import SwiftUI struct ContentView: View { @State private var alphabets = ["a", "b", "c"] @State private var numbers = ["1", "2", "3"] var body: some View { NavigationView { List { Section("Alphabets") { ForEach(alphabets, id: \.self) { alphabet in Text(alphabet) } .onMove { fromOffsets, toOffset in alphabets.move(fromOffsets: fromOffsets, toOffset: toOffset) } } Section("numbers") { ForEach(numbers, id: \.self) { number in Text(number) } .onMove { fromOffsets, toOffset in alphabets.move(fromOffsets: fromOffsets, toOffset: toOffset) } } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
我记得iOS 16.0之前似乎不会出现此问题,希望实现如下逻辑:
.onMove { fromOffsets, toOffset in if destinationSectionId == thisSectionId { alphabets.move(fromOffsets: fromOffsets, toOffset: toOffset) } else { do nothing display an animation to move the item back } }
解决方案
可以通过判断拖拽目标的偏移范围,限制仅允许同分区内的拖拽排序,同时触发回弹动画,具体实现如下:
方法1:基于onMove回调的范围判断(适配iOS 16+)
在onMove回调中计算当前分区的偏移范围,判断目标位置是否在当前分区内,不在则触发回弹:
import SwiftUI struct ContentView: View { @State private var alphabets = ["a", "b", "c"] @State private var numbers = ["1", "2", "3"] var body: some View { NavigationStack { // iOS 16+推荐使用NavigationStack替代NavigationView List { Section("Alphabets") { ForEach(alphabets, id: \.self) { alphabet in Text(alphabet) } .onMove { fromOffsets, toOffset in // Alphabets分区的偏移范围:从0到列表元素数量 let sectionStart = 0 let sectionEnd = alphabets.count if toOffset >= sectionStart && toOffset <= sectionEnd { alphabets.move(fromOffsets: fromOffsets, toOffset: toOffset) } else { // 触发默认回弹动画 withAnimation {} } } } Section("Numbers") { ForEach(numbers, id: \.self) { number in Text(number) } .onMove { fromOffsets, toOffset in // Numbers分区的起始偏移 = Alphabets元素数 + 1(Section标题占位) let sectionStart = alphabets.count + 1 let sectionEnd = sectionStart + numbers.count if toOffset >= sectionStart && toOffset <= sectionEnd { // 目标偏移需要转换为当前分区内的相对偏移 numbers.move(fromOffsets: fromOffsets, toOffset: toOffset - sectionStart) } else { withAnimation {} } } } } .toolbar { EditButton() } } } }
关键说明
- iOS 16之后,SwiftUI默认允许跨分区拖拽,因此需要手动判断目标偏移是否在当前分区范围内。
- 当拖拽到其他分区时,空的
withAnimation {}会触发SwiftUI的默认回弹动画,将元素放回原位置。 - 计算分区偏移时要注意:每个Section的标题会占用一个偏移位置,所以Numbers分区的起始偏移需要加上Alphabets的元素数量和1个标题占位。
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

