redearz
redearz

Reputation: 135

How to store textfield into core data with swiftui

I can't complete a task to store a value with core data when entered into TextField, and to be showed again when entering the view. Is it possible?

I need to store name and surname. For that I created ProfileData data model but can't find any relevant info. on how to make it work properly.

Please find below the code:

import SwiftUI
import CoreData

struct ProfileView: View {
    @State private var name: String = ""
    @State private var surname: String = ""
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(
        entity: ProfileData.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \ProfileData.name, ascending: true),
            NSSortDescriptor(keyPath: \ProfileData.surname, ascending: true),

        ]
    ) var profile: FetchedResults<ProfileData>
    @EnvironmentObject var profile1: ProfileData

    var body: some View {
        VStack {
            HStack {
                VStack {
                    HStack {
                        Text("Meno:")
                            .font(.headline)
                            .padding()
                        TextField("", text: $name, onCommit: {
                        self.profile1.name = self.name
                        try? self.managedObjectContext.save()})
                    .onAppear {
                        self.name = self.profile.name != nil ? "\(self.profile.name!)" : "Zadajte meno" //here I get error Value of type 'FetchedResults<ProfileData>' has no member 'name'
                    }
                    .onDisappear {
                        self.profile1.name = self.name
                        try? self.managedObjectContext.save()
                    }
                        }   .padding(.leading, 37)
                    }
                    HStack {
                        Text("Priezvisko:")
                            .font(.headline)
                            .padding()
                        TextField("Zadajte priezvisko", text: $surname)
                    }
                }
            }
        }
        .navigationBarTitle(Text("Profil"))
    }
}

Here is my ProfileData+CoreClassData.swift:

import Foundation
import CoreData

@objc(ProfileData)
public class ProfileData: NSManagedObject {

}

And here is my ProfileData+CoreDataProperties.swifft

//
//  ProfileData+CoreDataProperties.swift
//  UcmMap
//
//  Created by Jakub Adamec on 06/01/2020.
//  Copyright © 2020 Jakub Adamec. All rights reserved.
//
//

import Foundation
import CoreData


extension ProfileData {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<ProfileData> {
        return NSFetchRequest<ProfileData>(entityName: "ProfileData")
    }

    @NSManaged public var name: String?
    @NSManaged public var surname: String?

}

Upvotes: 2

Views: 4740

Answers (1)

Aspid
Aspid

Reputation: 699

Here's full code:

import SwiftUI
import CoreData

@objc(ProfileData)
public class ProfileData: NSManagedObject, Identifiable {
    public var id = UUID()
}


extension ProfileData {


    @NSManaged public var name: String?
    public var wrappedName: String{
        get{name ?? "NoName"}
        set{name = newValue}
    }
    @NSManaged public var surname: String?
    public var wrappedSurname: String{
        get{surname ?? "NoSurname"}
        set{surname = newValue}
    }
}

struct ProfileView: View {
    @State private var name: String = ""
    @State private var surname: String = ""
    @Environment(\.managedObjectContext) var moc: NSManagedObjectContext // it will need you to add new examples of youre entities and save all changes
    @FetchRequest(
        entity: ProfileData.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \ProfileData.name, ascending: true),
            NSSortDescriptor(keyPath: \ProfileData.surname, ascending: true),

        ]
    ) var profileList: FetchedResults<ProfileData>
    //fetchRequest is a list of all objects off type ProfileData - saved and unsaved

    var body: some View {
        NavigationView{

            List{
                ForEach(profileList){profile in
                    NavigationLink(destination: profileUpdateView(profile: profile)){
                        Text("\(profile.wrappedName) \(profile.wrappedSurname) ")
                    }
                }
                HStack{
                    Image(systemName: "plus.circle.fill")
                        .foregroundColor(.green)
                        .imageScale(.large)
                    Button("add a new profile"){
                        let newProfile = ProfileData(context: self.moc)
                        newProfile.wrappedName = "Name"
                        newProfile.wrappedSurname = "Surname"
                        }

                }
             }

                .navigationBarTitle(Text("Profile"))
                .navigationBarItems(trailing: Button("save"){
                if self.moc.hasChanges{
                    do{try self.moc.save()}
                    catch{print("Cant save changes: \(error)")}
                }
            })

        }
    }
}
struct profileUpdateView: View {
    @ObservedObject var profile: ProfileData
    var body: some View{
        VStack {
              HStack {
                  Text("Meno:")
                      .font(.headline)
                      .padding()
                TextField("Zadajte meno", text: $profile.wrappedName)
              }
              HStack {
                  Text("Priezvisko:")
                      .font(.headline)
                      .padding()
                    TextField("Zadajte priezvisko", text: $profile.wrappedSurname)
              }
          }
    }
}
struct ProfileView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        let newProfile = ProfileData(context: context)
        newProfile.wrappedName = "Name"
        newProfile.wrappedSurname = "Surname"
        return ProfileView().environment(\.managedObjectContext, context)
    }
}

notice several things:

  1. FetchRequest returns you a list of entities of type you define. There may be one item, several items or there might be none.
  2. If you fetches something, you need to have ForEach loop to show the result (most of the time).
  3. To do so, I added an id to you're entity. Its not connected to CoreData, and every time FetchRequest gets new results, id will change, but that's fine. The only purpose of it - is to let ForEach know, which part of loop is connected with exactly object. Therefor ForEach can change it and show you updates
  4. Don't forget to change codegen property of your entities in data model to manual/none. To do so open DataModel like, select your entity and go to data model inspector (right side of the screen). If you don't the compiler will create those files in compilation itself and this class will be defined twice.
  5. If you want to create new object of your type - you need to use MOC. Its the only way to create objects of NSManagedObject type.
  6. in TextField you can put a BindableObject. You tried to use @State for that purpose, but you don't need it. You can modify ordinary objects property just like that: TextField("Zadajte meno", text: $profile.wrappedName) The $ symbol is to make this property wrapped @Binding. This means, all the changes made inside this View will translate into this object instantly.
  7. You can't just put @NSManaged property, because most of them have optional type like String?. I made a computed property wrappedName to use it simply in Views.
  8. I use an @ObservedObject wrapper in update View. Its like @State, but for classes. you use it when you want instantly update the View when this object changes. It also helps create a Binding. Your class must meet the ObservableObject protocol requirements, but NSManagedObject already does, so you don't need to worry about it. All @NSManaged attributes are already @Published.
  9. There is a way to use Canvas even if you using CoreData. Just get the context from AppDelegate, create any test entities. But remember, saved changes will add to you're preview.
  10. And finally, you need to save all the changes with moc.save(). It can throw an exception, so you must do it in try-catch. And its a good practice to check, if there really are unsaved changes.

Good luck in learning SwiftUI. There is not so much info of using SwiftUI and CoreData. Try to check it on hackingwithswift, there's much helpful information.

Upvotes: 7

Related Questions