SwiftUI列表.moveDisabled异常:无法在Banana上下移动其他元素
SwiftUI列表移动异常问题:Banana不可移动时相邻元素无法跨位置移动
我创建了一个包含多种水果的SwiftUI List组件,需求是允许列表内的水果项移动,但Banana项不可移动。代码如下:
struct FruitsView: View { @State private var fruits: [String] = ["Apple", "Orange", "Banana", "Pear", "Mango", "Strawberry"] var body: some View { List{ ForEach(fruits, id: \.self){ fruit in Text(fruit) .moveDisabled(fruit == "Banana") } .onMove(perform: move) .onDelete(perform: delete) } } func move(indexSet: IndexSet, int: Int){ fruits.move(fromOffsets: indexSet, toOffset: int) } func delete(indexSet: IndexSet){ fruits.remove(atOffsets: indexSet) } }
出现的问题:
- 场景1:无法将Banana上方的元素(如Orange)移至Banana的正下方

- 场景2:无法将Banana下方的元素(如Pear)移至Banana的正上方

问题根源与解决方案
问题出在moveDisabled(_:)的逻辑上:它不仅禁止Banana本身被移动,还会拦截任何可能导致Banana相对位置变化的操作——当尝试将元素移到Banana相邻位置时,SwiftUI会判定该操作可能影响不可移动元素的位置,从而阻止操作执行。
方案1:拆分列表分区处理
将列表拆分为「Banana上方元素」「固定Banana」「Banana下方元素」三个独立部分,分别处理移动逻辑,确保各区域内的元素可自由移动,同时Banana位置固定:
struct FruitsView: View { @State private var fruits: [String] = ["Apple", "Orange", "Banana", "Pear", "Mango", "Strawberry"] // 拆分Banana上下的元素数组 private var topFruits: [String] { fruits.prefix(while: { $0 != "Banana" }) } private var bottomFruits: [String] { guard let bananaIndex = fruits.firstIndex(of: "Banana") else { return [] } return fruits.suffix(from: bananaIndex + 1) } var body: some View { List { // Banana上方元素区域 ForEach(topFruits, id: \.self) { fruit in Text(fruit) } .onMove(perform: moveTop) // 固定的Banana项 Text("Banana") .moveDisabled(true) // Banana下方元素区域 ForEach(bottomFruits, id: \.self) { fruit in Text(fruit) } .onMove(perform: moveBottom) } .onDelete(perform: delete) } // 处理上方元素移动 func moveTop(from source: IndexSet, to destination: Int) { var mutableTop = topFruits mutableTop.move(fromOffsets: source, toOffset: destination) guard let bananaIndex = fruits.firstIndex(of: "Banana") else { return } fruits.replaceSubrange(0..<bananaIndex, with: mutableTop) } // 处理下方元素移动 func moveBottom(from source: IndexSet, to destination: Int) { var mutableBottom = bottomFruits mutableBottom.move(fromOffsets: source, toOffset: destination) guard let bananaIndex = fruits.firstIndex(of: "Banana") else { return } fruits.replaceSubrange(bananaIndex + 1..<fruits.endIndex, with: mutableBottom) } // 禁止删除Banana(可选) func delete(at offsets: IndexSet) { let validOffsets = offsets.filter { fruits[$0] != "Banana" } fruits.remove(atOffsets: validOffsets) } }
方案2:调整移动目标偏移量
直接修改move函数,根据元素位置动态调整目标偏移量,绕过Banana的位置限制:
struct FruitsView: View { @State private var fruits: [String] = ["Apple", "Orange", "Banana", "Pear", "Mango", "Strawberry"] var body: some View { List{ ForEach(fruits, id: \.self){ fruit in Text(fruit) .moveDisabled(fruit == "Banana") } .onMove(perform: move) .onDelete(perform: delete) } } func move(from source: IndexSet, to destination: Int) { guard let bananaIndex = fruits.firstIndex(of: "Banana") else { return } var adjustedDestination = destination // 上方元素移到Banana下方时,偏移量减1 if source.contains(where: { $0 < bananaIndex }) && destination > bananaIndex { adjustedDestination -= 1 } // 下方元素移到Banana上方时,偏移量加1 else if source.contains(where: { $0 > bananaIndex }) && destination <= bananaIndex { adjustedDestination += 1 } fruits.move(fromOffsets: source, toOffset: adjustedDestination) } func delete(indexSet: IndexSet){ fruits.remove(atOffsets: indexSet) } }
内容的提问来源于stack exchange,提问作者Harry CHOI
相关产品推荐
相关产品推荐

