SwiftUI:如何更新 NavigationView 详细信息屏幕?

Posted

技术标签:

【中文标题】SwiftUI:如何更新 NavigationView 详细信息屏幕?【英文标题】:SwiftUI: How to Update NavigationView Detail Screens? 【发布时间】:2020-04-28 16:17:34 【问题描述】:

我有一个从 REST 后端服务器获取车辆列表的应用。然后,它使用该列表来构建车辆列表,点击这些车辆可以显示其中一辆的详细信息:

@State private var selectedVehicle: Vehicle?
@Binding var vehicles: [Vehicle]

List 
  NavigationView 

    ForEach( vehicles )  vehicle in
            NavigationLink( destination: VehicleDetailScreen( vehicle: vehicle ),
                            tag: vehicle,
                            selection: self.$selectedVehicle ) 
                                Text( vehicle.name )
            
    
  


struct VehicleDetailScreen: View     
    var vehicle: Vehicle

    var body: some View 
       // Lots of rendering code omitted
    



到目前为止,一切都很好。这很好用。当我们从服务器获取更新的信息时,问题就出现了。更新绑定的vehicles 属性非常适合更新列表。但详细信息屏幕仍显示不再相关的数据。

我的第一个想法就是从 NavigationView 中弹出详细视图。不幸的是,SwiftUI 没有提供任何可靠的方法来在 iPad 上的两列视图中执行此操作。

我的下一个想法是我们需要将车辆作为@Binding 传递给 VehicleDetailScreen 以便我们可以更新它。但这也很难做到,因为我们需要对该绑定的引用,以便我们可以将更新的值填入其中。我能想到的唯一方法是完全重写我们的网络和模型对象代码,使其像 CoreData 一样工作,将对象保存在内存中并使用来自服务器的新值更新它们,而不是生成新对象。这将是一个很大的努力,如果有其他选择,显然不是我热衷于做的事情。

所以我有点坚持这一点。非常欢迎任何想法/想法/建议!

【问题讨论】:

我认为我们不需要查看VehicleDetailScreen。您如何更新 vehicles 属性?向我们展示这方面的代码 【参考方案1】:

也许@Binding 的概念有些混乱。从@State var(父视图)到@Binding var(子视图)。 一个struct Hashable,用于方便和重新排序数组[Vehicle] 的元素。

类似这样的:

struct Vehicle: Hashable 
    var name:String
    //var otherItem: Any


struct ContentView: View 

    @State var vehicle: Vehicle //the struct of your REST
    @State var vehicles: [Vehicle] // the array of your REST

    var body: some View 
        List 
          NavigationView 
            ForEach(vehicles, id:\.self)  item in // loop the array to get every single item conform to the struct
                NavigationLink( destination: VehicleDetailScreen(vehicle: self.$vehicle))  // here to pass the binding
                    Text("\(self.vehicle.name)")
                
            
          
        
    


//detail view
struct VehicleDetailScreen: View 
    @Binding var vehicle: Vehicle // here the binding
    var body: some View 
        Text("\(vehicle.name)")
    

【讨论】:

这实际上并不能解决问题,唉,因为 something 必须负责更新该车辆对象。此外,在 ForEach 中,您不会在循环中显示当前车辆的名称,而是始终显示作为属性的车辆的名称。 :( @SeanMcMains 尝试检查dynamicMemberLookup【参考方案2】:

如果您希望在数据更改时更新详细视图,则必须使用绑定。

就架构而言,我建议创建所谓的 Stores 来保存可在多个视图中使用的数据。这与 Stores 的一些静态提供程序相结合,使您可以在任何地方轻松访问和修改数据,并让您的视图自动更新。

使用 UIKit 时,您可以通过调用 reloadTable 来手动刷新数据。在 SwiftUI 中,这还没有完成。假设您可以手动触发要更新的视图,但我建议不要这样做,因为这不是 SwiftUI 的预期方式。

我已经修改了您的代码以显示一个示例:

class StoreProvider 
    static let carStore = CarStore()


class CarStore: ObservableObject 
    @Published var vehicles: [Vehicle] = [Vehicle(id: "car01", name: "Porsche", year: 2016), Vehicle(id: "car02", name: "Lamborghini", year: 2002)]


struct Vehicle: Identifiable, Hashable 
    let id: String
    var name: String
    var year: Int


struct CarOverview: View 
    @ObservedObject var store = StoreProvider.carStore
    @State var selectedVehicle: Vehicle?

    var body: some View 
        NavigationView 
            List 
                ForEach(store.vehicles.indices)  vehicleIndex in
                    NavigationLink(destination: VehicleDetailScreen(vehicle: self.$store.vehicles[vehicleIndex])) 
                        Text(self.store.vehicles[vehicleIndex].name)
                    .onTapGesture 
                        self.selectedVehicle = self.store.vehicles[vehicleIndex]
                    
                
            
        
    


struct VehicleDetailScreen: View 
    @Binding var vehicle: Vehicle

    func updateValues() 
        vehicle.year = Int.random(in: 1990..<2020)
    

    var body: some View 
        VStack 
            Text(vehicle.name)
            Text("Year: ") + Text(vehicle.year.description)
        .onTapGesture(perform: updateValues)
    

【讨论】:

感谢这个例子。使用索引来处理集合,而不仅仅是 ForEach 和集合中的每个值 in 对我来说感觉有点奇怪,但我能够使用这种方法让事情正常工作。谢谢你的想法! 嗯,现在我被我的这种方法所困扰。如果我在列表中有 12 项,请选择第 12 项,然后 REST 调用将列表更新为只有 6 项,绑定仍在尝试解析到第 12 项并导致硬崩溃。 :( 没错,这不适用于删除项目,因为ForEach(_ Range) 仅适用于常量数据。您可以尝试将 Vehicle 结构设为ObservableObject ,然后在没有索引的车辆上使用 ForEach,并在详细视图中将车辆属性定义为 @ObservedObject 而不是 @Binding

以上是关于SwiftUI:如何更新 NavigationView 详细信息屏幕?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 SwiftUI 应用程序在 `NavigationView` 中的 `navigationBarItems` 内放置 `NavigationLink` 后向后导航时崩溃?

如何使用 SwiftUI 更新不同的视图?

如何解决“SwiftUI 无法更新预览”问题?

如何正确处理更新视图中的选取器(SwiftUI)

如何在 SwiftUI 中更新文本标签?

更新单独结构中的@AppStorage 后如何更新 SwiftUI 视图