SwiftUI NavigationSplitView详情视图无法更新问题求助
在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
相关产品推荐
相关产品推荐

