Post

Replies

Boosts

Views

Activity

Reply to SwiftUI update of deleted CoreData entities from different Tab
hi, your question made me realize i had something like this situation occurring as a very remote possibility in one of my projects. my solution (which works, as far as i have tested) is: when a detail view is initialized, record the id of the object you display (you do have an id: UUID? attribute on the object, yes?). with every .onAppear() of the DetailView, use a Core Data fetch to see whether you still have a CD object with that id. if no such object exists, then just dismiss the detail view right away. hope that helps, DMG
Jan ’21
Reply to Passing NSManagedObject SwiftUI
hi, i am assuming that you are describing this problem: you tap the Add button inTaskItemsView the addTask() function does indeed add a new Task to the list (var list: TaskList) the TaskItemsView does not update if that's the case, in TaskItemsView, you should do two things: define the list variable as @ObservedObject var list: TaskList. this makes the TaskItemsView responsive to changes in Core Data attributes of the list. after line 43 (list.addToItems(newTask)) add list.objectWillChange.send(). adding a new TaskItem to the list's NSSet of TaskItems is not seen as a change to a list attribute (its reference to the NSSet), so you need to signal a change to the list yourself. hope that helps, DMG
Dec ’20
Reply to List Core Data Entity Relationships
hi, in response to @toqix, i don't know exactly the definition of your entities, but the general principle is that if entity A has a one-to-many relationship with entity B (that is, each A can have many Bs, but each B is associated with only one A), then changing an attribute's value on a B does not trigger a SwiftUI view update on a view that is driven by a @FetchRequest or @ObservedObject for A -- because no attribute of A has been changed. the solution is usually that if you change an attribute's value on a B, then also determine its associated A, possibly some a = B.referenceBackToA (depending on how you named the relationship in B), and call a?.objectWillChange.send(). hope that helps, DMG
Nov ’20
Reply to Is it possible to delete an item from Coredata using onTapGesture?
hi, the .onDelete modifier is usually something you add to a ForEach, as in List { 	ForEach(items) { item in 		Text(item.name) 	} 		.onDelete(perform: yourDeleteFunction) } the .onTapGesture is something you would add to a view associated with an item in the list, within the scope of the ForEach: List { 	ForEach(items) { item in 		Text(item.name) 			.onTapGesture({ doSomething(to: item) }) // doSomething could certainly delete this item 	} } (i think that's right and) hope that helps, DMG
Nov ’20
Reply to Deleting in a ForEach with .filter
hi, the easiest method (assuming that you only use .onDelete with completed items, where $0.completed == false): since your delete function is given offsets into the "Open" list, start by getting that list with the same filter process, then modify your removal code based on pulling out items from the filtered list by the indices you have. something like this should work: func delete(at offsets: IndexSet) { 	let openEvents = viewModel.eventsList.filter { $0.completed == false } 	viewModel.removeEvents( offsets.map({ openEvents[$0] }) ) } where the removeEvents function on your viewModel removes all events in the given list: func removeEvents( events: [Event] ) { 	eventList.removeAll(where: { events.contains($0) }) } of course, you might not actually be removing these; perhaps you are just be setting the completed value to true. hope that helps, DMG
Oct ’20
Reply to Xcode 12: print() not printing to debug area
hi, (perhaps not your issue, but still, FWIW): Xcode 12 has a habit of opening projects with the Debug View at the bottom of the Xcode window showing only the Variables view, but not the Console window. you may need to click on the "Show the Console" button at the bottom, right of the Debug area, or follow the menu choices View --> Debug Area --> Activate Console. hope that helps, DMG
Sep ’20
Reply to Navigate back after saving in Swift UI
hi, since you tagged Core Data, i'll assume you are seeing a detail view of a Core Data object. the situation depends on whether this is a live edit or not. if this is a live edit, any change you make in the Detail view to the fields of the object (which is already an ObservableObject, and you would indicate that it be an @ObservedObject in your code) is done immediately; using the Back button works as is. if this is not a live edit, one might off-load all the values to be edited to @State variables when the view appears; those variables become the editable items; and when the user taps a Save button, the @State variables are copied back to the Core Data object to commit the edit. if it's the latter situation, one possibility is to hide the Back button; replace it with a Cancel button (so any edits are ignored), and add a Save button. you could do something like this: // define this in your Detail view @Environment(\.presentationMode) var presentationMode // and you have some Core Data object for this View var myObject: MyCoreDataObject // and lots of View code, with the following modifiers attached .navigationBarBackButtonHidden(true) .navigationBarItems( leading: Button(action : { self.presentationMode.wrappedValue.dismiss() }){ Text("Cancel") }, trailing: Button(action : { self.commitDataEntry() }){ Text("Save") }) .onAppear(perform: loadStateVariables) the loadStateVariables function would copy values from the core data object to the @State variables, and the commitDataEntry() function would copy values from the @State variables back to the object and then dismiss with the same presentationMode.wrappedValue.dismiss() call. i'll leave it to the UI police about whether this is the best of policies. hope that helps, DMG
Sep ’20
Reply to Xcode 12 beta 6 Core Data generated source yields compilation errors
hi, not sure it's a bug, as such. (it's a feature!) if you had an NSManagedObject in Xcode 11 with an id attribute, to make it Identifiable you would likely add an extension to the class in your own code to say that it conforms to Identifiable (without doing anything special). Xcode 12 seems to sense that you have an id field (that's Hashable) as an attribute and adds its own extension to the class to say that it's Identifiable in its generated file. just remove your own addition of conformance to Identifiable. hope that helps, DMG
Sep ’20
Reply to @FetchResults doesn't refresh view when updating related fields
hi, when a Category has a to-many relationship with an Item, and you edit an attribute of an Item, the item will do an objectWillChange.send(). but your @FetchRequest setup is only responding to changes to Categories. one possible suggestion: on line 26 when you change the name of an item, insert a line in advance of the name change (between line 25 and line 26) to tell your View that the content it is displaying needs to be recomputed: category.objectWillChange.send() i think that will work -- but full disclosure: i did not test. hope that helps, DMG BTW: thanks for asking. turns out i had something like this in one of my projects and realized when i read your question that i had the same situation in one case. sure enough -- one piece of a display was not updating after editing the "Items" associated with a "Category." i used essentially the idea above, although the technique was a little different.
Aug ’20
Reply to Recommended method of passing NSManagedObjectContext to @ObservedObject/@StateObject
hi, how about making private let context: NSManagedObjectContext be, instead, private var context: NSManagedObjectContext? = nil, writing @ObservedObject var filter = Filter() in the ContentView, and then adding an .onAppear() modifier in the body of the ContentView that does something like var body: some View { 	List(filter.someObjects) { object in 				// whatever you display here for an object 		} 			.onAppear(filter.setContext(context)) } where setContext() is a method on Filter that sets the context and does whatever fetch you need. that should cause the ContentView to update. hope that helps, DMG
Aug ’20
Reply to Update SwiftUI View on NSManagedObject edit
hi, one correction to my last reply: point number (3) wasn't quite right (i had it backwards; sorry, it was the last thing i wrote before heading off to bed). it should have been: (3) instead, if you write something like ExerciceHistoryRow(name: exerciceHistory.name!) and you use var name: String for the View, that will be updated properly. be sure to NOT define this variable using @State, because that gives ownership of the View to SwiftUI. and in general, you'll obviously want to pass along more than just date for a single field in the object ... my conclusions about this whole "display not updating" situation, especially with Core Data objects (one i've been struggling with for some time): passing a reference to an object to a "RowView" works fine if it is an @ObservedObject -- and if the RowView makes any changes to the object, you'll know about it. however, deleting that object will crash if the RowView is still held onto by SwiftUI when the deletion occurs. (i think the situation is even more murky when using Core Data and @FetchRequest.) passing a struct is the way to go whenever you want a view to be a "display-only," such as a row in a List where by tapping, you will navigate to a separate, detail view. however, do not make that a @State variable in the "RowView" -- once set on creation, SwiftUI owns it and you can't change or reset it from outside the view. (in XCode 11) if you really want to pass an object to such a "display-only" view, offload the data from the object you want displayed into a custom struct that copies the correct fields and information from the object, and pass that struct. again, do not make that a @State variable. i'd love to hear from others on this! hope that helps, DMG
Aug ’20
Reply to Update SwiftUI View on NSManagedObject edit
hi, I've looked at this more carefully today, and I am not sure the problem is really solved. I think there are three cases. (1) ExerciceHistoryRow(exerciceHistory: exerciceHistory) accepts its incoming argument as a simple var and does a simple View based on this var exerciceHistory -- e.g., a simple Text(exerciceHistory.name!) and some other items. I think this will fail. once that ExerciceHistoryRow is put out in the SwiftUI-sphere, it's out there and managed by SwiftUI and will not be changed because it is fully determined. the same holds true if you implement an .onAppear() in ExerciceHistoryRow to load a @State variable for the name. that @State var will never be changed in the future. (2) ExerciceHistoryRow(exerciceHistory: exerciceHistory) accepts its incoming argument as an @ObservedObject var, and that will update correctly. but if you ever delete it, you are looking at a certain crash. (3) instead, if you write something like ExerciceHistoryRow(name: exerciceHistory.name!) and you use @State var name: String for the View, that's likely to work and be updated properly. every redraw of the List will reset the @State variable and the row view will be updated. I'll be curious to know what you find. hope that helps, DMG
Aug ’20
Reply to Using CoreData for my first app?
hi, plists and User Defaults (which, i think, is just a plist) are fine for small amounts of data, but once the data size grows, you should be looking at Core Data and/or using the file system directly (the DocumentsDirectory in iOS). in Core Data, the two entities would be Game and Player, and it's a many-to-many relationship. assuming you want the players in a game listed in order (the first one, the second one) and indicate that in the Core Data model, the Game class would be using an NSOrderedSet for player references (perhaps named players), while the Player class would be using an NSSet for the game references (perhaps named games), assuming there's no need to keep an ordering there (but one could later sort by date played or something). Xcode generates custom accessors to manage these relationships (keeping both ends of the relationship in sync). if you have two Players and want to create a new Game, you could write let player1 = // a known Player who is player #1 in this Game let player2 = // a known Player who is player #2 in this Game let newGame = Game(context: managedObjectContext) // you could use player1.managedObjectContext for the context newGame.addToPlayers(player1) // addToPlayers is added by XCode for the Game class newGame.addToPlayers(player2)	// and the Game is automatically added to each of the player's games // set other properties of newGame try? managedObjectContext.save() if you had a Player and wanted to look through all the Games played by that player, you could write let gamesPlayed = player.games as! Set<Game> // typecast needed to get from older Objective C type to Swift for game in gamesPlayed { 	// do what you need } when you need to look at the NSOrderedSet of Players in a Game, i think the reciprocal cast (to move from an NSOrderedSet to a Swift Array) is let players = game.players.array as! [Player] to see a list of all Games and all Players is a straightforward fetch from Core Data, which many do using @FetchRequest and List in SwiftUI. that might be enough to digest for now. hope that helps, DMG
Aug ’20
Reply to Update SwiftUI View on NSManagedObject edit
hi, interesting. the id's of the ExerciceHistory object as an Identifiable are not changing when an object has a content change; but .id: \.self does come back as a different value when an item is edited, since it's basically a hash value of the object. i would think it enough to write only ForEach(training.exerciceHistories.array as! [ExerciceHistory], id: \.self) { exerciceHistory in 	ExerciceHistoryRow(exerciceHistory: exerciceHistory) 	.padding(.bottom, 10) } without adding the extra .id(UUID) at the end. i think the extra UUID would cause all rows of the list to be redrawn, not just the one row that changed. if fact, seeing what you've written, some testing i was doing elsewhere suggests you could write this as well: ForEach(training.exerciceHistories.array as! [ExerciceHistory]) { exerciceHistory in 	ExerciceHistoryRow(exerciceHistory: exerciceHistory) 		.id(exerciceHistory.hashValue) 		.padding(.bottom, 10) } thanks for the idea, DMG
Aug ’20