CoreData With SwiftUI -Part I
Hey Guys! Am back with some interesting stuff that am gonna show you how to use CoreData in SwiftUI. I promise you guys this is the way easiest we can use CoreData flawlessly. And while implementing this, i really enjoyed each and every lines of Code. I wanna share that experience with you all. Let’s see!
Introduction
Now, We gonna create an application similar to checkList for intaking medicines. In this app, we can create medicine checkList and update medicine status like taken or not. Below are outputs from my simlator.
Create a new project with the desired name you want enabled with CoreDate.
Now Default Entity Item got created and ContentView have some more stuffs that usual.
Delete that add New Entity You want. And Clear the content View Completely with HelloWorld Text.
Now We gonna add some entity for our medication checklist app. I’ve just added some attributes to save proper info about medications here for the custom entity we’ve created.
Under file section, you’ll notice Persistence.swift File. Now I’ll share you what it is.
The container property is the heart of the PersistenceController, which performs many different operations for us in the background when we store and call data. Most importantly, the container allows us to access the so-called viewContext, which serves as in an in-memory scratchpad where objects are created, fetched, updated, deleted, and saved back to the persistent store of the device where the app runs on.
The container gets initialized within the PersistenceController’s init function. In this, the container property gets assigned to an NSPersistentContainer instance. We need to use the name of our “.xcdatamodeld” file, which is “Shared” (or *YourAppName* when you created a mere iOS App project), as the “name” argument.
Lets Move to App file where we can see .environment property. Lets see what it is.
Before we talked about viewContext and if we need to share this viewcontext along the app, it feeds the environment’s managedObjectContext key with the viewContext we’ve have.
“.environment” refers to the location of system-wide settings. This includes Calendar, Locale, ColorScheme, and now also viewContext, which is stored in the persistenceController’s container property. For our case, the .managedObjectContext key is the key for each setting.
Our app can now retrieve, update, and store objects using the viewContext as a scratchpad. We will access it later using the managedObjectContext environment key.
You don’t need to worry if you don’t know what this is. You only need to remember that managedObjectContext can be used for fetching and saving medicines. I’ll show you how easy this is in a moment.
UI For Showing Fetched Result: (Retrieving Data)
Let’s create a content View to show fetched result from DB using viewContext of CoreData.
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(entity: Pills.entity(), sortDescriptors: [], predicate: NSPredicate(format: "status != %@", ""))
var medicines: FetchedResults<Pills>
Our ContentView needs to read out the saved orders in order to change this. With the @FetchRequest property, you can easily achieve this functionality. However, our ContentView itself requires access to the viewContext. The @Environment property is used again in this case.
@FetchRequest reads out the persistent storage to fetch stored medicine list. At this point, the @FetchRequest passes the retrieved medicines to the Pills property. As soon as we save a new medicine, the @FetchRequest will add it to the Pills data set. In the same way that State functionality renews a ContentView’s body, this causes the ContentView to refresh.
Now, Create a List to show fetched results.
var body: some View {
NavigationView {
List {
ForEach(medicines){ item in
//....Row View goes here
}
}
}
}
I just here shows medicine name and count along with the staus of pill taken or not.
HStack{
VStack(alignment: .leading, spacing: 10.0) {
Text(item.medicineName ?? "")
.font(.callout)
.fontWeight(.semibold)
Text("Count: \(item.countOfPill )")
}
Spacer()
Text(item.status ?? "")
.foregroundColor(.blue)
}
The row View looks like this.
Now we will see how to save data to DB using coreData. First we need to create Add UI. For that am using a textField, Stepper for pills count, And a picker for when it needs to be taken and an add button.
Create Data
For that i’ve added plus icon navigation bar button at initial view(Content View) like below.
Am gonna present a view to add new medicine. For that, we need a boolean to handle sheet present as we all know that.
@State var showAddMedicineSheet = false
For navigation bar button, am adding like this.
.navigationBarItems(trailing: Button(action: {
showAddMedicineSheet = true
}, label: {
Image(systemName: "plus.circle")
.imageScale(.large)
}))
For presenting Sheet,
.sheet(isPresented: $showAddMedicineSheet){
AddMedicineView()
}
Here Add medicine view is the screen where we can add new drug to checklist and will see here later.
For Adding a new medicine, we need the drug name, count of pill onna take on daily basis and time also status of pill whether taken or not. As it is a new medicine, i’ve set status to “not taken” as default. Again we need viewContext from environment and presentation mode to dismiss after adding medicine.
@Environment (\.presentationMode) var presentationMode
@Environment(\.managedObjectContext) private var viewContext
@State var medicineName = ""
@State var pillsCount = 0
@State var medicineWhen:[String] = ["After Food", "Before Food", "Evening"]
@State var selectedmedicineWhen = 0
Now add textField for medicine name and stepper and picker for when we need to take medicine.
var body: some View {
NavigationView {
Form {
Section(header: Text("Medicine Name")) {
TextField("Enter Medicine Name", text: $medicineName)
Stepper("\(pillsCount) per day", value: $pillsCount, in: 0...10)
}
Section(header: Text("When you need to take this pill")) {
Picker(selection: $selectedmedicineWhen, label: Text("When")) {
ForEach(0..<medicineWhen.count){
Text(self.medicineWhen[$0]).tag($0)
}
}
}
}
.navigationTitle("Add Medicine")
}
}
For saving new data, We need same viewContext from environment Object.
Button(action: {
guard self.pillsCount != 0 else {return}
let newMed = Pills(context: viewContext)
newMed.medicineName = medicineName
newMed.status = "Not Taken"
newMed.countOfPill = Int16(self.pillsCount)
newMed.when = medicineWhen[selectedmedicineWhen]
newMed.id = UUID()
newMed.time = Date()
do {
try viewContext.save()
print("Medicine saved.")
presentationMode.wrappedValue.dismiss()
} catch {
print(error.localizedDescription)
}
}) {
Text("Add Medicine")
}
And finally UI looks like below.
Now on entering the data, it will be saved in DB using viewContext’s save() function.
Updating Data
For Updating Data, am gonna design a new UI and below it is!
struct UpdateMedView: View {
@State var medicine: Pills
@State var pillsCount = 0
@State var medicineWhen:[String] = ["After Food", "Before Food", "Evening"]
@State var selectedmedicineWhen = 0
let pillStatus = ["Not Taken", "Taken"]
@State var selectionStaus = 0
@Environment(\.managedObjectContext) private var viewContext
@Environment (\.presentationMode) var presentationMode
var body: some View {
NavigationView{
Form{
Section(header: Text("Medicine Detail")) {
Text(medicine.medicineName ?? "")
Stepper("\(pillsCount) per day", value: $pillsCount, in: 1...10)
}
Section(header: Text("When you need to take this pill")) {
Picker(selection: $selectedmedicineWhen, label: Text("When")) {
ForEach(0..<medicineWhen.count){
Text(self.medicineWhen[$0]).tag($0)
}
}
}
Section(header: Text("Did you take the medicines")) {
Picker(selection: $selectionStaus, label: Text("I am")) {
ForEach(0..<pillStatus.count){
Text(self.pillStatus[$0]).tag($0)
}
}
}
Button(action: {
updateMedicine(medicine: medicine)
}) {
Text("Update Medicine")
}
}
.navigationTitle("Update Medicine Status")
}
.onAppear(){
pillsCount = Int(medicine.countOfPill)
selectedmedicineWhen = medicineWhen.firstIndex(of: medicine.when ?? "") ?? 0
selectionStaus = pillStatus.firstIndex(of: medicine.status ?? "") ?? 0
}
}
func updateMedicine(medicine: Pills){
viewContext.performAndWait {
medicine.status = pillStatus[selectionStaus]
medicine.countOfPill = Int16(pillsCount)
medicine.when = medicineWhen[selectedmedicineWhen]
if pillStatus[selectionStaus] == "Taken"{
medicine.time = Date()
}
try? viewContext.save()
presentationMode.wrappedValue.dismiss()
}
}
}
Delete Data
Similarly for delete, we have to pass the item to delete function and after delete have to call viewContext.save() to make sure the updates.
Am using trailing swipe to do this in main screen
.swipeActions(edge: .trailing) {
Button {
print("Delete")
viewContext.delete(item)
try? viewContext.save()
} label: {
Text("Delete")
}
.tint(.red)
}
Reset Satus
To reset daily pill status, I’ve made some changes on onAppear() of contentView. As we checked the date saved for each item in DB and will update the status to “Not Taken” if not same Date.
.onAppear(){
for item in medicines{
if Calendar.current.isDateInToday(item.time ?? Date()){
print("yes")
}else{
print("No")
viewContext.performAndWait {
item.status = "Not Taken"
try? viewContext.save()
}
}
}
}
This is just a Phase I concept of using coreData in swiftUI and will see following Phases one by one. Follow me for Posted Updates.