Reputation: 4300
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
}
}
Any guidance would be appreciated. Xcode Version 11.2.1 (11B500)
Upvotes: 2
Views: 1765
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
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
Upvotes: 1