Post

Replies

Boosts

Views

Activity

Reply to Passing ObservableObject to View within NavigationView and editing it
hi, i'll offer three suggestions. there's no need (or there may be, but not in this example) for User to be a class. make it a struct and kill the ObservableObject protocol. struct User: Identifiable { let id = UUID() var name: String init(name: String) { self.name = name } } treat the Users object as a viewModel, and when a change is made to a user's name, provide a method for the Users object to do that. class Users: ObservableObject { @Published var users: [User] init() { self.users = [ User(name: "John Doe"), User(name: "Jane Doe") ] } func updateUsername(for user: User, to newName: String) { if let index = users.firstIndex(where: { $0.id == user.id }) { users[index].name = newName } } } and treat the UserDetailView to not be a live edit, but one where you have parameters users and user coming in; you offload the name you want to edit when the view comes on screen; and when you save the edit, ask the Users object to make that change for you. something like this (with the Save button now also dismissing the view). struct UserDetail: View { @Environment(\.presentationMode) var presentationMode var user: User var users: Users @State private var username: String = "" var body: some View { VStack { TextField("Enter Name", text: $username) Button(action: { self.users.updateUsername(for: self.user, to: self.username) self.presentationMode.wrappedValue.dismiss() }, label: { Text("Save") }) } .onAppear { self.username = self.user.name } } } that should do it. when the Users object updates the user's name in its array, your TestNavigationView will know about it. hope that helps, DMG
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’20
Reply to Updating SwiftUI Views when changing a property of a Core Data object
hi, first a question: you're using @FetchRequest to drive your list, yes? your objects are Identifiable, yes? a little code would be helpful in diagnosing the issue. and second, yeah, the ObservedObject thing will die if you delete the item it references. SwiftUI is still holding on to a reference to it because it's an ObservedObject. (i've been there!) you might be better off without @ObservedObject, and sometimes it simply depends on how you write the List code as to whether its view gets updated correctly. but @FetchRequest certainly does pick up property changes. looking forward to seeing more from you! hope that helps, DMG
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’20
Reply to Starting My First Project
hi, you asked: what are some good tutorials for Xcode 12 and SwiftUI.  take a look at the 100 Days of SwiftUI course on Paul Hudson's website (hackingwithswift.com). This is one of the best. And be sure to look through the entire website: it's a terrific resource, including the newest Xcode 12 SwiftUI features. live long and prosper, DMG
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’20
Reply to Update SwiftUI View on NSManagedObject edit
hi, i would stay away from trying to manage the NSOrderedSet yourself via replacement, and instead rely on using the accessors generated by XCode for your Training class. my guess is that these do the right thing with generating objectWillChange messages. if you want to add a new element to exerciceHistories, just use the accessor addToExerciceHistories(_ value: ExerciceHistory), assuming that ExerciceHistory is the class name of things in the exerciceHistories NSOrderedSet. same thing for deleting and reordering. even if just editing one, i would use an accessor -- maybe one of replaceExerciceHistories(at: with:), or removeFrom... and insertInto... as a fallback, if things are not updating in certain operations, you could explicitly call .objectWillChange.send() on a Training object and see if that does it for you. hope that helps, DMG
Topic: UI Frameworks SubTopic: SwiftUI Tags:
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
Topic: UI Frameworks SubTopic: SwiftUI Tags:
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, 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
Topic: UI Frameworks SubTopic: SwiftUI Tags:
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
Topic: UI Frameworks SubTopic: SwiftUI Tags:
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
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’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.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’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
Topic: Programming Languages SubTopic: Swift Tags:
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
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’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 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
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Oct ’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
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Nov ’20