iosswiftswiftdataios18

Dynamic Query SwiftData won't work anymore in ios 18


I used to filter using initializers like this(example):

struct ProductListView: View {
    
    @Environment(\.modelContext) private var modelContext    
    @Query private var products: [Product]
    @State var clickOnProduct = false
    @State var productSku: String = ""

    var body: some View {
        NavigationStack{
           List(){
             ForEach(products){ product in
               VStack(){
                 //<---
               }.onTapGesture{
                  productSku = product.sku
                  clickOnProduct.toggle()
                }
             }
           }.navigationDestination(isPresented: $clickOnProduct){
             ProductDescriptionView(productSku: productSku)
            }
        }
    }

    
}


struct ProductDescriptionView: View {
    @Environment(\.modelContext) private var modelContext 
    @Query var products: [Product]
    var productSku = ""
    
    
    init(productSku: String) {
        self.productSku = productSku
        let predicate = #Predicate<Product>{$0.sku == productSku}
        self._products = Query(filter: predicate)
    }
    
    var body: some View {
       
    }

}

But now when I use this method my iPhone get hot and the app freezes, even Simulator shows overload of CPU (https://i.sstatic.net/McqcZapB.png)

That method worked fine in ios 17 but now It doesn't work anymore. Do you have any solution?

Edit. I have change the example code to be more specific. Using NavigationStack and sending a value through navigationDestination the app freezes and doesn't go to ProductDescriptionView.


Solution

  • Based on this Answer

    Try this approach using an intermediate property to avoid referencing the article model in the predicate.

    struct ArticleView: View {
        @Environment(\.modelContext) private var modelContext
        @Query private var articleStates: [ArticleState]
        
        let article: Article
        
        init(article: Article) {
            self.article = article
            let articleID = article.id  // <--- here
            _articleStates = Query(filter: #Predicate { $0.articleID == articleID } )
        }
        
        var body: some View {
            // ... // SwiftUI view that depends on query data.
        }
    }
    

    EDIT-1:

    Try a different approach than to perform the Query inside the init. Using a simple filter, and if required a computed property, as shown in the example code.

    struct ProductDescriptionView: View {
        @Environment(\.modelContext) private var modelContext
        @Query private var allProducts: [Product]
        
        let productSku: String
        
        var products: [Product] {
            allProducts.filter{$0.sku == productSku}
        }
        
        var body: some View {
            Text(productSku)
            List {
                ForEach(products){ product in
                    Text(product.sku)
                }
            }
        }
    }
    

    Or this alternative using modelContext.fetch... in .onAppear

    struct ProductDescriptionView2: View {
        @Environment(\.modelContext) private var modelContext
    
        let productSku: String
        @State private var products: [Product] = []
    
        var body: some View {
            Text(productSku)
            List {
                ForEach(products){ product in
                    Text(product.sku)
                }
            }
            .onAppear {
                let fetchDescriptor = FetchDescriptor<Product>(predicate: #Predicate { product in
                    product.sku == productSku
                })
                do {
                    products = try modelContext.fetch(fetchDescriptor)
                } catch {
                    print("Failed to load data")
                }
            }
        }
    }