Why do I get NIL when passing a CoreData entity object to another view? - core-data

This one is testing my patience, I simply want to pass a Coredata entity object to another view, but when I pass it on the sheet I get nil!!!
process:
save the entity object into a state var
pass the state var to a sheet modal
has anybody experienced this error? how did you fix it? do CData needs some special treatment?
writing some more stuff because stackoverflow doesn't let me post this question if I don't have enough text
import SwiftUI
struct TodayView: View {
#Environment(\.managedObjectContext) var viewContext
#FetchRequest var fetchRequest: FetchedResults<ToDoItem>
#EnvironmentObject var model: ContentModel
#State var taskToEdit: ToDoItem? //tp pass tpo the item detail view
#State var isPresented: Bool = false
init() {
_fetchRequest = FetchRequest<ToDoItem>(sortDescriptors: [NSSortDescriptor(keyPath: \ToDoItem.timestamp, ascending: false)],predicate: NSPredicate(format: "today == true && done == false"))
}
var body: some View {
VStack{
Text("Today").font(.title)
List {
ForEach(fetchRequest, id: \.self) { task in
Text(task.desc!)
.foregroundColor(task.done ? .white : task.today ? .white : .black)
.listRowBackground(task.done ? Color.green : task.today ? Color.blue : Color.white)
.onTapGesture {
taskToEdit = task
isPresented.toggle()
}
.swipeActions(allowsFullSwipe: true) {
Button(role: .cancel) {
task.done.toggle()
task.today = false
task.date_completion = Date()
do {try viewContext.save()} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
print("mark as done")
} label: {
Label("Done", systemImage: "checkmark")
}
.tint(.green)
}
.simultaneousGesture(LongPressGesture()
.onEnded { _ in
let impactMed = UIImpactFeedbackGenerator(style: .light)
impactMed.impactOccurred()
task.today.toggle()
do {try viewContext.save()} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
print("Loooong")
print(task.today)
}
)
}
.onDelete(perform: deleteItems)
}
.listRowSeparator(.hidden)
.listRowInsets(.init(top: 4, leading: 8, bottom: 4, trailing: 8))
.sheet(isPresented: $isPresented, onDismiss: {
isPresented = false
}) {
itemDetail(taskItem: taskToEdit, textFieldText: taskToEdit!.detail!, isPresented: $isPresented)
}
}
}

i changed my sheet to as suggested by Vadian and it worked. what the heck!
.sheet(item: self.$taskToEdit, content: { taskToEdit in
itemDetail(taskItem: taskToEdit, textFieldText: taskToEdit.detail ?? "")
})

Related

Too Many Notifications CoreData/CloudKit Sync

I have several apps that use CoreData / iCloud syncing and they all receive a slew of update/change/insert/delete/etc notifications when they start and sometimes as they are running without any changes to the underlying data. When a new item is added or deleted, it appears that I get notifications for everything again. Even the number of notifications are not consistent.
My question is, how do I avoid this? Is there a cut-off that can be applied once I'm sure I have everything up to date on a device by device basis.
Persistence
import Foundation
import UIKit
import CoreData
struct PersistenceController {
let ns = NotificationStuff()
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.stuff = "Stuff"
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "TestCoreDataSync")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
class NotificationStuff
{
var changeCtr = 0
init()
{
NotificationCenter.default.addObserver(self, selector: #selector(self.processUpdate), name: Notification.Name.NSPersistentStoreRemoteChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contextDidSave(_:)), name: Notification.Name.NSManagedObjectContextDidSave, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contextObjectsDidChange(_:)), name: Notification.Name.NSManagedObjectContextObjectsDidChange, object: nil)
}
#objc func processUpdate(_ notification: Notification)
{
//print(notification)
DispatchQueue.main.async
{ [self] in
observerSelector(notification)
}
}
#objc func contextObjectsDidChange(_ notification: Notification)
{
DispatchQueue.main.async
{ [self] in
observerSelector(notification)
}
}
#objc func contextDidSave(_ notification: Notification)
{
DispatchQueue.main.async
{
self.observerSelector(notification)
}
}
func observerSelector(_ notification: Notification) {
DispatchQueue.main.async
{ [self] in
if let insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject>, !insertedObjects.isEmpty
{
print("Insert")
}
if let updatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>, !updatedObjects.isEmpty
{
changeCtr = changeCtr + 1
print("Change \(changeCtr)")
}
if let deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject>, !deletedObjects.isEmpty
{
print("Delete")
}
if let refreshedObjects = notification.userInfo?[NSRefreshedObjectsKey] as? Set<NSManagedObject>, !refreshedObjects.isEmpty
{
print("Refresh")
}
if let invalidatedObjects = notification.userInfo?[NSInvalidatedObjectsKey] as? Set<NSManagedObject>, !invalidatedObjects.isEmpty
{
print("Invalidate")
}
let mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
guard let context = notification.object as? NSManagedObjectContext else { return }
// Checks if the parent context is the main one
if context.parent === mainManagedObjectContext
{
// Saves the main context
mainManagedObjectContext.performAndWait
{
do
{
try mainManagedObjectContext.save()
} catch
{
print(error.localizedDescription)
}
}
}
}
}
}
ContentView
import SwiftUI
import CoreData
struct ContentView: View {
#State var stuff = ""
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
VStack
{
TextField("Type here", text: $stuff,onCommit: { addItem(stuff: stuff)
stuff = ""
})
List {
ForEach(items) { item in
Text(item.stuff ?? "??")
}
.onDelete(perform: deleteItems)
}
}.padding()
}
private func addItem(stuff: String) {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
newItem.stuff = stuff
do {
try viewContext.save()
} catch {
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 {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
The database has an Item entity with a timestamp field and a string field named stuff.
It depends on if it's for examining Production or Debug builds in the system's Console or Xcode's Console respectively.
For Production builds, my understanding is the aim is to make my messages more findable (rather than de-emphasising/hiding other messages) by consistently using something like:
let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "YourCategorisationOfMessagesGoingToThisHandle")
and then in the code I might have things like
log.debug("My debug message")
log.warning("My warning etc")
fwiw: I tend to categorise stuff by the file it's in, as that's deterministic and helps me find the file, so my source files tend to start with
fileprivate let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: #file.components(separatedBy: "/").last ?? "")
If I do this, then I can easily filter the system's console messages to find stuff that's relevant to my app.
There's more on how to use this and the console to filter for the app's messages in the sytem console over here.
For Debug builds and the Xcode console the same consistent app log messages from my app could be used, e.g. my app's debug messages always start with "Some easily findable string or other". I don't believe there is a way to throttle/cut-off responses selectively. But it definitely possible to turn off debug messages from many of the noisy sub-systems completely (once happy that they are working reliably)
For Core Data and CloudKit cases mentioned, if I run the Debug builds with the -com.apple.CoreData.Logging.stderr 0 and -com.apple.CoreData.CloudKitDebug 0 launch args then that make Xcode's console a lot quieter :-). Nice instructions on how to set this up in the SO answer over here
My problem was that CoreData -> CloudKit integration was re-synching the same items over and over, thus the notifications. I discovered that I needed to add a sorted index for the modifiedTimestamp on all entities. Now things are much faster and few if any re-synched items.

SwiftUI / Core Data - Involuntary navigation between list view and detailed view when updating detailed view

I am using a TabView/NavigationView & NavigationLink to programmatically navigate from a list view to detailed view and when I update the boolean property 'pinned' to true & save the core data entity in the detailed view, I am getting the following unfortunate side effects:
an involuntary navigation back to the list view and then again back to the detailed view or
an involuntary navigation to another copy of the same detailed view and then back to the list view.
I have prepared a small Xcode project with the complete sample code
In the list view I use #FetchRequest to query the list and sort on the following:
#FetchRequest(entity: Task.entity(),sortDescriptors: [NSSortDescriptor(key: "pinned", ascending: false),
NSSortDescriptor(key: "created", ascending: true),
NSSortDescriptor(key: "name", ascending: true)])
In the list view I use the following:
List() {
ForEach(tasks, id: \.self) { task in
NavigationLink(destination: DetailsView(task: task), tag: task.id!.uuidString, selection: self.$selectionId) {
HStack() {
...
}
}
}
.
(1) If I omit the 'NSSortDescriptor(key: "pinned" ...)' I don't see the behavior.
(2) If I omit the 'tag:' and the 'selection:' parameters in the NavigationLink() I don't see the behavior. But I need to be able to trigger the navigation link programmatically when I create a new Task entity.
(3) It seems never to happen when I have a single entity in the list or changing the value of the 'pinned' boolean property in the first entity in the list.
(4) I get the warning:
[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window)...
The parent view to the list view (TasksListView) contains a TabView:
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
TasksListView()
}
.tabItem {
Image(systemName: "tray.full")
.font(.title)
Text("Master")
}
NavigationView {
EmptyView()
}
.tabItem {
Image(systemName: "magnifyingglass")
.font(.title)
Text("Search")
}
}
}
}
struct TasksListView: View {
// NSManagedObjectContext
#Environment(\.managedObjectContext) var viewContext
// Results of fetch request for tasks:
#FetchRequest(entity: Task.entity(),sortDescriptors: [NSSortDescriptor(key: "pinned", ascending: false),
NSSortDescriptor(key: "created", ascending: true),
NSSortDescriptor(key: "name", ascending: true)])
var tasks: FetchedResults<Task>
// when we create a new task and navigate to it programitically
#State var selectionId : String?
#State var newTask : Task?
var body: some View {
List() {
ForEach(tasks, id: \.self) { task in
NavigationLink(destination: DetailsView(task: task), tag: task.id!.uuidString, selection: self.$selectionId) {
HStack() {
VStack(alignment: .leading) {
Text("\(task.name ?? "unknown")")
.font(Font.headline.weight(.light))
.padding(.bottom,5)
Text("Created:\t\(task.created ?? Date(), formatter: Self.dateFormatter)")
.font(Font.subheadline.weight(.light))
.padding(.bottom,5)
if task.due != nil {
Text("Due:\t\t\(task.due!, formatter: Self.dateFormatter)")
.font(Font.subheadline.weight(.light))
.padding(.bottom,5)
}
}
}
}
}
}
.navigationBarTitle(Text("Tasks"),displayMode: .inline)
.navigationBarItems(trailing: rightButton)
}
var rightButton: some View {
Image(systemName: "plus.circle")
.foregroundColor(Color(UIColor.systemBlue))
.font(.title)
.contentShape(Rectangle())
.onTapGesture {
// create a new task and navigate to it's detailed view to add values
Task.create(in: self.viewContext) { (task, success, error) in
if success {
self.newTask = task
self.selectionId = task!.id!.uuidString
}
}
}
}
}
struct DetailsView: View {
// NSManagedObjectContext
#Environment(\.managedObjectContext) var viewContext
#ObservedObject var task : Task
#State var name : String = ""
#State var dueDate : Date = Date()
#State var hasDueDate : Bool = false
#State var isPinned : Bool = false
var body: some View {
List() {
Section() {
Toggle(isOn: self.$isPinned) {
Text("Pinned")
}
}
Section() {
TextField("Name", text: self.$name)
.font(Font.headline.weight(.light))
Text("\(task.id?.uuidString ?? "unknown")")
.font(Font.headline.weight(.light))
}
Section() {
HStack() {
Text("Created")
Spacer()
Text("\(task.created ?? Date(), formatter: Self.dateFormatter)")
.font(Font.subheadline.weight(.light))
}
Toggle(isOn: self.$hasDueDate) {
Text("Set Due Date")
}
if self.hasDueDate {
DatePicker("Due Date", selection: self.$dueDate, in: Date()... , displayedComponents: [.hourAndMinute, .date])
}
}
}
.navigationBarTitle(Text("Task Details"),displayMode: .inline)
.navigationBarItems(trailing: rightButton)
.listStyle(GroupedListStyle())
.onAppear() {
if self.task.pinned {
self.isPinned = true
}
if self.task.name != nil {
self.name = self.task.name!
}
if self.task.due != nil {
self.dueDate = self.task.due!
self.hasDueDate = true
}
}
}
// save button
var rightButton: some View {
Button("Save") {
// save values in task & save:
self.task.pinned = self.isPinned
if self.hasDueDate {
self.task.due = self.dueDate
}
if self.name.count > 0 {
self.task.name = self.name
}
Task.save(in: self.viewContext) { (success, error) in
DispatchQueue.main.async {
if success {
print("Task saved")
}
else {
print("****** Error: Task can't be saved, error = \(error!.localizedDescription)")
}
}
}
}
.contentShape(Rectangle())
}
}
extension Task {
static func save(in managedObjectContext: NSManagedObjectContext, completion: #escaping (Bool, NSError?) -> Void ) {
managedObjectContext.performAndWait() {
do {
try managedObjectContext.save()
completion(true, nil)
} catch {
let nserror = error as NSError
print("****** Error: Unresolved error \(nserror), \(nserror.userInfo)")
completion(false, nserror)
}
}
}
}
Any suggestions?
You seem to be creating your tab view in a different way than me. Those extra navigation views made cause an issue.
Not sure if it will help or not but I do it like that:
struct ContentView: View {
var body: some View {
TabView {
TasksListView()
.tabItem {
Image(systemName: "tray.full")
.font(.title)
Text("Master")
}
EmptyView()
.tabItem {
Image(systemName: "magnifyingglass")
.font(.title)
Text("Search")
}
}
}
}
This is a bug in iOS 13.
It has been fixed since iOS 14.0 beta 3.
You will find a similar question here (the accepted answer provides a workaround):
Issue when rearranging List item in detail view using SwiftUI Navigation View and Sorted FetchRequest

