Core Data: how to have a default value - core-data

self learning beginner here.
Although in the xcdatamodel file I have the default value for the attribute of that entity (see screenshot), when I build the preview, the Text("+") isn't there. I guess the countnum attribute in the TargetEntity is still empty. My thinking is to have the add() run if the attribute is empty. But that doesn't work either. Is there a way to automatically initialize the attribute when the app runs, instead of needing to build a button for the user to press?
Thanks a million
struct ListView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var targets: FetchedResults<TargetEntity>
var body: some View {
VStack {
if let firstItem = targets.first {
Text("+")
.onTapGesture (count: 2){
do {
increment(firstItem)
try moc.save()
} catch {
print(error)
add()
}
}
}
}
}
func increment(_ item: TargetEntity) {
item.countnum += 1
save()
}
func add() {
let countnum = TargetEntity(context: moc)
countnum.countnum = 0
save()
}
func save() {
do { try moc.save() } catch { print(error) }
}
}

Related

Fetching data in Preview: "A fetch request must have an entity UncaughtExceptionError"

I have a ListView file which can build the preview code fine without the Core Data piece in it. However, when I build that ListView in the ContentView, I got an error that is not shown in the text editor like usual but in the diagnostics window. The error is "A fetch request must have an entity UncaughtExceptionError: [AppName] crashed due to an uncaught exception".
When I run the app in the simulator, the ContentView seems to build fine. (the functions attached to Text("+") don't work either, something about "map table argument is NULL", but one error at a time.)
EDIT: The entities in Core Data are set up as “Class Definition”.
Many many thanks in advance.
struct ListView: View {
#Environment(\.managedObjectContext) var viewContext
#FetchRequest(sortDescriptors: []) var targets: FetchedResults<TargetEntity>
#FetchRequest(sortDescriptors: []) var positives: FetchedResults<PositiveEntity>
var body: some View {
VStack {
HStack {
VStack {
Text("+")
.onAppear{add()}
.onTapGesture (count: 2){
do {
print("error")
increment(targets.first!)
try viewContext.save()
} catch {
print("error")
}
}
.onLongPressGesture {
addPositive()
}
List{
ForEach(positives) { item in
Text(item.title ?? "Unknown Title")
}
.onDelete(perform: deleteItems)
}
}
}
}
}
func add() {
let countnum = TargetEntity(context: viewContext)
countnum.countnum = 0
save()
}
func addPositive(){
let newPositive = PositiveEntity(context: viewContext)
newPositive.title = "Action"
save()
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { positives[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
func save() {
do { try viewContext.save() } catch { print(error) }
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
ListView()//.environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
}
Just faced same issue after updating core data model with new entity, in a project created with core data.
Error happens only to "canvas" preview, with simulator or real devices everything is ok.
Issue resolved for me by adding mock of created model to PersistenceController under the preview var

onAppear is causing problem with the preview but no error is shown

self learning beginner here.
When I remove .onAppear{add()}, the preview works fine. I tried to attach it to other the body view, the Vstack but it causes another error. I read/watched several tutorials but nothing like this is mentioned....
Any help is appreciated
struct ListView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var targets: FetchedResults<TargetEntity>
#FetchRequest(sortDescriptors: []) var positives: FetchedResults<PositiveEntity>
var body: some View {
VStack {
Text("+")
.onAppear{add()}
.onTapGesture (count: 2){
do {
increment(targets.first!) //I also sense that doing "!" is not good. But it's the only way I can keep it from causing error "Cannot convert value of type 'FetchedResults' to expected argument type 'X'"
try moc.save()
} catch {
print("error")
}
}
}
}
func increment(_ item: TargetEntity) {
item.countnum += 1
save()
}
func add() {
let countnum = TargetEntity(context: moc)
countnum.countnum = 0
save()
}
func save() {
do { try moc.save() } catch { print(error) }
}
}
EDIT 20220509:
As advised by #Yrb (great thanks), the error is likely caused by the lack of a proper set up of preview var in the persistence file. I post the relevant code here for visiblity.
Data Controller file
import CoreData
import Foundation
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "CounterLateApr")
init () {
container.loadPersistentStores { description, error in
if let error = error {
print("Core Data failed to load: \(error.localizedDescription)")
}
}
}
}
preview code in a view
struct ListView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
ListView()
}
}
}
[AppName].app file
import SwiftUI
#main
struct CounterLateAprApp: App {
#StateObject private var dataController = DataController()
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
}

SwiftUI: How to show/edit an int from CoreData without being in a List?

