3

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.

1 Answer 1

1

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)
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks so much for the explanation! I went with making the @Environment var private and it worked like you said! Super easy. I sort of understand the first method, but not entirely. Do you think one is better than the other?
@RyanClare IMO I prefer the first code snippet, in terms of style.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.