JohnSF
JohnSF

Reputation: 4300

Can't Load SwiftUI Picker Choice from Core Data and Display as Selection

I have a SwiftUI app that persists data in CoreData. I want to have a Picker where the user can choose an item (from one entity) and then store that information in another entity to be displayed as the Picker selection.

I have an Entity named Patient where data is added by the user. I have an Entity named Clinic that is a list of objects created by the user. The purpose is to use the picker to rapidly add information without repeatedly typing the items for each Patient record.

In Swift, I did this by attaching a UIPicker to a TextField. That worked fine. I have not been able to do the equivalent in SwiftUI. I can make the creation and population of the picker data and I successfully save changes to the Patient entity, but I have not been able to load the Patient Clinic data into the picker as the default when the Detail View is loaded.

This piece of the app is equivalent to the master/detail style. The DetailView is:

struct DetailView: View {

    @Environment(\.managedObjectContext) var managedObjectContext
    @Environment(\.presentationMode) var presentationMode
    @FetchRequest(fetchRequest: Clinic.getAllClinics()) var clinics: FetchedResults<Clinic>
    @State private var selectClinicItem: Int = 0
    var patient: Patient

    @State private var updatedFirstName: String = "No First Name"
    @State private var updatedLastName: String = "No Last Name"
    @State private var updatedClinic: String = "Clinic Not Chosen"
    @State private var chosenClinicUUID: UUID = UUID()

    var body: some View {

        ScrollView {
            Group {//top group
                VStack {
                    MyDataCell(tfData: $updatedFirstName, passedInLabel: "First Name", patient: patient, patientAttribute: patient.firstName ?? "No First")
                }//Top VStack - 8 views
            }//top Group

            Group {//second group
                Form {
                    Picker(selection: self.$chosenClinicUUID, label: Text("Choose Clinic")) {
                        ForEach(clinics) { c in
                            Text("\(c.name ?? "no name")")
                        }
                    }
                    .frame(width:320, height: 30)
                }//Form
                .frame(width:350, height: 200)
                .clipShape(RoundedRectangle(cornerRadius: 12))
                .overlay(RoundedRectangle(cornerRadius: 12)
                .stroke(Color.gray, lineWidth: 2))
                .padding(.leading, 12)
                .padding(.top, 20)
                .padding(.bottom, 20)
            }//second Group

        }//scroll or top level Form

            .modifier(AdaptsToSoftwareKeyboard())
            .navigationBarTitle("View and Edit", displayMode: .inline)
            .navigationBarItems(trailing: Button(action: {

                self.patient.firstName = self.updatedFirstName
                self.patient.lastName = self.updatedLastName
                self.patient.clinicName = self.updatedClinic
                self.patient.clinicID = self.chosenClinicUUID

                let n = self.clinics.filter { $0.id == self.patient.clinicID }

                if n.count > 0 {
                    self.patient.clinicName = n[0].name
                }

                do {
                    try self.managedObjectContext.save()
                } catch {
                    print(error)
                }

                self.updatedFirstName = ""
                self.updatedLastName = ""
                self.updatedClinic = ""

                self.presentationMode.wrappedValue.dismiss()

            }) {
                Text("Save")
            })

    }//body
}

And for reference, the Patient class:

public class Patient : NSManagedObject, Identifiable {
    @NSManaged public var id: UUID
    @NSManaged public var firstName: String?
    @NSManaged public var lastName: String?
    @NSManaged public var clinicName: String?
    @NSManaged public var clinicID: UUID
}

extension Patient {
    static func getAllPatients() -> NSFetchRequest<Patient> {
        let request: NSFetchRequest<Patient> = Patient.fetchRequest() as! NSFetchRequest<Patient>
        let sortDescriptor = NSSortDescriptor(key: "lastName", ascending: true)
        request.sortDescriptors = [sortDescriptor]
        return request
    }
}

The Clinic class:

public class Clinic : NSManagedObject, Identifiable {
    @NSManaged public var id: UUID
    @NSManaged public var name: String?
    @NSManaged public var comment: String?
    @NSManaged public var isShown: Bool
}

extension Clinic {
    static func getAllClinics() -> NSFetchRequest<Clinic> {
        let request: NSFetchRequest<Clinic> = Clinic.fetchRequest() as! NSFetchRequest<Clinic>
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        request.sortDescriptors = [sortDescriptor]
        return request
    }
}

enter image description here enter image description here

Any guidance would be appreciated. Xcode Version 11.2.1 (11B500)

Upvotes: 2

Views: 1765

Answers (3)

Peter Suwara
Peter Suwara

Reputation: 816

Solved the problem by manually setting the selected index during Init. Seems like a strange way to do it.

struct MyView: View {
    ...
    @ObservedObject var model = VideoDetailsModel()
    @State var selectedCategoryIndex : Int = -1

    // Init with initial model
    init(onDismiss: @escaping (VideoDetails?) -> ()) {
        self.onDismiss = onDismiss
    }

    // Init with existing model
    init(model: VideoDetailsViewModel, onDismiss: @escaping (VideoDetails?) -> ()) {
        self.model = model
    
        self.onDismiss = onDismiss
        let selectedIndex = self.model.categories.categories.firstIndex(where: {
            return $0 == self.model.videoModel?.categories[0]
        }) ?? -1
        
        // Force the set state here since initialisation is optional
        _selectedCategoryIndex = .init(initialValue: selectedIndex)
    }
    
    var body: some View {
    ... 
            Picker(selection: $selectedCategoryIndex , label: Text("Category")) {
                ForEach(0..<self.model.categories.categories.count, id: \.self) { index in
                    Text(self.model.categories.categories[index])
                }
            }
    ... 
}

Upvotes: 0

WelcomeNewUsers
WelcomeNewUsers

Reputation: 257

This is what I did, a dummy instance of Icon

  @FetchRequest(
    entity: Icon.entity(),
    sortDescriptors: []
  ) var coreDataIcon: FetchedResults<Icon>

  @State private var oneCoreDataIconIndex: Int = 0

  var body: some View {

    Form {

        Picker("Select Icon", selection: $oneCoreDataIconIndex) {
          ForEach (0..<coreDataIcon.count) {
            Image(systemName: self.coreDataIcon[$0].wrappedIcon)
              .tag($0)
              .font(.title)
          }//ForEach

        }//Picker

      }//Form

... later in my code, I was able to add the relationship

oneAccount.icon = self.coreDataIcon[self.oneCoreDataIconIndex]

in your case, you can do

    @State private var chosenClinicIndex: Int = 0

...and

       Picker(selection: $chosenClinicIndex, label: Text("Choose Clinic")) {
          ForEach(0..<clinics.count) {
            Text("\(self.clinics[$0].name ?? "no name")").tag($0)
          }
        }

later in your code

self.patient.clinicID = self.clinics[self.chosenClinicIndex].id

this will also show an initial clinic before you pick

like mine enter image description here

Upvotes: 1

simibac
simibac

Reputation: 8570

Try using a Int (in combination with an array) and not the actual UUID as the selection variable. Check out this.

Upvotes: 0

Related Questions