swiftuiswiftdata

SwiftUi error when trying to navigate to a view with a SwiftData model context


I'm doing the SwiftData tutorial on Hacking With Swift, and I'm trying one of the challenges which is to add a swipe to delete on a list of related models. I thought I had the right idea, but I'm getting an error that I don't understand. Here is my code:

ContentView

import SwiftUI
import SwiftData

struct ContentView: View {
    
    @Environment(\.modelContext) var modelContext
    
    @State private var path = [Destination]()
    @State private var sortOrder = SortDescriptor(\Destination.name)
    @State private var searchText = ""
    
    func addDestination() {
        let destination = Destination()
        modelContext.insert(destination)
        path = [destination]
    }
    
    var body: some View {
        NavigationStack(path: $path) {
            DestinationListingView(sort: sortOrder, searchString: searchText)
                .navigationTitle("iTour")
                .searchable(text: $searchText)
            .navigationDestination(for: Destination.self, destination: EditDestinationView.init)
            .toolbar {
                Menu("Sort", systemImage: "arrow.up.arrow.down") {
                    Picker("Sort", selection: $sortOrder) {
                        Text("Name")
                            .tag(SortDescriptor(\Destination.name))

                        Text("Priority")
                            .tag(SortDescriptor(\Destination.priority, order: .reverse))

                        Text("Date")
                            .tag(SortDescriptor(\Destination.date))
                    }
                    .pickerStyle(.inline)
                }
                Button("Add Destination", systemImage: "plus", action: addDestination)
            }
        }
    }
}

#Preview {
    ContentView().modelContainer(for: Destination.self)
}

EditDestinationView

import SwiftUI
import SwiftData

struct EditDestinationView: View {
    
    @Environment(\.modelContext) var modelContext
    
    @Bindable var destination: Destination
    
    @State private var newSightName = ""
    
    func addSight() {
        guard newSightName.isEmpty == false else { return }

        withAnimation {
            let sight = Sight(name: newSightName)
            destination.sights.append(sight)
            newSightName = ""
        }
    }
    
    func deleteSight(_ indexSet: IndexSet) {
        for index in indexSet {
            let sight = destination.sights[index]
            modelContext.delete(sight)
        }
    }
    
    var body: some View {
        Form {
            TextField("Name", text: $destination.name)
            TextField("Details", text: $destination.details, axis: .vertical)
            DatePicker("Date", selection: $destination.date)
            
            Section("Priority") {
                Picker("Priority", selection: $destination.priority) {
                    Text("Meh").tag(1)
                    Text("Maybe").tag(2)
                    Text("Must").tag(3)
                }
                .pickerStyle(.segmented)
            }
            
            Section("Sights") {
                ForEach(destination.sights) { sight in
                    Text(sight.name)
                }.onDelete(perform: deleteSight)

                HStack {
                    TextField("Add a new sight in \(destination.name)", text: $newSightName)

                    Button("Add", action: addSight)
                }
            }
        }
        .navigationTitle("Edit Destination")
        .navigationBarTitleDisplayMode(.inline)
    }
}

#Preview {
    do {
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try ModelContainer(for: Destination.self, configurations: config)

        let example = Destination(name: "Example Destination", details: "Example details go here and will automatically expand vertically as they are edited.")
        return EditDestinationView(destination: example)
            .modelContainer(container)
    } catch {
        fatalError("Failed to create model container.")
    }
}

On line 30 of ContentView i'm getting an error: Cannot convert value of type '(Environment<ModelContext>, Destination) -> EditDestinationView' to expected argument type '(Destination) -> EditDestinationView'

I'm very new to Swift so I don't understand this error or know what to do to fix it. Can anyone help me? Thanks!

I've tried googling the error, but I don't understand enough about Swift yet to make sense of it.


Solution

  • You should change

    .navigationDestination(for: Destination.self, destination: EditDestinationView.init)
    

    to

    .navigationDestination(for: Destination.self) {
        EditDestinationView(destination: $0)
    }
    

    The type of EditDestinationView.init is, as the error message says, (Environment<ModelContext>, Destination) -> EditDestinationView. This is the automatically-generated initialiser that takes two arguments.

    // this is what the automatically-generated initialiser looks like
    init(
        modelContext: Environment<ModelContext> = Environment(\.modelContext), 
        destination: Destination
    ) { 
        self._modelContext = modelContext
        self._destination = Bindable(destination)
    }
    

    The first argument Environment<ModelContext> is optional and you normally do not pass it explicitly, but this does not change the fact that EditDestination.init is still a function that takes 2 parameters - it cannot be passed to navigationDestination which expects a function that only takes one parameter.

    If you make modelContext private, then the automatically-generated initialiser will not include it as a parameter (similar to how newSightName is not included in the automatically-generated initialiser):

    @Environment(\.modelContext) private var modelContext
    

    Now this compiles:

    .navigationDestination(for: Destination.self, destination: EditDestinationView.init)