9

I have a fairly common set up:

  • A list of items created using the ForEach component
  • When an item is tapped my app navigates to a detailed view
  • Inside of the detailed view, the user can perform updates that change the underlying item

The problem is that updating the underlying item (which is a struct) causes SwiftUI to automatically navigate backward. I assume this is because the struct is an immutable value and gets destroyed during the update, however, it does conform to Identifiable so I'm expecting SwiftUI to understand that the item still exists and just needs to be updated rather than destroyed.

Is there any way to update the underlying list without navigating away from the detail view?

Here's a minimal, reproducible example.

import SwiftUI

struct ContentView: View {
    var body: some View {
       DemoList(viewModel: ViewModel())
    }
}

struct DemoItem: Codable, Hashable, Identifiable {
    var id: UInt
    var description: String
}

final class ViewModel: ObservableObject, Identifiable {
    @Published var list = [
        DemoItem(id: 1, description: "One"),
        DemoItem(id: 2, description: "two")
    ]
    
    /// This update causes SwiftUI to automatically navigate away from the detail view
    func update(item: DemoItem) {
        list = list.map { $0.id == item.id ? item : $0 }
    }
}

struct DemoList: View {
    @ObservedObject var viewModel: ViewModel
    
    var body: some View {
        NavigationView {
            ForEach(viewModel.list, id: \.self) { item in
                NavigationLink(destination: DemoDetail(viewModel: self.viewModel, item: item)) {
                    Text(item.description)
                }
            }
        }
    }
}

struct DemoDetail: View {
    @ObservedObject var viewModel: ViewModel
    var item: DemoItem
    
    var body: some View {
        Text(item.description)
            .onTapGesture {
                let newItem = DemoItem(id: self.item.id, description: UUID().uuidString)
                self.viewModel.update(item: newItem)                
            }
    }
}
Robert
  • 981
  • 1
  • 15
  • 24

2 Answers2

5

Just remove the id from the ForEach and it'll stop navigating back

ForEach(viewModel.list) { item in
      NavigationLink(destination: DemoDetail(viewModel: self.viewModel, item: item)) {
      Text(item.description)
    }
}
Ludyem
  • 1,709
  • 1
  • 18
  • 33
  • Hmm, that works! Would you mind explaining why that works? – Robert Jul 03 '20 at 20:20
  • 1
    That's because you are invalidating the NavigationLink, have a look at the answer in this question: https://stackoverflow.com/questions/58302913/swiftui-navigationlink-pops-immediately-if-used-within-foreach – Ludyem Jul 03 '20 at 20:50
1

This should fix your problem:

NavigationView {
    //Your views here
}.navigationViewStyle(.stack) 

This is happening due to a bug in iOS 15.0.1.
You can find the details from this forum thread