2

I am using SwiftData & SwiftUI. This is the rough setup of my code:

struct ListItemView: View {
    @Environment(\.modelContext) var modelContext
    @Query
    private var items: [Items]

    @State private var itemToEdit: Item?
    private var isEditing: Binding<Bool> {
        Binding { itemToEdit != nil }
        set: { _ in itemToEdit = nil }
    }

    var body: some View {
       NavigationStack{
           List(items) { item in
                Text(item.name)
                    .onTapGesture {
                        itemToEdit = item
                    }
           }
       }.navigationDestination(isPresented: isEditing) {
            if let item = itemToEdit {
                EditView(item: item)
            }
       }
    }
}
struct EditView: View {
    var item: Item
    ...
}

Now, this actually works fine. If I click on the text for an item, I go into the edit view and everything works well. However, if I change the @Query to add a filter like so:

@Query(filter: #Predicate<Item>{ $0.name != ""})
    private var items: [Item]

Then it no longer works. The list of items will be displayed, but if I click on any of them nothing happens and it seems like the app hangs. I also added some print statement and it seems like it is constantly creating the body of ListItemView for some reason.

On clicking on an item, I would expect that it navigates to EditView. For some reason, this is not the case but only when I add a filter predicate.

2 Answers 2

1

The solution to the problem seems to be OS dependent but the issue is with navigationDestination in combination with the Binding property and also the @Query.

If we change the boolean property to be a @State property we can avoid the infinite loop of updates that otherwise occurred.

struct ListItemView: View {
    @Environment(\.modelContext) var modelContext
    @Query(filter: #Predicate<Item>{ $0.name != ""})
    private var items: [Item]

    @State private var itemToEdit: Item?
    @State var isEditing = false // Changed declaration

    var body: some View {
       NavigationStack{
           List(items) { item in
                Text(item.name)
                    .onTapGesture {
                        itemToEdit = item
                       isEditing = true // Update the property here
                    }
           }
       }
       .navigationDestination(isPresented: $isEditing) {
            if let item = itemToEdit {
                EditView(item: item)
            }
       }
    }
}

If only iOS is supported we can use .navigationDestination(item:) and skip the @State property which gives us less code

struct ListItemView: View {
    @Environment(\.modelContext) var modelContext
    @Query(filter: #Predicate<Item>{ $0.name != "" }) private var items: [Item]
    @State private var itemToEdit: Item?

    var body: some View {
       NavigationStack{
           List(items) { item in
                Text(item.name)
                    .onTapGesture {
                        itemToEdit = item
                    }
           }
       }
       .navigationDestination(item: $itemToEdit) {
           EditView(item: $0)
       }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

-1

Since you're seemingly supporting at least iOS 16, I believe in your case, a better approach might be using a different initializer for the navigation destination:

.navigationDestination(for: Item.self, destination: { item in
    EditView(item: item)
})

This associates the navigation with the presented data type. The use of a boolean to determine the state that the NavigationStack should be in might not always be reliable, so having something like the item that is being selected in the List can be a little more easier to manage.

This should easily filter out using a predicate with your Query.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.