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

SwiftUI NavigationSplitView详情视图无法更新问题求助

解决NavigationSplitView中自定义DetailView不更新的问题

在NavigationSplitView中,侧边栏通过ForEach生成NavigationLink的功能正常,选中列表项后,原生Text视图能正常显示更新后的内容,但自定义DetailView却无法响应选中变化。已简化代码复现问题,相关代码如下:

问题代码

ContentView.swift

struct ContentView: View {
    @StateObject var controller = DetailViewTestingController()
    
    var body: some View {
        NavigationSplitView {
            VStack {
                List(selection: $controller.selection) {
                    ForEach(controller.listOfObjects, id: \.self.id) { _object in
                        NavigationLink(value: _object.id) {
                            Text(_object.title ?? "no title")
                        }
                        .buttonStyle(PlainButtonStyle())
                        .padding(.vertical)
                    }
                }
            }
            .navigationSplitViewColumnWidth(240)
        } detail: {
            ZStack {
                VStack{
                    if (controller.selection ?? -1 >= 0) {
                        Text("Direct: \(controller.listOfObjects[controller.selection!].title ?? \"none\")")
                        Text("Found: \(controller.detailObject.title ?? \"none\")")
                        DetailView(detailObject: $controller.detailObject)
                    }
                }
            }
        }
    }
}

DetailView.swift

struct DetailView: View {
    @Binding var detailObject : DetailObject
    
    var body: some View {
        Text("Object Title: \(detailObject.title ?? \"none\")")
    }
}

DetailObject.swift

public class DetailObject : Identifiable, Hashable, ObservableObject {

    public var id: Int
    public var title : String?
    
    public init(id: Int, title: String? = nil) {
        self.id = id
        self.title = title
    }
    
    // MARK: Hashable Protocol
    
    open func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(title)
    }
    
    public static func == (lhs: DetailObject, rhs: DetailObject) -> Bool {
        if (lhs.id == rhs.id) { return false }
        if (lhs.title == rhs.title) { return false }
        return true
    }
}

DetailViewTestingController.swift

open class DetailViewTestingController : ObservableObject {
    @Published public var listOfObjects : [DetailObject] = []
    @Published public var detailObject : DetailObject = DetailObject(id: 0, title: "")
    @Published public var selection : Int? {
        didSet {
            detailObject = DetailObject(id: 0, title: "")
            if (selection == nil) { return }
            for d in listOfObjects {
                if (d.id == selection) {
                    detailObject = d
                    return
                }
            }
        }
    }
    
    public init() {
        listOfObjects.append(DetailObject(id: 0, title: "one"))
        listOfObjects.append(DetailObject(id: 1, title: "two"))
        listOfObjects.append(DetailObject(id: 2, title: "three"))
        listOfObjects.append(DetailObject(id: 3, title: "four"))
    }
}

问题原因

  • Equatable实现完全错误:DetailObject的==方法逻辑写反了,当前代码中如果两个对象id或title相等时返回false,这会导致SwiftUI无法正确判断对象是否发生变化,进而不会触发视图更新。
  • ObservableObject属性未发布:DetailObject继承了ObservableObject,但title属性没有添加@Published修饰符,虽然这里是直接替换整个对象,但如果后续需要修改对象内部属性,也会导致视图不更新。
  • 可选值处理不够安全:controller中selection的处理使用了强制解包,存在崩溃风险。

修复方案

1. 修正DetailObject的Equatable实现并添加@Published

修改DetailObject.swift,正确实现相等判断,并给title添加@Published:

public class DetailObject : Identifiable, Hashable, ObservableObject {

    public var id: Int
    @Published public var title : String?
    
    public init(id: Int, title: String? = nil) {
        self.id = id
        self.title = title
    }
    
    // MARK: Hashable Protocol
    
    open func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(title)
    }
    
    public static func == (lhs: DetailObject, rhs: DetailObject) -> Bool {
        // 正确逻辑:id和title都相等时才返回true
        return lhs.id == rhs.id && lhs.title == rhs.title
    }
}

2. 优化controller的selection处理逻辑

修改DetailViewTestingController.swift,用更简洁安全的方式获取选中对象:

open class DetailViewTestingController : ObservableObject {
    @Published public var listOfObjects : [DetailObject] = []
    @Published public var detailObject : DetailObject = DetailObject(id: 0, title: "")
    @Published public var selection : Int? {
        didSet {
            // 用first(where:)替代循环,更简洁安全
            guard let selectedId = selection else {
                detailObject = DetailObject(id: 0, title: "")
                return
            }
            detailObject = listOfObjects.first(where: { $0.id == selectedId }) ?? DetailObject(id: 0, title: "")
        }
    }
    
    public init() {
        listOfObjects.append(DetailObject(id: 0, title: "one"))
        listOfObjects.append(DetailObject(id: 1, title: "two"))
        listOfObjects.append(DetailObject(id: 2, title: "three"))
        listOfObjects.append(DetailObject(id: 3, title: "four"))
    }
}

3. 可选值安全处理(可选优化)

在ContentView中,避免强制解包selection:

// 替换原有的判断和文本
if let selectedId = controller.selection, 
   let selectedObj = controller.listOfObjects.first(where: { $0.id == selectedId }) {
    Text("Direct: \(selectedObj.title ?? \"none\")")
    Text("Found: \(controller.detailObject.title ?? \"none\")")
    DetailView(detailObject: $controller.detailObject)
}

内容的提问来源于stack exchange,提问作者ogre-dru

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 10:34:52