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

SwiftUI中接收Publisher值时如何更新List选中行

问题分析与解决方案

核心问题

你在ForEach的每个列表项上绑定onReceive监听全局@Published值的做法不合理。因为每个列表项视图都会独立订阅同一个发布者,当发布者的值变化时,所有项的onReceive闭包都会执行一遍——这就是所有列表项都被更新的根本原因,哪怕你最后把publish设为false,所有项已经完成了一轮修改逻辑。

最优解决方案:直接操作当前项

完全不需要额外的ObservableObject中转,在contextMenu的按钮里直接修改触发菜单的列表项即可,这和Notes应用的长按操作逻辑完全一致:

import SwiftUI

struct Ocean: Identifiable {
    let name: String
    let id = UUID()
    var hasPhoto: Bool = false
}

struct OceanDetails: View {
    var ocean: Ocean

    var body: some View {
        Text("\(ocean.name)")
    }
}

struct ContentView: View {
    @State private var oceans = [
        Ocean(name: "Pacific"),
        Ocean(name: "Atlantic"),
        Ocean(name: "Indian"),
        Ocean(name: "Southern"),
        Ocean(name: "Arctic")
    ]

    var body: some View {
        NavigationView {
            List() {
                ForEach(Array(oceans.enumerated()), id: \.element.id) { index, ocean in
                    NavigationLink(destination: OceanDetails(ocean: ocean)) {
                        if ocean.hasPhoto {
                            Text(ocean.name) + Text(Image(systemName: "photo"))
                        } else {
                            Text(ocean.name)
                        }
                    }
                    .contextMenu {
                        Button("Publish") {
                            // 直接修改当前索引对应的项,无需全局发布者
                            oceans[index].hasPhoto.toggle()
                        }
                    }
                }
                .onDelete(perform: removeRows)
            }
        }
    }

    func removeRows(at offsets: IndexSet) {
        withAnimation {
            oceans.remove(atOffsets: offsets)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

自定义类似onDelete的修饰符(复用场景)

如果需要复用这类操作逻辑,可以自定义视图修饰符,实现和onDelete类似的调用方式:

// 自定义修饰符
struct ContextMenuPublishModifier: ViewModifier {
    let index: Int
    let action: (Int) -> Void

    func body(content: Content) -> some View {
        content
            .contextMenu {
                Button("Publish") {
                    action(index)
                }
            }
    }
}

// 扩展View,简化调用
extension View {
    func publishContextMenu(index: Int, action: @escaping (Int) -> Void) -> some View {
        self.modifier(ContextMenuPublishModifier(index: index, action: action))
    }
}

在ContentView中使用:

ForEach(Array(oceans.enumerated()), id: \.element.id) { index, ocean in
    NavigationLink(destination: OceanDetails(ocean: ocean)) {
        if ocean.hasPhoto {
            Text(ocean.name) + Text(Image(systemName: "photo"))
        } else {
            Text(ocean.name)
        }
    }
    .publishContextMenu(index: index) { index in
        oceans[index].hasPhoto.toggle()
    }
}

关键总结

  1. 避免在ForEach的每个元素上绑定全局发布者的onReceive,会导致不必要的视图更新和逻辑错误。
  2. 优先直接在操作按钮中处理当前项,逻辑清晰且性能更优。
  3. 如需复用操作逻辑,通过自定义视图修饰符实现类似系统onDelete的调用范式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 11:27:29