【问题标题】:Selection in SwiftUI NavigationView lost if List order changes如果列表顺序更改,SwiftUI NavigationView 中的选择会丢失
【发布时间】:2020-07-05 09:14:11
【问题描述】:

这是测试数据模型:

class Item: Identifiable {
  let name: String

  init( n: Int) {
    self.name = "\(n)"
  }
}

class Storage: ObservableObject {
  @Published var items = [Item( n: 1), Item( n: 2)]

  func reverse() {
    items = self.items.reversed()
  }
}

这是我的内容视图,带有 NavigationLink 和带有反转项目顺序按钮的详细视图:

struct ContentView: View {

  @ObservedObject
  var storage = Storage()

  var body: some View {
    NavigationView {
      List {
        ForEach( storage.items) { item in
          NavigationLink( destination: Button( action: {
            self.storage.reverse()
          }) {
            Text("Reverse")
          }) {
            Text( item.name).padding()
          }
        }
      }
    }
  }
}

现在,如果我点击 ReverseNavigationViewList 似乎会失去选择,弹出视图,然后再次推送:

这是预期的行为还是 SwiftUI 中的错误?有解决方法吗?我希望详细视图保持原样,无需重新加载。

【问题讨论】:

  • 您正在更改已观察对象中的已发布值。您的视图将重新加载,这会导致上述行为。
  • 我想要达到的效果与例如macOS 上的 Notes 应用程序:主视图是按上次修改日期排序的列表。然后,当我单击旧笔记并对其进行更改时,所选笔记将移至列表顶部。你认为这在 SwiftUI 中是不可能实现的吗?

标签: swift swiftui swiftui-list swiftui-navigationlink


【解决方案1】:

您需要为您的ForEach 循环指定一个显式的id

如果您使用静态 ForEach(不带 id 参数),您的视图将重新构建,因为数据 (storage.items) 已更改。

尝试以下方法:

struct ContentView: View {
    @ObservedObject
    var storage = Storage()

    var body: some View {
        NavigationView {
            List {
                ForEach(storage.items, id:\.name) { item in // <- add `id` parameter
                    NavigationLink(destination: self.destinationView) {
                        Text(item.name).padding()
                    }
                }
            }
        }
    }
    
    var destinationView: some View {
        Button(action: {
            self.storage.reverse()
        }) {
            Text("Reverse")
        }
    }
}

但是,此方法仅在所选项目的原始位置保持不变的情况下才有效。

在此示例中,从项目 1 的详细信息屏幕执行update()不会弹出 NavigationLink。

class Storage: ObservableObject {
    @Published var items = [Item(n: 1), Item(n: 2)]

    func update() {
        items = [Item(n: 1), Item(n: 3)]
    }
}

这里有一个解决方法可以让它工作(使用一个空的NavigationLink):

struct ContentView: View {
    @ObservedObject var storage = Storage()
    @State var isLinkActive = false

    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(storage.items, id:\.name) { item in
                        Button(action: {
                            self.isLinkActive = true
                        }) {
                            Text(item.name).padding()
                        }
                    }
                }
                NavigationLink(destination: self.destinationView, isActive: $isLinkActive) {
                    EmptyView()
                }
            }
        }
    }

    var destinationView: some View {
        Button(action: {
            self.storage.reverse()
        }) {
            Text("Reverse")
        }
    }
}

【讨论】:

  • 这适用于我在 Xcode 11.5、iOS 13 中。你是否也提取了 destinationView
  • 是的,我复制粘贴了 Xcode 11.5、iOS 13(最新)。 DetailView 仍在移动。
  • pawello2222,只是为了确认一下,您的实现:您点击一个列表项,然后点击“反向”,详细视图保持在顶部,而不移动?然后,当点击返回按钮时,列表项被反转?
  • @lassej 实际上它对我有用,直到我将代码粘贴到新项目中(我猜是有问题)。我更新了我的答案以包含一个解决方法。让我知道这是否是您要找的。​​span>
  • 感谢您对此的帮助。您的解决方法解决了不稳定的详细信息视图的问题,但现在列表视图不显示选择了哪个项目。在 iPhone 上,这可能不是什么大问题,但在 iPad 或 Mac 上,如果视图显示在拆分视图中,我希望用户能够看到当前选择了哪个列表项。
猜你喜欢
  • 2020-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-17
  • 2016-09-13
相关资源
最近更新 更多