BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

How to use fetched (Core)Data in a Picker?

Forums > SwiftUI

Hi there, newbie here :-) first: Thank you Paul Hudson for all your content and thank you to all the other contributing members of this forum!

After viewing a lot of Swift-tutorials I still feel like I don't know anything about coding. Again I am stuck at a point I think I should be able to solve it by myself, but...

I want a Picker to show my saved Entity's names. It workes in a List, but I can't get the Picker to work. All examples I can find are using Arrays, not fetched Data, so I think I should maybe create an Array.

Can somebody please help me out?

Does the Binding var need to be inside the Struct/View?

Thanks a lot, Freddy

struct loadNewWorkers: View {
          @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Worker.name, ascending: true)],
          predicate: NSPredicate(format: "pinCodeSet == false"))

          var newWorkers: FetchedResults<Worker>

        @Binding var newUserIndex: Int

              var body: some View {

 //              delete List afterwards
                VStack {
                List(newWorkers, id: \.self) { newWorker in
                        Text(newWorker.name!)
                    }

//              use  Picker
                Picker(selection: $newUserIndex, label: Text("")) {
                    ForEach(0..<newWorkers.count) {
                        Text(newWorker.name[$0])
                                  }

                    }
                }
              }
}

3      

hi,

does the loadNewWorkers View appear inside a NavigationView? that needs to be the case for the Picker to work correctly.

hope that helps,

DMG

3      

hi, thank you for your suggestion. Indeed I had commented-out the NavigationView, but unfotunately this doesn't change anything.

greetings

3      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Try

Picker("", selection: $newUserIndex) {
  ForEach(newWorkers, id: \.self) { newWorker in
    Text(newWorker.name)
  }
}

This code newWorker.name[$0] means your entity has a name property which is an array. What you could try is replace it $0.name. You go through an array of NewWorker objects and want access a property of one NewWorker object. $0 is a shortcut for what I did with newWorker in. What I did is I just gave $0 the name of newWorker.

3      

Mhm... still throws error "Value of type 'NSManagedObject' has no member 'name'".

The error disappears, when I force it Text(newWorker.name!) or even Text(newWorker.name ?? "error")

But there is an remaining error "Missing argument for parameter 'newUserIndex' in call" at the line I put the struct in the body-View

var body: some View {

        NavigationView {
        VStack {
            loadNewWorkers()

3      

Picker("", selection: $newUserIndex) {
  ForEach(newWorkers, id: \.self) { (newWorker: Worker) in
    Text(newWorker.name)
  }
}

But I don't see which value you want to assign to your newUserIndex.

3      

I see your point... this remains from the example, where the source is an Array of Strings. The Binding var newUserIndex defines the Position in the array. When data is fetched, how do you define wich Worker.name gets in wich place in the Picker?

3      

You can use the tag modifier

Picker("", selection: $newUserIndex) {
  ForEach(newWorkers, id: \.self) { (newWorker: Worker) in
    Text(newWorker.name).tag(newWorker.id)
  }
}

But you will need a Int which identifies your worker uniquely. Do you have a id attribute in your Worker class? This id would be assigned to newUserIndex. To be clear: the code above won't work for you as long as you don't have an Int attribute in your Worker class.

https://www.ioscreator.com/tutorials/swiftui-picker-tutorial

4      

Hooray, it finally works! the Picker contains the workers names now.

In fact, I don't even have implemented a class yet, only my Worker-Entity in .xcdatamodeld (I think I have to add creating of classes to my ToDo). I had an id-attribute, specified as UUID. I added an Int-attribute to test, but it seems to work with my UUID-attribute as well.

Also I changed the name of newUserIndex-State var to the more logic var selectedName.

That is what its looks like now:

struct loadNewWorkers: View {
          @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Worker.name, ascending: true)],
          predicate: NSPredicate(format: "pinCodeSet == false"))

          var newWorkers: FetchedResults<Worker>

    @State private var selectedName = "dafd"

              var body: some View {
                VStack {
                    Picker("", selection: $selectedName) {
                ForEach(newWorkers, id: \.self) { (newWorker: Worker) in
                    Text(newWorker.name!).tag(newWorker.id)
              }
}
//                 SelectionResult
                    Text("\(selectedName)")
              }
}
}

Now I've reached the next problem. I need to figure out how to read out the pickers selection. In an example they are using the State var in a Text to display the selected value, but it only displays the placeholder-String "dafd" In fact, xcode didn't complain about changing selectedName from Int to String, so does it even do anything?

3      

Try newWorker.id.uuidString. UUID isn't a String, it's a UUID.

3      

newWorker doesn't work: "use of unresolved identifier 'newWorker'" I think its because the constant newWorker is generated inside the closure and not externaly usable.

tried it with newWorkers (plural), but "Value of type 'FetchedResults<Worker>' has no member 'id'"

3      

I ment replace newWorker.id with newWorker.id.uuidString in your closure.

3      

I suggest watching this great video from Stanford CS193P, Spring 2020, lecture 11. This goes in depth into Pickers, and it's around 31:00 where the pickers come into play. Then watch lecture 12 where the instructor changes everything to use Core Data. Helped me tremendously. Most importantly, what ever type you use as a $selection, is what should be the result of the picker.

Lecture 11: https://www.youtube.com/watch?v=fCfC6m7XUew&feature=youtu.be Lecture 12: https://www.youtube.com/watch?v=yOhyOpXvaec&feature=youtu.be

CS193p course lectures and code can be found at the link below and they are a great supplement to HWS site. https://cs193p.sites.stanford.edu

7      

I struggled with getting Core Data directly bound to a picker as well. The code below is a standard Xcode Core Data template project modified to work with a picker. It also automatically changes the picker to a new item when a new item is created. I like this solution because the type of the selection is the same as the type of the things being selected.

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest private var items: FetchedResults<Item>
    @State private var selection: Item

    init(moc: NSManagedObjectContext) {
        let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
        fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.timestamp, ascending: false)]
        fetchRequest.predicate = NSPredicate(value: true)
        self._items = FetchRequest(fetchRequest: fetchRequest)
        do {
            let tempItems = try moc.fetch(fetchRequest)
            if(tempItems.count > 0) {
                self._selection = State(initialValue: tempItems.first!)
            } else {
                self._selection = State(initialValue: Item(context: moc))
                moc.delete(selection)
            }
        } catch {
            fatalError("Init Problem")
        }
    }

    var body: some View {
        VStack {
            if (items.count > 0) {
                Picker("Items", selection: $selection) {
                    ForEach(items) { (item: Item) in
                        Text(item.timestamp!, formatter: itemFormatter).tag(item)
                    }
                }.padding()
            }
            List {
                ForEach(items) { item in
                    Text("Item at \(item.timestamp!, formatter: itemFormatter)")
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                Button(action: addItem) {
                    Label("Add Item", systemImage: "plus")
                }
            }
            if (items.count > 0) {
                Text("Item \(selection.timestamp ?? Date(timeIntervalSince1970: 0), formatter: itemFormatter) is currently selected.").padding()
            }
        }
    }

    private func addItem() {
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
            do {
                try viewContext.save()
                selection = newItem //This automatically changes your selection when you add a new item.
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

4      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.