Search code examples
swiftswiftuinavigationswiftui-navigationlink

SwiftUI: Navigate to a new list item immediately after adding it


I'm building a SwiftUI app where I have a list of notes. I want to add a new note to the list when a button is clicked and immediately navigate to the detail view of that new note, similar to what happens when clicking a NavigationLink.

Here’s my current code:

import SwiftUI
import SwiftData

struct NoteList: View {
    let folder: Folder
    
    @Query(sort: \Note.lastModified) private var notes: [Note]
    @Environment(\.modelContext) private var context
    
    var body: some View {
        List {
            ForEach(folder.notes) { note in
                NavigationLink() {
                    NoteView(note: note)
                } label: {
                    Text(note.title)
                }
            }
        }
        .navigationTitle(folder.name)
        .toolbar {
            ToolbarItemGroup(placement: .bottomBar) {
                Button("Add Note", systemImage: "square.and.pencil") {
                    let newNote = Note(title: " ", folder: folder)
                    folder.notes.append(newNote)
                    context.insert(newNote)
                }
            }
        }
    }
}

The folder.notes array contains Note objects. When the "Add Note" button is clicked, I can successfully add a new note to the list, but I don’t know how to programmatically navigate to the new note’s detail view (NoteView).

How can I achieve this in SwiftUI? Any guidance would be greatly appreciated!

I also tried wrapping the button with a NavigationLink like this:

NavigationLink(destination: NoteView(note: newNote)) {
    Button("Add Note", systemImage: "square.and.pencil") {
        let newNote = Note(title: " ", folder: folder)
        folder.notes.append(newNote)
        context.insert(newNote)
        self.newNote = newNote
    }
}

However, this approach didn't work as expected because newNote wouldn't be set or updated before the navigation occurred. I'm not entirely sure how to correctly handle this.


Solution

  • Try this approach using NavigationStack(path: $path) as shown in the example code. This will navigate to the new list item immediately after adding it.

    struct ContentView: View {
        @State private var folder = Folder(notes: []) // <--- for my testing
        @State private var path = NavigationPath() // <--- here
        
        var body: some View {
            NavigationStack(path: $path) {
                NoteList(path: $path, folder: folder) 
                    .navigationDestination(for: Note.self) { note in
                        NoteView(note: note)
                    }
            }
        }
    }
    
    struct NoteList: View {
        @Binding var path: NavigationPath // <--- here
        let folder: Folder
        
        @Query(sort: \Note.lastModified) private var notes: [Note]
        @Environment(\.modelContext) private var context
        
        var body: some View {
            List {
                ForEach(folder.notes) { note in
                    NavigationLink() {
                        NoteView(note: note)
                    } label: {
                        Text(note.title)
                    }
                }
            }
            .navigationTitle(folder.name)
            .toolbar {
                ToolbarItemGroup(placement: .bottomBar) {
                    Button("Add Note", systemImage: "square.and.pencil") {
                        let rnd = UUID().uuidString.prefix(6) // <--- for testing
                        let newNote = Note(title: String(rnd), folder: folder)
                        folder.notes.append(newNote)
                        context.insert(newNote)
                        path.append(newNote) // <--- here
                    }
                }
            }
        }
    }
    
    // <--- for testing
    struct NoteView: View {
        let note: Note
        
        var body: some View {
            Text("NoteView")
            Text(note.title)
        }
    }