NavigationLink pops out upon List update ONLY when List is not scrolled to the top

Solution 1:

You're correct that this relates to the fact that the List lazily loads elements -- once the NavigationLink is off the screen, if the Chat element changes, the View ends up getting popped off the stack.

The standard solution to this is to add a hidden NavigationLink to your hierarchy that has an isActive property that controls whether or not it is active or not. Unfortunately, it requires a little more boilerplate code than the convenient list element binding that was introduced in Swift 5.5.

Your code might look something like this:

struct DebugView: View {
    
    @StateObject var chatsManager = ChatsManager()
    @State private var activeChat : String?
    
    private func activeChatBinding(id: String?) -> Binding<Bool> {
        .init {
            activeChat != nil && activeChat == id
        } set: { newValue in
            activeChat = newValue ? id : nil
        }
    }
    
    private func bindingForChat(id: String) -> Binding<Chat> {
        .init {
            chatsManager.chats.first { $0.id == id }!
        } set: { newValue in
            chatsManager.chats = chatsManager.chats.map { $0.id == id ? newValue : $0 }
        }
    }
    
    var body: some View {
        NavigationView{
            VStack{
                HStack {
                    Text("Chats")
                }.padding()
                VStack{
                    List() {
                        ForEach($chatsManager.chats, id: \.id) { $chat in
                            Button(action: {
                                activeChat = chat.id
                            }) {
                                DemoChatRow(chat: $chat)
                            }
                        }
                    }.listStyle(.plain)
                }
                .background {
                    NavigationLink("", isActive: activeChatBinding(id: activeChat)) {
                        if let activeChat = activeChat {
                            ChatDetailView(chat: bindingForChat(id: activeChat).wrappedValue)
                        } else {
                            EmptyView()
                        }
                    }
                }
            }.navigationBarTitle("").navigationBarHidden(true)
            
        }.navigationViewStyle(.stack)
            .environmentObject(chatsManager)
    }
}

Note: I kept the Binding that you have to DemoChatRow even though it looks like it's just a one-way connection here in the demo code, making the assumption that in your real code, you need two-way communication there