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

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的正下方
    场景1
  • 场景2:无法将Banana下方的元素(如Pear)移至Banana的正上方
    场景2

问题根源与解决方案

问题出在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 08:27:38