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)
}
}
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:
- Use @ObservedObject instead of @State for passing around the CoreData objects
- 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
}
}
}
}
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)
}
}
}
}
}
}
}
Using Picker with Core Data
The type of the selection value is String
while Category.categoryName is String?. Notice the added ?
, this means it is an Optional.
It is the default type for CoreData, but you can remove the Optional value inside the model:
I would ask myself does a Category without a name make sense before doing this change. If it does, you will probably have to use another identifier for the selection.
Related Topics
Remove or Edit User Location Blue Pulsing Circle
Inter-App Data Migration (Migrating Data to New App Version)
Nsobject Subclass in Swift: Hash VS Hashvalue, Isequal VS ==
Convert String to Date in Swift
Swift - Resolving a Math Operation in a String
Input from the Keyboard in Command Line Application
Access Firebase Variable Outside Closure
How to Compare Two Dictionaries in Swift
Cannot Assign Property in Method of Struct
Providing a Default Value For an Optional in Swift
Swift: How to Add a Protocol Extension to a Protocol
Swift @Escaping and Completion Handler
Nsfilemanager Fileexistsatpath:Isdirectory and Swift