Reputation: 6494
This is the test data model:
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()
}
}
This is my content view, with a NavigationLink
and a detail view with a button that reverses the item order:
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()
}
}
}
}
}
}
Now if I tap on Reverse
the NavigationView
or List
seems to lose its selection, pops the view, and pushes it again:
Is this expected behaviour or a bug in SwiftUI? Is there a workaround? I would expect that the detail view simply stays as it is, without reloading.
Upvotes: 5
Views: 1380
Reputation: 54426
You need to specify an explicit id
for your ForEach
loop.
If you use a static ForEach
(without the id
parameter) your view is rebuilt because the data (storage.items
) is changed.
Try the following:
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")
}
}
}
This method, however, only works if the original position of selected item is maintained.
In this example performing the update()
from the detail screen for item 1 will not pop the NavigationLink.
class Storage: ObservableObject {
@Published var items = [Item(n: 1), Item(n: 2)]
func update() {
items = [Item(n: 1), Item(n: 3)]
}
}
Here is a workaround to make it work (use an empty 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")
}
}
}
Upvotes: 4