SWIFTUI Core Data Object Return from Picker

I've been working this problem for a couple of days and decided to ask for help. The use case is that I have two Core Data Entities (in CloudKit) which have a relationship between them. In the updating of one entity, I am trying to add the related entity by adding the result from a user selection, via a picker.
The issue I am facing is that I can't get the picker to return the record as the selection - it's always null. I have developed the following test code to highlight the specific case. The picker populates and I can make it return anything other than the actual record - but to insert in the relationship, I need the record itself. Here is the picker test code. Selection is ALWAYS empty.

I would appreciate direction.

Craig

Code Block
struct TestView: View {
@Environment(\.managedObjectContext) var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Player.familyName, ascending: true)],
animation: .default)
private var players: FetchedResults<Player>
@State var selection: Player?
var body: some View {
VStack{
Picker("", selection: $selection){
ForEach(players, id: \.self){ (player: Player) in
Text(player.givenName!).tag(player.self)
}
}
Text(((selection?.familyName ?? "default")))
Text("\(players.count)")
}
}
}

Answered by NickPolychronakis in 663331022
Hey @kennedycraig,

I had many problems with pickers myself, so I have good news for you, it's an easy fix.
Wait for it.....

....."selection" shouldn't be optional.

Yep. Thats it.
Initial "selection" value, has to be one of the "player" values.

I modified your code example, to give you a small hint:

Code Block
import SwiftUI
import CoreData
struct ContentView: View {
    @FetchRequest private var players: FetchedResults<Player>
    @State private var selection: Player
    init(moc: NSManagedObjectContext) {
        let fetchRequest: NSFetchRequest<Player> = Player.fetchRequest()
        fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Player.familyName, ascending: true)]
        fetchRequest.predicate = NSPredicate(value: true)
        self._players = FetchRequest(fetchRequest: fetchRequest)
        do {
            let firstPlayer = try moc.fetch(fetchRequest)
            self._selection = State(initialValue: firstPlayer[0])
        } catch {
            fatalError("Uh, fetch problem...")
        }
    }
    var body: some View {
        VStack{
            Picker("", selection: $selection){
                ForEach(players) { (player: Player) in
                    Text(player.givenName ?? "")
                        .tag(player)
                }
            }
            Text(selection.familyName ?? "No family name")
            Text("\(players.count)")
        }
    }
}


Hope I helped,
Stay safe!

Nikos
Accepted Answer
Hey @kennedycraig,

I had many problems with pickers myself, so I have good news for you, it's an easy fix.
Wait for it.....

....."selection" shouldn't be optional.

Yep. Thats it.
Initial "selection" value, has to be one of the "player" values.

I modified your code example, to give you a small hint:

Code Block
import SwiftUI
import CoreData
struct ContentView: View {
    @FetchRequest private var players: FetchedResults<Player>
    @State private var selection: Player
    init(moc: NSManagedObjectContext) {
        let fetchRequest: NSFetchRequest<Player> = Player.fetchRequest()
        fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Player.familyName, ascending: true)]
        fetchRequest.predicate = NSPredicate(value: true)
        self._players = FetchRequest(fetchRequest: fetchRequest)
        do {
            let firstPlayer = try moc.fetch(fetchRequest)
            self._selection = State(initialValue: firstPlayer[0])
        } catch {
            fatalError("Uh, fetch problem...")
        }
    }
    var body: some View {
        VStack{
            Picker("", selection: $selection){
                ForEach(players) { (player: Player) in
                    Text(player.givenName ?? "")
                        .tag(player)
                }
            }
            Text(selection.familyName ?? "No family name")
            Text("\(players.count)")
        }
    }
}


Hope I helped,
Stay safe!

Nikos
Thank you, @NickPolychronakis
Absolutely brilliant. Got my head in the right place and it works perfectly. Sincerely appreciate you taking the time to write it.
Craig
Thank you both @@NickPolychronakis and @kennedycraig as I really struggled to get figure this out on my own.

I just wanted to add for anyone else that is reading that the code above won't actually work if starting with an empty database. If anyone wants or needs to see a complete project to get this working for them I modified the standard Xcode Core Data template app to work with a picker.

https://github.com/dah/CoreDataPickerExample

Preamble

The solutions previously posted don't allow for the picker to initially have no value selected, but sometimes you may want this. Going with the code-example in the Original Poster's question above, let's say the Original Poster wished to initially have no player selected in the Picker, in which case "default" would be shown in the first Text View until the first time the user selects a Player via the Picker View.

Problem

The tag type doesn't match that of selection.

Solution

Cast the player to Player? in the .tag(...) View Method call, so that the tag's type will match that of selection/$selection.wrappedValue.

Here's the Original Poster's code-example with this solution applied:

struct TestView: View {
  @Environment(\.managedObjectContext) var viewContext
  @FetchRequest(
    sortDescriptors: [NSSortDescriptor(keyPath: \Player.familyName, ascending: true)],
    animation: .default)
  private var players: FetchedResults<Player>
   
  @State var selection: Player?
   
  var body: some View {
    VStack{
      Picker("", selection: $selection){
        ForEach(players, id: \.self){ (player: Player) in
          Text(player.givenName!).tag(player as Player?) // Cast `player` to `Player?`, so that the tag's type will match that of `selection`.
        }
      }
      Text(((selection?.familyName ?? "default")))
      Text("\(players.count)")
    }
  }
}
SWIFTUI Core Data Object Return from Picker
 
 
Q