self-learning beginner here. I am trying to show an Int from Core Data in a VStack in ContentView, not in a List. But literally all the tutorials I can find about Core Data (tracking Books, Movies, Orders, Students) are using a List to show an array containing an Int. Nothing on showing an Int by itself.
Xcode can build countnum.countnum +=1 with no problem. Seems to me it is reading it fine. But once I try to show it, it just doesn’t work. I’m wrecking my brain here.
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var countnum: FetchedResults<CountNum>
var body: some View {
// let countnum = CountNum(context: moc)
VStack{
Text("+")
.padding()
.onTapGesture (count: 2){
let countnum = CountNum(context: moc)
countnum.countnum += 1
}
Text("\(countnum)") //No exact matches in call to instance method 'appendInterpolation'
}
}
}
Thanks
....all the tutorials ... show an array containing an Int. Yes, that's because CoreData
can contain many "objects". You get an array of your CountNum objects when
you do your .....var countnum: FetchedResults<CountNum>. So you need to decide which CountNum you want to
use. For example, if you want to use the first one, then:
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var countnum: FetchedResults<CountNum>
var body: some View {
VStack {
if let firstItem = countnum.first {
Text("+")
.padding()
.onTapGesture(count: 2) {
firstItem.countnum += 1
do {
try moc.save()
} catch {
print(error)
}
}
Text("\(firstItem.countnum)").foregroundColor(.green)
}
}
}
}
EDIT-1: adding new CountNum to CoreData example code in the add button.
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var countnum: FetchedResults<CountNum>
var body: some View {
Button(action: {add()}) { Text("add new CountNum").foregroundColor(.green) }
.padding(.top, 50)
List {
ForEach(countnum) { item in
HStack {
Text("++")
.onTapGesture(count: 2) { increment(item) }
Text("\(item.countnum)").foregroundColor(.blue)
Text("delete").foregroundColor(.red)
.onTapGesture { delete(item: item) }
}
}
}
}
func increment(_ item: CountNum) {
item.countnum += 1
save()
}
func add() {
let countnum = CountNum(context: moc)
countnum.countnum = 0
save()
}
func delete(item: CountNum) {
moc.delete(item)
save()
}
func save() {
do { try moc.save() } catch { print(error) }
}
}

after delete of item in SwiftUI List (backed by Core Data) getting a "Thread 1: EXC_BAD_INSTRUCTION" error? (code attached)

After completing a delete of a row in a SwiftUI List I am getting a "Thread 1: EXC_BAD_INSTRUCTION" error. It seems the Core Data delete works as after I restart that data has been removed. So maybe something to do with SwiftUI trying to update it's view after the Core Data delete is performed????
Code:
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(entity: GCItem.entity(), sortDescriptors: []) var gcItems: FetchedResults<GCItem>
private func addItem(title:String) {
let newItem = GCItem(context: context)
newItem.id = UUID()
newItem.title = title
do {
try context.save()
} catch let e as NSError {
fatalError("Unresolved error \(e), \(e.userInfo)")
}
}
private func deleteItem(at offsets:IndexSet) {
self.context.perform {
// Delete Item
for index in offsets {
let item = self.gcItems[index]
self.context.delete(item)
}
// Persist
do {
try self.context.save()
} catch let e as NSError {
// TODO: How to undelete list???
print("ERROR : Can not save GCItem items: \(e.description)")
}
}
}
var body: some View {
NavigationView {
VStack {
List() {
ForEach(gcItems) { gcItem in
HStack {
Text("test")
}
}
.onDelete(perform: self.deleteItem)
}
Button(action: { self.addItem(title: "Testing 123") }) {
Text("ADD ITEM")
}
}
}
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return ContentView().environment(\.managedObjectContext, context)
}
}
#endif
Screen Shot
The fix was NOT to use attributes in Core Data named "id". So I changed this to "myId" and then things worked fine.

SwiftUI - Pass a fetchResult to another View

I'm trying to pass a FetchResult to another view in order to have all my tables updated at the same time.
My problem:
view1 {
#FetchRequest
ForEach{
NavigationLink(passing fetchRequest.value to View 2)}
}
View2 {
var value1 :fetchRequest.value from view 1
ForEach{
NavigationLink(passing value1.value to View 3)}
}
View3....
Problem here is, if I do a delete or a add on the view 3, the views 1 and 2 won't update until I go back to view 1, and descend again to view 2 and 3.
Do you have an idea on how to have a quick update of these values ?
Best
Tim
I've never seen anyone else try this before but I just lifted the #FetchRequest up into a superview and passed the fetch results (items in this case) as a param down to the subview:
struct ContentView: View {
#State var count = 0
#FetchRequest<Item>(sortDescriptors: [], predicate: nil, animation: nil) var items
var body: some View {
NavigationView {
MasterView(items: items)
.navigationTitle("Master \(count)")
.navigationBarItems(trailing: Button("Increment"){
count += 1
})
}
}
}
struct MasterView: View {
var items : FetchedResults<Item>
var body: some View {
List {
ForEach(items) { item in
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
}
.onDelete(perform: deleteItems)
}
.toolbar {
// #if os(iOS)
// ToolbarItem(placement: .navigation){
// EditButton()
// }
// #endif
//ToolbarItem(placement: .automatic){
ToolbarItem(placement: .bottomBar){
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
ToolbarItem(placement: .bottomBar){
Button(action: {
ascending.toggle()
}) {
Text(ascending ? "Descending" : "Ascending")
}
}
}
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
newItem.name = "Master"
do {
try newItem.validateForInsert()
try viewContext.obtainPermanentIDs(for: [newItem])
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map {items[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
The reason I did this is I used a the launch argument -com.apple.CoreData.SQLDebug 4 and I noticed it was hitting the database every time the state changed and a View was recreated that contained a #FetchRequest which I didn't want.

Resources