i'm trying to save what i'm typing into core data. So, if somone accidentaly closed the app, the text need to remain in the TextField. This is how i'm trying to do it, but i'm getting this error message:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Cart deliveryAddress]: unrecognized selector sent to instance 0x600002276a00'
This is my View :
struct LivrareView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: [])
var carts: FetchedResults<Cart>
@State var adresaTextField : String = ""
@State var telefonTextField : String = ""
@State var tacamuriSwitch: Bool = false
@State var oraLivrare: String = ""
@State var specificatiiTextEditor: String = "Ex : Nu merge interfonu"
@ObservedObject var cart : Cart
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text(Texts.livrareViewText1)
.foregroundColor(.orange)
TextField("Ex: str. 16 Decembrie 1989, nr, 23, ap 1, et 1", text: $cart.wrappedDeliveryAddress, onEditingChanged: { _ in
let newCart = Cart(context: viewContext)
newCart.deliveryAddress = cart.wrappedDeliveryAddress
print(newCart.deliveryAddress)
do {
try viewContext.save()
} catch {
let error = error as NSError
fatalError("Unresolved error\(error)")
}
})
}
}
.padding(34)
}
public init(model: Cart? = nil) {
self.cart = model ?? Cart()
}
}
}
Here is my Cart. I'm using codegen : " Manually / none "
@objc(Cart)
public class Cart: NSManagedObject, Identifiable {
}
extension Cart {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Cart> {
return NSFetchRequest<Cart>(entityName: "Cart")
}
@NSManaged public var grams: Double
@NSManaged public var name: String?
@NSManaged public var price: Int32
@NSManaged public var deliveryAddress: String?
public var wrappedDeliveryAddress : String {
get { deliveryAddress ?? ""}
set { deliveryAddress = newValue}
}
}
This is a photo of my Core Data Model:
How can i make this work ?
This is where i'm calling the View :
TabView(selection: self.$index){
LivrareView()
.tag(0)
RidicareView()
.tag(1)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
CodePudding user response:
The issue you are having is trying to create a Cart
in the public init. Essentially, you are saying if the prior view gives me a Cart
use it, otherwise, I am going to make my own Cart
that doesn't exist in the managed object context. Since you never pass a Cart
in to this view, you only get a Cart
that is not in context. The first rule of Core Data is you have to operate in the same context. (Rule 2 is sometimes you can violate this, but not as a beginner.) When you try to use it, you get the error. And, the way you have things set up, you don't need it.
I condensed LivrareView
down to the bare minimum, and then added some Text()
views so you can see what is going on. First, you already have a @State
variable for adresa. You don't need the @ObservedObject var cart
for that. Then, when the time comes, you put the adresaTextField
in to newCart.wrappedDeliveryAddress
(you made it, you just as well use it exclusively and make deliveryAddress
private) and then save your entity. I moved the save out of the .onEditingChange
into an .onSubmit()
to save a little sanity, but the bug will become apparent when you use this code. You are creating a new entity every time you hit return (instead of every keystroke as your code had). I think this is what you were trying to avoid when you created the @ObservedObject var cart
, but that won't work. Your better plan is simply to use all @State
variables, collect your data and then create and save your managed object with a "save" button. I would also validate your entries first, before you allow a save.
struct LivrareView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: []) var carts: FetchedResults<Cart>
@State var adresaTextField : String = ""
var body: some View {
ScrollView {
VStack(alignment: .leading) {
TextField("Ex: str. 16 Decembrie 1989, nr, 23, ap 1, et 1", text: $adresaTextField)
.onSubmit {
let newCart = Cart(context: viewContext)
newCart.wrappedDeliveryAddress = adresaTextField
print(newCart.wrappedDeliveryAddress)
do {
try viewContext.save()
} catch {
let error = error as NSError
fatalError("Unresolved error\(error)")
}
}
Text("adresaTextField is \(adresaTextField)")
Text("Managed Objects:")
ForEach(carts) { cart in
Text(cart.wrappedDeliveryAddress)
}
}
}
}
}
edit: Also, a suggestion to save you some heartache when using Core Data with manual codegen. Create a third extension file to put your custom code in. If you change your attributes and need to regenerate the extensions, you will erase your custom code. I call mine "Cart CoreDataWrappedProperties". You can use what naming extension you want, but it will not be replaced if you regenerate the code. Ask me how I know.