How to Use Core Data Value from Picker? #Swiftui #Coredata

SwiftUI Picker from CoreData Values

Change your select property to be a User and make it optional (since nothing is selected when the view is opened)

@State var selectedUser: User?

Then you need to use .tag in your picker so the selected user can be correctly assigned to selectedUser

Picker("Select user", selection: $selectedUser) {
ForEach(users) { user in
Text(user.name!)
.tag(Optional(user))
}
}
.pickerStyle(.menu)

Note that the user variable needs to be made optional in the tag so its type matches the type of selectedUser

How to use a picker on CoreData relationships in SwiftUI

OK. Huge vote of thanks to Lorem for getting me to the answer. Thanks too for Roma, but it does turn out that his solution, whilst it worked to resolve one of my key problems, does introduce inefficiencies - and didn't resolve the second one.

If others are hitting the same issue I'll leave the Github repo up, but the crux of it all was that @State shouldn't be used when you're sharing CoreData objects around. @ObservedObject is the way to go here.

So the resolution to the problems I encountered were:

  1. Use @ObservedObject instead of @State for passing around the CoreData objects
  2. Make sure that the picker has a tag defined. The documentation I head read implied that this gets generated automatically if you use ".self" as the id for the objects in ForEach, but it seems this is not always reliable. so adding ".tag(element as Element?)" to my picker helped here.
    Note: It needed to be an optional type because CoreData makes all the attribute types optional.

Those two alone fixed the problems.

The revised "LicenceView" struct is here, but the whole solution is in the repo.

Cheers!

struct LicenceView : View {
@Environment(\.managedObjectContext) private var viewContext
@ObservedObject var licence: Licence
@Binding var showModal: Bool

@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Element.desc, ascending: true)],
animation: .default)
private var elements: FetchedResults<Element>

func save() {
try! viewContext.save()
showModal = false
}

var body: some View {
VStack {
Button(action: {showModal = false}) {
Text("Close")
}
Picker(selection: $licence.licenced, label: Text("Element")) {
ForEach(elements, id: \.self) { element in
Text("\(element.desc!)")
.tag(element as Element?)
}
}
Text("Selected: \(licence.licenced!.desc!)")
Button(action: {save()}) {
Text("Save")
}
}

}
}

How can I use Core Data value from picker? #SwiftUI #CoreData

I misunderstood your issue with my original comment see below. There is a lot of info in the comments.

import SwiftUI
struct ParentPickerView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \PageName.pageName, ascending: true)],
animation: .default)
private var items0: FetchedResults<PageName>
//State is a source of truth
@State var selectedPage: PageName? = nil
var body: some View {
NavigationView {
Form {
Text(selectedPage?.pageName ?? "not selected")
PickerView(items0: Array(items0), selectedPage: $selectedPage)
}
.foregroundColor(.blue)
.background(Color.yellow)
}
}
}
struct PickerView: View {
//If you don't need all your PageName in your parentView it would be best to have the fetch here
// @Environment(\.managedObjectContext) private var viewContext
// @FetchRequest(
// sortDescriptors: [NSSortDescriptor(keyPath: \PageName.pageName, ascending: true)],
// animation: .default)
// private var items0: FetchedResults<PageName>
var items0: [PageName]
//Binding is a two-way connection
@Binding var selectedPage: PageName?
//You don't need custom init in SwiftUI because they are struct
// init(items0: [PageName], selectedPage: Binding<PageName?>) {
// self.items0 = items0
// self._selectedPage = selectedPage
//
// //@State Init here is bad practice as you are experiencing it becomes a dead end
// //self._selectedPage = State(initialValue: items0.first)
// }
var body: some View {
Picker(selection: $selectedPage, label: Text("Page")) {
ForEach(items0) { item in
Text(item.pageName ?? "").tag(item as PageName?)
}
}
Text("\((selectedPage?.pageName ?? "not selected"))")

.onAppear(){
//Set your initial item here only if the selected item is nil
if selectedPage == nil{
selectedPage = items0.first
}
}
}
}

Choosing CoreData Entities from form picker

As written you are not matching the types of the array the picker and the FetchResult. See the comments

import SwiftUI

@available(iOS 15.0, *)
struct LearnView: View {
//This is optional Language
@State private var selectedLanguage: Language?
//I commented out variables that are not part of the reproducible body provided
//@State private var selectedCategory: SubCategory?
//@State private var selectedDate = Date()

@Environment(\.managedObjectContext) private var viewContext
//This is FetchedResults<Language>
@FetchRequest(entity: Language.entity(), sortDescriptors: []) var languages: FetchedResults<Language>
// @FetchRequest(entity: SubCategory.entity(), sortDescriptors: []) var subCategories: FetchedResults<SubCategory>

var body: some View {
NavigationView {
ZStack {
Form {
Section("Learning Schedule") {
Picker("Please choose a language", selection: $selectedLanguage) {
//Map is needed for a plain array of languages
//ForEach(languages.map{$0}, id: \.self) { language in
//This version works too
//The point is to go from FetchedResults<Language> to [Language]
ForEach(Array(languages), id: \.self) { language in

Text(language.name ?? "Unknown")
//The casting is necessary to make it an optional since your @State is an optional
.tag(language as? Language)
}
}
Text("You selected: \(selectedLanguage?.name ?? "Unknown")")
}
}
}
}
}
}

@available(iOS 15.0, *)
struct LearnView_Previews: PreviewProvider {
static var previews: some View {
LearnView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

Core Data results into a SwiftUI Picker view without preselecting an option

You could set the selectedAction to a non-existent Actions in init(...) like this:

self._selectedAction = State(initialValue: Actions(context: context))

that will not set a pre-selected object in the picker.

struct RecordCreateview: View {
@FetchRequest private var actions: FetchedResults<Actions>
@State private var selectedAction: Actions

init(context: NSManagedObjectContext) {
let fetchRequest: NSFetchRequest<Actions> = Actions.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Actions.action, ascending: true)]
fetchRequest.predicate = NSPredicate(value: true)
self._actions = FetchRequest(fetchRequest: fetchRequest)
self._selectedAction = State(initialValue: Actions(context: context)) // <--- here
}

var body: some View {
NavigationView {
Form {
Picker("Select action", selection: $selectedAction){
ForEach(actions) { action in
if action.title == true {
Text("\(action.action!)").tag(action)
}
}
}
}
}
}
}

SwiftUI Core Data Object Return from Picker

A helpful commenter on the Apple forums showed me the answer - which was to do with making sure the selection variable was not optional:

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)")
}
}
}

SwiftUI - Using CoreData Fetched Result to Populate Picker

You need to make your value an optional String because that is what the type primaryKeyId is

@Binding var value: String?

And then you need a tag on each element of the picker to set the selection:

Text(data.value ?? "Unknown").tag(data.primaryKeyId)


Related Topics



Leave a reply



Submit