• Tags: swiftui
  • Vladimirs Nordholm

SwiftUI dynamic Core Data @FetchRequest work-around

Introduction

Fetching from Core Data with dynamic variables is not straight forward.

Error: Cannot use instance member ‘variable’ within property initializer; property initializers run before ‘self’ is available

This blog post will provide a work-around to use dynamic data in requests.

Solution

The work-around is made in two steps:

  1. Move our data into its own view
  2. Set request’s underlying value in the init(…) method

Example code

ContentView.swift
-----------------

@State private var fruitColor: String = "red"

var body: some View {
  FilteredFruitList(color: fruitColor)
}


FilteredFruitList.swift
-----------------------

@FetchRequest var fruits: FetchedResults<Fruit>

init(color: String) {
  _fruits = FetchRequest<Fruit>(
    sortDescriptors: [
      NSSortDescriptor(keyPath: \Fruit.name, ascending: true)
    ],
    predicate: NSPredicate(format: "color == %@", color)
  )
}


var body: some View {
  List(fruits) { fruit in
     // …
  }
}

NOTE: As of 2023-08-25, SwiftUI has a bug which prevents reliably modifying entities and have the view update consistently if entites are modified directly in the new view. If you need to modify your entities and have the view update, please see the next section.

Modifying entities, and updating the view

To reliably update entity data and update corresponding views, another workaround is required.

Say you want to toggle some attribute:

HStack {
  Text(fruit.name!)
  Text(fruit.eaten ? "eaten" : "uneaten")
  Button("Toggle") {
    fruit.eaten.toggle()
  }
}

In order to properly reflect the changes made to the entity in the view, we need to wrap each entity in it’s own View with an @ObservedObject property:

struct FruitRow: View {
  @ObservedObject var fruit: Fruit

  var body: some View {
    HStack {
      Text(fruit.name!)
      Text(fruit.eaten ? "eaten" : "uneaten")
      Button("Toggle") {
        fruit.eaten.toggle()
      }
    }
  }
}

… and in our list provide the entity as such:

var body: some View {
  List(fruits) { fruit in
     FruitRow(fruit: fruit)
  }
}

Closing thoughts

This is a verbose and cumbersome workaround for a problem that I wished had a simpler solution.

If you have the simple solution I am looking for, please find a way to message me – I would love to know.

References

[?]
Paul Hudson, Dynamically filtering @FetchRequest with SwiftUI, Hacking with Swift, Nov 29th 2021
[?]
Apple Inc., FetchRequest / wrappedValue, Apple Developer Documentation