iOS 13.4 CoreData SwiftUI app crashes with "EXC_BREAKPOINT (code=1, subcode=0x1f3751f08)" on device

A very simple CoreData app: All code provided below.
Start up with CoreData template single view app.
2 entities with a string attribute each: Message(title) and Post(name)
A NavigationView containing
NavigationLink to a list of messages
NavigationLink to a list of posts
Each linked ListView (Message/Post) has
a button to add an item to the list
a button to remove all items from the list
Now, when you run this app on a simulator (any iOS 13.x version) all runs as expected from the description above.
But on a DEVICE running iOS 13.4
Tap "Messages"
Creating/deleting messages works fine, SwiftUi view updates immediately.
Tap "back"
Tap "Messages" again. While still creating/deleting messages works fine: The debugger now shows a warning: "Context in environment is not connected to a persistent store coordinator: NSManagedObjectContext: 0x280ed72c0
Tap "Posts"
==> App crashes with EXC_BREAKPOINT (code=1, subcode=0x1f3751f08)
You can start the process with Posts first, too. Then the same crash occurs on the messages list view.
I strongly believe this is an iOS 13.4 bug because similar code ran fine on Xcode 11.3 / iOS 13.3.
Does anyone know a fix or workaround for this?
Here is a link to the full project: Full Xcode Project
The ContentView:
import SwiftUI
import CoreData
struct MessageList: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Message.entity(), sortDescriptors: [])
var messages: FetchedResults<Message>
var body: some View {
List() {
ForEach(messages, id: \.self) { message in
Text(message.title ?? "?")
}
}
.navigationBarItems(trailing:
HStack(spacing: 16) {
Button(action: deleteMessages) {
Image(systemName: "text.badge.minus")
}
Button(action: addMessage) {
Image(systemName: "plus.app")
}
}
)
}
func addMessage() {
let m = Message(context: moc)
m.title = "Message: \(Date())"
try! moc.save()
}
func deleteMessages() {
messages.forEach {
moc.delete($0)
}
}
}
struct PostList: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Post.entity(), sortDescriptors: [])
var posts: FetchedResults<Post>
var body: some View {
List {
ForEach(0..<posts.count, id: \.self) { post in
Text(self.posts[post].name ?? "?")
}
}
.navigationBarItems(trailing:
HStack(spacing: 16) {
Button(action: deletePosts) {
Image(systemName: "text.badge.minus")
}
Button(action: addPost) {
Image(systemName: "plus.app")
}
}
)
}
func addPost() {
let p = Post(context: moc)
p.name = "Post \(UUID().uuidString)"
try! moc.save()
}
func deletePosts() {
posts.forEach {
moc.delete($0)
}
try! moc.save()
}
}
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
var body: some View {
NavigationView {
VStack(alignment: .leading){
NavigationLink(destination: MessageList()) {
Text("Messages")
}.padding()
NavigationLink(destination: PostList()) {
Text("Posts")
}.padding()
Spacer()
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct ContentView_Previews: PreviewProvider {
static let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
static var previews: some View {
ContentView()
.environment(\.managedObjectContext, moc)
}
}
Screenshot of the Model:
The SceneDelegate (unaltered from template, provided for completeness):
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let contentView = ContentView().environment(\.managedObjectContext, context)
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {}
func sceneDidBecomeActive(_ scene: UIScene) {}
func sceneWillResignActive(_ scene: UIScene) {}
func sceneWillEnterForeground(_ scene: UIScene) {}
func sceneDidEnterBackground(_ scene: UIScene) {
(UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}
}
The AppDelegate (unaltered from template, provided for completeness):
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Coredata134")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Update iOS 14.0 (beta 1):
This issue seems to have been resolved on iOS 14.
I also believe this is a bug.
You can workaround for now by setting the environment variable again within the NavigationLinks in ContentView:
NavigationLink(destination: MessageList().environment(\.managedObjectContext, moc)) {
Text("Messages")
}.padding()
NavigationLink(destination: PostList().environment(\.managedObjectContext, moc)) {
Text("Posts")
}.padding()
EDIT:
Just noticed that this workaround has at least one serious negative side effect: in case the #FetchRequest in the destination View uses a sortDescriptor and the destination View itself contains a NavigationLink, (e.g. to a DetailView), then modifying an attribute contained in the sortDescriptor in the DetailView will cause the DetailView to be popped and pushed again as soon as the new attribute value leads to a new sort order.
To demonstrate this:
a) add a new attribute of type Integer 16 named "value" to the Message entity in the Core Data model.
b) update func addMessage() as follows:
func addMessage() {
let m = Message(context: moc)
m.title = "Message: \(Date())"
m.value = 0
try! moc.save()
}
c) add the following struct to ContentView.swift
struct MessageDetailList: View {
#ObservedObject var message: Message
var body: some View {
Button(action: {
self.message.value += 1
}) {
Text("\(message.title ?? "?"): value = \(message.value)")
}
}
}
d) Update the ForEach in struct MessageList as follows:
ForEach(messages, id: \.self) { message in
NavigationLink(destination: MessageDetailList(message: message).environment(\.managedObjectContext, self.moc)) {
Text("\(message.title ?? "?"): value = \(message.value)")
}
}
e) replace #FetchRequest in MessageList with:
#FetchRequest(entity: Message.entity(), sortDescriptors: [NSSortDescriptor(key: "value", ascending: false)])
Run the code and tap on "Messages". Create three messages, then tap on the third one. In the DetailView, tap on the Button. This will increase the value attribute of this message to 1 and thus resort the fetch results on MessageList, which will trigger a pop and push again of the detail list.

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