Posts

Post marked as solved
1 Replies
hi, my suggested answers are: (1) appropriate time to deploy? see (2). (2) Should it be done before beta testing via TestFlight? yes, you must move to production before users can test via TestFlight. (3) does that mean I won't be able to reset the schema if testers discover critical bugs related to the data model? well, this one's more nuanced. as you know, once you are in production, that CloudKit schema only allows additive changes (add an attribute to an existing entity, or add a new entity). if there are problems in the database schema, there's no reason you cannot make any necessary additive changes, and then simply ignore values of entities and attributes that are no longer needed. for example, if you want to change a relationship from one-to-one to one-to-many, add a new relationship between the two entities; read the existing one-to-one relationship's value; and set the new one-to-many relationship. if you really want to keep the CloudKit container trimmed, you could take the time to remove all objects of a given entity type (the entity type would still be in the CloudKit schema; there just would be no objects of that type), and you could set attributes no longer in use in a given entity to some small space-consuming value (e.g., "" for a String, or nil for a Data). nullify any relationships no longer in use. i would suggest that you consider an explicit version number attribute to all the entities in your schema, with default value 1 (or whatever is your current core data model version). if you do make (additive) changes in the future, you can update the version number; and you can later test this in code to know what record type you're working with and what fields it has by first checking its version number, and then use appropriate logic to work with the object. the best reference on this might be to start with David Stites's WWDC2022 video named "Evolve Your Core Data Schema". the last third of this covers the CloudKit issues. finally, if you really do want to reconfigure the data model once it's in production, could you possibly define the new version of your core data model, which takes care of the local data store ... but then assign a new CloudKit container to your app? i don't know the answer to this one, but perhaps you won't need go there. hope that helps, DMG
Post not yet marked as solved
1 Replies
hi, my guess on this is that you never inserted either model into the modelContext; linking them together either by appending the film to the director's films, or setting the film's director reference would cause an issue. as to which you should do in linking objects together, it used to be simple in Core Data: you could do either (and the other direction would bee handled automatically). however, for at least the early betas and the official release of Xcode 15 and iOS 17, it appeared to me that you were better off writing newDirector.films?.append(film) than film.director = newDirector, because the former was more likely to work with observation. (that could be fixed by now). finally, setting self.director = director in a Film's init() is probably a bad thing to do; i'd want to insert a new Film into the modelContext before setting the director reference. hope that helps, DMG
Post marked as solved
2 Replies
hi, i don't think that SwiftData will do this automatically for you (there are aggregate functions available in Core Data for this, i think, but they are not there yet in SwiftData -- if they are, i'd like to know about that). in the meantime, yes, just go ahead and fetch all records, but limit what's fetched to only the integer property you're interested in. for example, i have an app concerning a model type Location and a model property position: Int and i want to find the maximum position value among all Locations. private func lastLocationPosition() -> Int? { var fetchDescriptor = FetchDescriptor<Location>() fetchDescriptor.propertiesToFetch = [\.position] // only the positions values come back fully realized do { let locations = try fetch<Location>(fetchDescriptor) return locations.map({ $0.position }).max() } catch let error { print("*** cannot fetch locations: \(error.localizedDescription)") return nil } } it would be easy to slightly modify this function to return a Location? that realizes this maximum value -- compute the maximum and just find the first object among the fetched locations where the maximum is achieved, returning it instead of an Int?). (all remaining properties of the Location returned will be faulted, so you can use the model object with no problem: any property values you want will be faulted in when needed.) hope that helps, DMG
Post not yet marked as solved
1 Replies
hi, the error message about "parameter of type 'FormStyleConfiguration'" does not give the precise cause of the issue. in short, error messages from the SwiftUI compiler can be confusing and simply not point you to the real issues, especially when you have lots of code defining the view where the nesting levels go pretty deep. in this case, commenting out the first Section will better point you to the problem that exists in the second section. indeed, the second Section in your DetailView starts with ForEach($clients.enclosures) { .... but you have not defined an enclosures property on Client, which should be a @Relationship, and Enclosure might want to have an inverse @Relationship property back to Client. hope that helps, DMG
Post marked as solved
8 Replies
hi, i'm getting something similar in behavior related to toolbar items for the keyboard, although it's not exactly the same. (i am using Xcode 15 official release, not a beta or the RC.) in my case, i have a view that can be shown either by navigating to it, or opening it in a sheet (wrapped inside a navigationStack). when the view is navigated to, the button i place on the keyboard (to dismiss the keyboard) works as expected. when the view is opened in a sheet (view is wrapped inside a navigationStack), the button does indeed appear, but it is disabled. i'll try to put together a minimal example to file a feedback, but i'll be curious to see if there are more reports. DMG
Post marked as solved
3 Replies
hi, your iteration is over the elements of a dictionary that has three key-value pairs: for (key ‚numbers) in interestingNumbers { // interestingNumbers is not an array dictionaries are not ordered; so one time you may get Fibonacci, Prime, Square ... another time it may come out in a different order. this is exactly the behavior that you're seeing. hope that helps, DMG
Post not yet marked as solved
2 Replies
hi, basically, you cannot use the properties of self on the right side of the assignments in the init() to initialize other properties of self ... it's a logical problem because self does not fully exist until all its properties are set. right now, you attempt to set self.predicate using the value of self.home; and you try to set _rooms using the values of self.sortBy and self.predicate. use a local variable or two, and rely on the incoming parameter as well: init(home: Home) { self.home = home let descriptor = SortDescriptor<Room>(\.name) _sortBy = State(initialValue: descriptor) let myPredicate = NSPredicate(format: "%K = %@", "home", home) // note use of incoming parameter home, not self.home self.predicate = myPredicate _rooms = FetchRequest<Room>(sortDescriptors: [descriptor], predicate: myPredicate) // note use of local variables here } hope that helps, DMG FWIW: i am curious as to why sortBy is marked as a @State property. this gives you the ability to change it, yes, but you'd also want to then also update the FetchRequest that relies on it.
Post not yet marked as solved
3 Replies
hi, when the delete function is called, the indices handed to the delete function are relative to the array indices in the section where the .onDelete modifier is attached ... for example, splitArray(schueler)[3] has chunkSize (or possibly fewer) elements with indices in the range 0 ... chunkSize - 1 ; yet your 'delete function removes elements in the main schueler array using those indices directly, without regard to which section triggered the deletion call. if you can pass the section/group number information along to your delete function, you can first adjust the incoming indices by section. for example, in the third group with a chunkSize of 7, adjust the incoming indices 0 ... 6 to instead be 14 ... 20, and then delete from the main schueler array using the adjusted indices. first, make a simple syntax modification to the .onDelete invocations in each section, so that your delete function will also receive section/group information. for example, in the third section: Section("Group 3") { ForEach(splitArray(schueler)[2], id: \.self) { SchuelerName in Text(SchuelerName) } .onDelete { indexSet in delete(indexSet: indexSet, inGroup: 3) } } update your delete function's signature to handle a second parameter, and begin by adjusting the incoming indices accordingly to determine matching indices in the main schueler array. then do the appropriate deletions. func indexSet in delete(indexSet: IndexSet, inGroup group: Int) { // this is your code to write ... i would think indices in the indexSet would have to be // offset by (group - 1) * chunkSize first before doing any deletions. } hope that helps, DMG
Post not yet marked as solved
2 Replies
hi Richard, i'll answer this from the Core Data perspective, where a list is driven with a @FetchRequest. from all that i've read, it should work the same with SwiftData, where a list is driven by @Query. my simple answer is: do nothing. in your "Add New Object view", the outcomes are: Cancel = dismiss and do nothing. Save = Create and save a new object to Core Data (SwiftData) and then dismiss. but do nothing else! the @FetchRequest (@Query) in your list view will pick up that a new object has been added, and redraw the list. if your list view wanted to use what was the new object created in your list view, then i would have the "Add New Object view" take a closure as a parameter with signature (NewObject) -> Void that is defined in your list view, and have the save routine invoke that closure. hope that helps, DMG
Post not yet marked as solved
2 Replies
hi, if you only need to count people within a certain age range, provide a predicate and use .fetchCount, rather than using .fetch and counting what comes back. no records are pulled for this and the operation should be pretty speedy. if you need to average out the ages of all people, you can specify the .propertiesToFetch in the FetchDescriptor to return only the age property values (not whole records) and then add and divide. at least that's what the documentation suggests right now (i am looking at beta 7, and Core Data already supports these). hope that helps, DMG
Post marked as solved
2 Replies
hi, the expression allClosingValues.map { [$0.timeStamp! , $0.vooClose] } results in something that would have data looking something like [ [a date, a double], [a date, a double], [a date, a double] , ... ] i.e., where each element of the array is a two-element array of a date and a double, which has type [Any], and so the expression's result has type [[Any]]. however, you have defined closingValues as a dictionary of type [Date:Double], with keys being dates and values being doubles. that explains the error message. if you're happy with a result of 2-tuples, where the result is more like [ (a date, a double), (a date, a double), (a date, a double), ... ] then you could use closingValues = allClosingValues.map { ($0.timeStamp! , $0.vooClose) } where you define var closingValues: [ (Date, Double) ] to print out such a result, this should work: for closingValue in closingValues { print(closingValue.0, closingValue.1) } if you really do want closingValues to be a dictionary, let us know. hope that helps, DMG
Post not yet marked as solved
3 Replies
hi, in many cases when you delete the Core Data object, SwiftUI may try to execute the body property of a view in which you reference the object as @ObservedObject. i recall checking the .isDeleted property and it did not always seem to return the right value ... but something that is true about the deleted object will be that all its attributes will be zeroed out. so, all optional properties will be nil, all numeric values will be 0, a date will be back in 1970 somewhere, all relationships will be nil, and so forth. if you have nil-coalesced all properties of the Core Data object, then everything should pretty much just work. for example: Text(myObject.name ?? "No Name") will not crash your app, but Text(myObject.name!) will certainly crash. i usually add an extension to a Core Data object so that every property is nil-coalesced. e.g., if i have a name_ attribute defined in the Core Data model, then i add var name: String { get { name_ ?? "No Name" } set { name_ = newValue } } as an extension to the entity, and now i can freely write myObject.name throughout SwiftUI views and not have to continually nil-coalesce everything when i use it. you might want to check out my "build and fail in public" ShoppingList16 app, which is where i found this problem back in iOS 13, and i have not had crashes with this strategy after finding out what was happening. there's plenty of commentary in the source code on this very point. hope that helps, DMG
Post marked as solved
3 Replies
hi SpaceMan, the best reference i can find on this is in the developer documentation on Reading CloudKit records for Core Data. this documents how Core Data is encoded in CloudKit. in short, String, Binary Data, and Transformable attributes can be automatically converted to external assets during serialization. it looks like this happens when you begin approaching 1 MB, but there seems to be no hard-and-fast rule. however, you can certainly work with the CKRecords directly and determine whether data is in the record or farmed out to a CKAsset, as described in the article above: "When inspecting a CloudKit record directly, check the length of the original field’s value; if it is zero, look in the asset field." hope that helps, DMG
Post marked as solved
3 Replies
hi, as long as you're OK with "I will never manipulate the CloudKit data directly," then i don't see what the problem is. i doubt that the NSPersistentCloudKitContainer designers ever intended for users to be interacting directly in code with CKRecords and making an end run around what NSPersistentCloudKitContainer does for you automatically. however, that aside, i do recall reading at one point that Core Data does not necessarily use external storage just because you checked "Allows External Storage." it could be the case that Core Data made such decisions about your data at some point; if so, the CloudKit representation of what NSPersistentCloudKitContainer does for you would probably respect that decision. hope that helps, DMG
Post not yet marked as solved
2 Replies
hi, each of TagViewPhone and WordView2 has its own WordViewModel. changing one does not change the other. you should create the WordViewModel as a @StateObject in MainView2, and make it available to the TagViewPhone and WordView2 subviews, either as a parameter (reference the model passed in as an @ObservedObject) or through the environment. should you still have updating problems ... my guess is that you will ... then you may want to explicitly precede any change to a Word or Tag by telling all associated objects (Tags associated with a Word, or Word associated a Tag) to initiate an objectWillChange.send() message. changing attributes of a Tag/Word at one end of a relationship is not seen as a change of any attributes on the other Word/Tag end of the relationship. hope that helps, DMG