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
Post
Replies
Boosts
Views
Activity
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
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
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
hi,
OOPer's request for specificity is important: unless you know what a List/ForEach structure will display in each row, you won't know what type of Array you would need to drive the display.
suppose, even, that you want a sectioned list using a List/ForEach/Section/ForEach construct, where each section corresponds to a SomeObject, and the rows of the section correspond to the ValueObjects associated with that SomeObject in the dictionary.
if you'd like to have the latter, sectioned-list setup, then it would be nice to have an array where each element is basically a SomeObject and [ValueObject] pair. that's not really that hard to do: something like this would work:
// a new struct to represent a key-value pairing in your dictionary
struct ListItem {
let someObject: SomeObject
let valueObjects: [ValueObject]
}
// make an array of ListItem structs, one for each item in the dictionary
// assumes that SomeObject is Hashable and Comparable
var myList = [ListItem]()
for key in myDictionary.keys.sorted() {
myList.append(ListItem(someObject: key, valueObjects: myDictionary[key]!))
}
i don't know what your object types actually are, but if SomeObject had a name (String) variable, and ValueObject had a value (Int) variable, then this is how you read what myList in the code above gives you:
for listItem in myList {
print(listItem.someObject.name)
for valueObject in listItem.valueObjects {
print(valueObject.value)
}
}
so this would match up with a sectioned list where you would use List/ForEach/Section/ForEach. indeed, it would look something like this:
List {
ForEach(myList, id: \.self) { listItem in
Section(header: Text(listItem.someObject.name)) {
ForEach(listItem.valueObjects, id: \.self) { valueObject in
// show data for the valueObject
}
}
}
}
although you'd have to be sure that ListItem is Hashable (it would be if both SomeObject and ValueObject were hashable).
hope that helps,
DMG
hi,
the function instantiateInitialViewController takes no argument because every storyboard already has an identified initialViewController, so there's no need to pass its identifier.
on the other hand, perhaps you might to use instantiateController which does let you specify withIdentifier if you want something other than the initial view controller.
hope that helps,
DMG
hi,
two things on syntax will make this compile (Xcode 11.6):
line 16: remove the msg: label ... there is no label for print().
lines 20-21: move these as modifiers onto the Text in line 18, inside the closing brace on line 19.
hope that helps,
DMG
hi,
sorry about my earlier attempt to post something ... i just wish you could delete these things!
your original post said that "The app has an array of people, and each person has an array of events associated with them." that doesn't seem to fit the DataStore you showed, which appears to keep independent lists of people and life events.
later you say that "On the Person Detail view there is a list of events. Tapping on an event takes you to an Event Detail view."
that's why i asked to see the list code in PersonDetailView. how do you associate life events to a particular person? is there a DataSource method to filter out events for a given person and is that used to build out the list in PersonDetailView?
so, it would be helpful if we saw the definition of Person and LifeEvent, whether each is a struct or a class, the relevant view code in PersonDetailView for the list, and the definition of the DataStore method updateLifeEvent(id: event.id, ....
i am truly interested in helping on this, since i have constantly run into view updates as you describe. even just yesterday, an app i was using just stopped doing all the visual updates in a situation like yours that had been working fine. it turned out that i had cleaned up some code and, for some reason, labeled a variable as @State when it was not labeled that way before. even though it seemed the logical thing to do, it turned out to be exactly the problem.
hope that helps,
DMG
Second Reply Withdrawn
i had something else to add and maybe correct in my earlier reply, but once i posted it, i realized it didn't make quite as much sense as i thought. but i cannot delete what i posted. i might try later.
hi,
i don't have an opinion on simulator/iPhone/iPad differences, but it would be useful to see the definition of DataStore. is it an ObservableObject? does it actually publish changes both on updatePerson() and updateLifeEvent() ? it could be that the former does publish changes, but maybe the second does not?
it's also curious that you have @State var person:Person in the EditPersonView, but only var person:Person in the LifeEventDetailView, so that the code is not exactly the same.
finally, it might also be useful to see the Person Detail view code, from which you jump into the EditPersonView and the LifeEventDetailView, and especially how the list of events is constructed.
hope i can help out,
DMG
hi,
when you pass a struct as an argument, you get a read-only copy of the struct as the argument, as if it were a let constant. so your (slimmed-down, let's avoid the SpriteNode stuff) code is basically this:
struct backGroundStruct
{
var w : CGFloat
var h : CGFloat
}
func heresJohnny (BackGroundStruct : backGroundStruct)
{
BackGroundStruct.h = 0.1
}
var wnh : backGroundStruct
wnh = backGroundStruct.init(w: 10, h: 10)
heresJohnny(BackGroundStruct : wnh)
and this is essentially what your function call is doing (which generates the same error trying to modify the argument):
var wnh : backGroundStruct
wnh = backGroundStruct.init(w: 10, h: 10)
let argument = wnh // a read-only copy to be passed
argument.h = 0.1 // what the function is doing in heresJohnny(BackGroundStruct : wnh)
the wnh variable would not be changed in the code above.
if you really want your function to work the way you thought it should -- modifying what is passed in -- you'd have to make it an inout argument and pass it as such:
func heresJohnny (BackGroundStruct : inout backGroundStruct)
{
BackGroundStruct.h = 0.1
}
var wnh : backGroundStruct
wnh = backGroundStruct.init(w: 10, h: 10)
heresJohnny(BackGroundStruct : &wnh)
the wnh variable has been changed in the code above.
one suggestion: by convention, Swift code usually is written with struct type names in UpperCamelCase and variables in lowerCamelCase. your backGroundStruct and BackGroundStruct name choices run somewhat counter to that convention.
hope that helps,
DMG
hi Oghweb,
after i posted my response, i did a quick edit after thinking about @State being used with a class -- but by the time i looked this up in my own code, it was too late to re-edit (posts close to editing after about ten minutes). sorry for the confusion.
i have two things for you:
i was right the first time in my code: @State should be used, not @ObservedObject.
be sure you have @State private var selectedEntry: Entry? SceneDelegate would only complain if you left off the private access control.
FWIW: i have a bunch of code i'm developing out in public that deals with a lot of what you're looking at. it's basically my SwiftUI playground. please feel free to take it and use what you can (or ask a question later about it); you'll find a lot of similarity. it's called ShoppingList - https://github.com/DelawareMathGuy/ShoppingList and you can find it on Github (be sure you find my project; don't be confused the two thousand other things on Github named with some combination or variation of the words "shopping" and "list").
hope that helps,
DMG
hi,
i think these ideas might help.
you don't want a .sheet modifier on each list item; but you do want a .sheet modifier (just one) probably attached to the List.
but to tie that .sheet to whichever list item is tapped, you need to off-load the tapped entry to a local variable to make the connection to the .sheet
a sheet does not inherit the managed object context of its parent. you need to add that explicitly with an .environment modifier..
so here's some proposed code for you (i haven't tested, but i have used this technique before). first add a variable to your View (it should be @State so you can write to it -- but on second thought, you may need to make this an @ObservedObject)
@State private var selectedEntry: Entry? // or @ObservedObject, since Entry is a class and not a struct ??
then rewrite your interior List code to be
List {
ForEach(entries, id: \.self) { entry in
Text(entry.name ?? "Unknown")
.onTapGesture {
isPresented = true
selectedEntry = entry
}
}
}
.sheet(isPresented: $isPresented) {
Detail(entry: selectedEntry!).environment(\.managedObjectContext, managedObjectContext)
}
you may need some self. qualifiers in the code above, if you are using Xcode 11; and your detail view can retrieve its managedObjectContext using @Environment(\.managedObjectContext).
hope that helps,
DMG
hi,
there are a number of strategies, depending on the types and how you want to manage storage.
(1) you can mark an attribute in Core Data as Transformable, and supply a ValueTransformer that turns a custom value (e.g., a color) into, say, Data or string/JSON. (i have not ever needed to create a custom ValueTransformer.)
(2) Core Data seems to know how to transform certain types of data without needing a custom ValueTransformer. An example would be an array of String, or an array of Int, or an array of UUID (all of which i have used in projects) -- and i assume an array of Double (as in the RGB components of a UIColor) would also work.
for an array of Double, you might define an attribute rgbComponents in Core Data's model editor, mark its type as Transformable, and then set the Transformer to NSSecureUnarchiveFromData and its Custom Class to [Double]. then, in code, you can simply refer to your entity's rgb components as rgbComponents[0], rgbComponents[1], and rgbComponents[2] (attach opacity as rgbComponents[3] if you want).
when you initialize one of your objects, assuming you might want the fourth opacity value included, set rgbComponents to some default, e.g., [0.9, 0.9, 0.1, 0.6], which is a nice soft yellow. or, you can set it to [] to mean "no color yet assigned," and then change it later. but be sure to provide some value upon initialization, otherwise Core Data will throw an error when your context does a save().
(3) if you want an array of custom "colors," i'd recommend just creating a new entity in Core Data named, say, "ColorDescription." it could have red, green, and blue (and opacity) Double attributes, perhaps with an identifying name (String). set a relationship from your object that is one-to-many to ColorDescription (perhaps named colors). you'll get an NSSet type for colors, and you can work with it by using the custom accessors generated by Xcode in the Core Data code and by doing something like this in your own code to get the right type of things to work with
func colorList() -> [ColorDescription] {
	// return a list of all my colors, sorted by name
	if let colors = colors as? Set<ColorDescription> {
		return colors.sorted(by: { $0.name! <= $1.name! }
	}
	return [] // it's doubtful this will happen -- you know what the type of the objects are
(4) sorry, i haven't done a Dictionary, yet, but it would be interesting to see if the same strategy as (2) above could work by setting the Custom Class to something like [String : Int].
hope that helps,
DMG
hi,KMT and Claude31 make good points. i'll add two others (BTW: i write code and i play a lot of golf)you should decide whether you'll go with UIKit or SwiftUI. CoreData would be easier to work with in SwiftUI than with UIKit (JSON data would be about the same -- you'd write your JSON file or files to the Documents directory).be sure you first test out recording on a physical scorecard what you think you'll want to enter into your phone while you're on the golf course. it will help you develop a good UI (e.g., if it takes you a long time to enter all your written data after the round, that's a lot of time you would have spent entering data during your round while your playing partners were waiting for you).FWIW: i did a little MapKit-based app some time ago, to learn about MapKit. it was a basic "punch the button, record my GPS location in a list, and show me the distance from the last location." i also could pull up a map to trace the path from location to location. (MapKit is more manageable in UIKit, although that may change with WWDC2020 next week.)it sounded interesting originally (and it has nothing specifically to do with golf), but measuring with GPS on the phone was never as good as measuring with my laser, so i stopped using it. and i kept forgetting to punch the button while i was on the course, since i was always focused on the next shot, not on the shot i just hit!good luck, keep us updated, and hope that helps,DMG