Posts

Post marked as solved
1 Replies
So this wasn't as difficult as it first seemed... As it happened, all that I was required to do to solve this problem was remove the renaming identifier for all model versions subsequent to my model version 8. To be clear, I retained the renaming ID in the model version 8 to change the relationship name from 'foodServes' (v7) to 'foodPortions' (v8), then for every model version 9 onwards (to v13), deleted the renaming ID. New Entity created so I'm happy and no migration errors so CloudKit is happy.
Post not yet marked as solved
1 Replies
Here is my answer... Two alternatives: Iterate over the tableSelection, then filter the NSSet where the entity.id equals the tableSelection ID (there will be only one), then iterate over the resulting array of NSManagedObject. Filter the NSSet where the entity.id is in the tableSelection, then iterate over the resulting array of NSManagedObject. Example for option 1: @State private var tableSelection = Set<Action.ID>() ... if let eventActions = event.actions { for actionID in tableSelection { let eventActionsSelected = eventActions.filter { ($0 as! Action).id == actionID } as! [NSManagedObject] for action in eventActionsSelected { // perform some action... e.g. context?.delete(action) } } } Example for option 2: @State private var tableSelection = Set<Action.ID>() ... if let eventActions = event.actions { let eventActionsSelected = eventActions.filter { tableSelection.contains(($0 as! Action).id) } as! [NSManagedObject] for action in eventActionsSelected { // perform some action... e.g. context?.delete(action) } } I prefer Option 2. While there is a bit too much force downcasting for my liking, at least both options work and from here I can improve.
Post not yet marked as solved
4 Replies
to provide some additional context to my original post... Here is another enum for which I can access a raw value for an enum, but it is not pure - it has an associated type NSNumber. The significant difference to this enum is that is has an associated type NSNumber and so I can set a typealias RawValue = NSNumber. enum EntityDateFormat: NSNumber, CaseIterable, Identifiable {     typealias RawValue = NSNumber     var id: NSNumber? { self.rawValue }     case dateTime = 0     case dateOnly     case monthYear     case yearOnly     var template: String {         switch self {         case .dateTime:     return Date.templateDDMYHM // e.g. under extension to Date, static let templateDDMYHM: String! = "EEE dd MMM yyyy hh mm"         case .dateOnly:     return Date.templateDDMY         case .monthYear:    return Date.templateMY         case .yearOnly:     return Date.templateY         }     }     var calendarComponent: Calendar.Component {         switch self {       case .dateTime:     return .minute         case .dateOnly:     return .day         case .monthYear:    return .month         case .yearOnly:     return .year         }     }     var title: String {         switch self {         case .dateTime:     return "Date & Time"         case .dateOnly:     return "Date Only"         case .monthYear:    return "Month & Year"         case .yearOnly:     return "Year Only"         }     }     var prefix: String {         switch self {         case .dateTime:     return "At"         case .dateOnly:     return "On"         case .monthYear:    return "In"         case .yearOnly:     return "In"         }     } } Then I use a function to obtain a formatted string, but I use a parameter format of type NSNumber to reference against the enum and obtain a raw value. struct EntityDate {     private let messageError: String = "Error formatting EntityDate"     func from(_ date: Date, withFormat format: NSNumber) -> String {         guard             let calendarComponent = EntityDateFormat(rawValue: format)?.calendarComponent,             let template = EntityDateFormat(rawValue: format)?.template,             let prefix = EntityDateFormat(rawValue: format)?.prefix             else {                 return messageError         }         let dateRounded = date.roundDown(to: calendarComponent)         let dateFormatted = dateRounded.formatAsStringUsing(template: template, forLocale: Locale.current)         return String("\(prefix) \(dateFormatted)")     } Where .roundDown(to:) and formatAsStringUsing(template:, forLocale:) are custom functions under an extension to Date. So as you can see in this last func, rather than having to run the value for the parameter format through a switch statement to obtain the relevant enum value, (using EntityDateFormat(rawValue: format)?) I have three single lines in a guard statement to determine each value I require for my custom Date functions.
Post not yet marked as solved
2 Replies
I learned almost all I know about SwiftUI Bindings (with Core Data) by reading a blog by Jim Dovey on Core Data Bindings (do a Google search - its worth it). The remainder is a combination of some research and quite a few hours of making mistakes. So when I use Jim's technique to create Extensions on SwiftUI Binding then we end up with something like this... public extension Binding where Value: Equatable { init(_ source: Binding<Value>, deselectTo value: Value) { self.init(get: { source.wrappedValue }, set: { source.wrappedValue = $0 == source.wrappedValue ? value : $0 } ) } } Which can then be used throughout your code like this... Picker("country", selection: Binding($selection, deselectTo: nil)) { ... } OR Picker("country", selection: Binding($selection, deselectTo: someOtherValue)) { ... } OR when using .pickerStyle(.segmented) Picker("country", selection: Binding($selection, deselectTo: -1)) { ... } which sets the index of the segmented style picker to -1 as per the documentation for UISegmentedControl and selectedSegmentIndex. The default value is noSegment (no segment selected) until the user touches a segment. Set this property to -1 to turn off the current selection.
Post not yet marked as solved
2 Replies
Using the very helpful answer by @nocsi I have the following working solutions... List { ForEach(gardens) { section in     DisclosureGroup(section.id, content: {         ForEach(section) { garden in             NavigationLink(destination: GardenDetail(...),                            tag: garden.uuID!.uuidString,                            selection: $appData.selectedGardens             ) {                 GardenRow(garden: garden,                           selected: garden.uuID?.uuidString == appData.selectedGardens)             }         }     }) } } OR List { ForEach(gardens) { section in     DisclosureGroup(section.id, content: {         ForEach(section) { garden in             NavigationLink(destination: GardenDetail(...),                            tag: garden.uuID!.uuidString,                            selection: $appData.selectedGardens             ) {                 GardenRow(garden: garden,                           selected: garden.uuID?.uuidString == appData.selectedGardens)             }         } }, label: { Text(section.id)     }) } } However this solution... provides only a top level disclosure group, not the cascading outline group style that I was hoping to achieve; does not provide section headers for iOS and macOS; in my humble opinion, does not work as well as the disclosure indicators for iOS and sizeClassHorizontal == .compact that are automatically provided for each section.
Post not yet marked as solved
3 Replies
I cannot make @SectionedFetchRequest work with a SortDescriptor where the value is obtained from traversing a Core Data entity relationship. Your example: item.name In fact in Xcode Version 13.1 the compiler presents me with the particularly informative "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions" and subsequently my project/s will not build. SO this is how I get around the issue (using your example)... I add a new attribute named sectionIdentifier of type String or Integer 64 optional (by default) and if Integer 64 then not scalar to the Core Data entity, in your case the entity named Attribute. With Codegen set to Class Definition, Core Data framework automatically creates an NSManagedObject with property type String? or NSNumber? respectively. Wherever the data for an instance of Attribute is entered by the user (or you), add the following .onChange modifier. (where attribute is defined by @ObservedObject var attribute: Attribute) .onChange(of: attribute.item) { changed in // or because we're not using changed, then { _ in     if let item = attribute.item {         let itemOrder = String(format: "%02d", item.order) // if you set sectionIdentifier type as String, OR         let itemOrder = item.order // if you set sectionIdentifier type as Integer 64         attribute.sectionIdentifier = itemOrder     }     else {         attribute.sectionIdentifier = "0" // if you set sectionIdentifier type as String, OR         attribute.sectionIdentifier = 0 // if you set sectionIdentifier type as Integer 64     } } Change a @SectionedFetchRequest to the following (specifically note the first SortDescriptor). @SectionedFetchRequest(sectionIdentifier: \.sectionName,                     sortDescriptors: [                         SortDescriptor(\.sectionIdentifier, order: .reverse),                          SortDescriptor(\.order, order: .reverse)],                      animation: .default ) var attributes: SectionedFetchResults<String, Attribute> This should produce a dynamically changing SectionedFetchResults for var attributes and subsequently update any List or similar view whenever order changes for an instance of Item.
Post not yet marked as solved
2 Replies
Ironically it’s not the code for Table() that was the issue. Table is itself a ScrollView At the time of writing this, Table does not function when it is embedded into another ScrollView. So, all I needed to make Table structure function as expected, is to remove it from the ScrollView! Many thanks to Apple DTS for solving this.
Post marked as solved
2 Replies
IGNORE MY PREVIOUS ANSWER!!! The following is based upon: a really good answer in StackOverflow to this question "SwiftUI View and @FetchRequest predicate with variable that can change", please refer https://stackoverflow.com/a/64200159/1883707. a well written blog "Swift with Majid" specifically this article https://swiftwithmajid.com/2020/01/22/optimizing-views-in-swiftui-using-equatableview/ and in particular his explanation of "Diffing in SwiftUI". This is the correct answer for two main reasons that I can currently identify... This pattern breaks up the larger view into smaller views, which allows SwiftUI to better render and re-render. This pattern breaks out the properties that are used in the SwiftUI diffing algorithm (as noted in the article by Majid) and therefore the fetch request calls are minimised and the predicate that I need for the @AppStorage property is injected into the child view. (I still can't quite get my head entirely around this, but the pattern works perfectly. If you can better explain it, I'd be grateful for an answer or comment.) So here is the code... struct Accounts: View {     @AppStorage("preference_displayArchived") var kDisplayArchived = true     var body: some View {         AccountsView(displayArchived: kDisplayArchived)     } } struct AccountsView: View {     let displayArchived: Bool     var body: some View {         AccountsList(accounts: SectionedFetchRequest(sectionIdentifier: \.sectionTypeName,                                                      sortDescriptors: [                                                         SortDescriptor(\.type?.name, order: .forward),                                                         SortDescriptor(\.sortOrder, order: .forward)                                                      ],                                                      predicate: displayArchived == true ? NSPredicate(value: true) : NSPredicate(format: "isArchived == %@", NSNumber(booleanLiteral: displayArchived)),                                                      animation: .default),                      displayArchived: displayArchived         )     } } struct AccountsList : View {     @SectionedFetchRequest var accounts: SectionedFetchResults<String, PTG_Account>     let displayArchived: Bool     @State private var searchText = String()     var query: Binding<String> {         Binding {             searchText         } set: { newValue in             searchText = newValue             let predicate01 = NSPredicate(format: "nameTensePresent CONTAINS[cd] %@", newValue)             let predicate02 = NSPredicate(format: "nameTensePast CONTAINS[cd] %@", newValue)             let predicateArchived = displayArchived ? NSPredicate(value: true) : NSPredicate(format: "isArchived == %@", NSNumber(booleanLiteral: displayArchived))             let predicateOr = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate01, predicate02])             let predicateAll = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateOr, predicateArchived])             accounts.nsPredicate = newValue.isEmpty ? predicateArchived : predicateAll         }     }     var title: String {         return "Title For Your View"     }     var body: some View {         VStack(spacing: 0) {             ZStack(alignment: .bottom) {                 ScrollViewReader { proxy in                     List { ...                     }                     .onChange(of: displayArchived) { _ in                         searchText = String()                     }                 }                 ListFooter(countListRows: accounts.reduce(0, {$0 + $1.count}))             }         }         .searchable(text: query)         .navigationTitle(title)         .toolbar {             ToolbarItem(placement: .primaryAction) {                 ...             }         }     } } Where @AppStorage("preference_displayArchived") var kDisplayArchived = true is the user setting to display archived files (in this case, in the Account List()). PTG_Account is the class name for a core data entity Account. .isArchived is the entity attribute of type Bool that is used to archive or unarchive an entity record (in this case, for the entity Account).
Post marked as solved
2 Replies
I'm not that pleased with this solution but it works (on iOS only) and until I can find a more elegant method (that also works on macOS), this will have to suffice! Noting that... @AppStorage("preference_displayArchived") var kDisplayArchived = true Using the same call to @SectionedFetchRequest in the question, I need to complete three tasks... 1 - Update the List when it appears and when the value to the preference changes to include a predicate that will effectively filter the SectionedFetchResults (in this case SectionedFetchResults<String, PTG_Event>):    var body: some View { List() { .... }         .onAppear() {         events.nsPredicate = predicateArchived         }        .onChange(of: kDisplayArchived) { _ in             events.nsPredicate = predicateArchived        } } 2 - Add a computer property for predicateArchived:     var predicateArchived: NSPredicate {         kDisplayArchived == true ? NSPredicate(value: true) : NSPredicate(format: "isArchived == %@", NSNumber(booleanLiteral: kDisplayArchived))     } 3 - Finally I also have to update the search to ensure that this app preference is adopted during the search. Building on the code presented to us in WWDC21-10017 (in presentation skip to time 21:29):     @State private var searchText = String()     var query: Binding<String> {         Binding {             searchText         } set: { newValue in             searchText = newValue             let predicate01 = NSPredicate(format: "name CONTAINS[cd] %@", newValue)             let predicate02 = NSPredicate(format: "reference CONTAINS[cd] %@", newValue)             let predicateArchived = kDisplayArchived == true ? NSPredicate(value: true) : NSPredicate(format: "isArchived == %@", NSNumber(booleanLiteral: kDisplayArchived))             let predicateOr = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate01, predicate02])             let predicateAll = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateArchived, predicateOr])             events.nsPredicate = newValue.isEmpty ? nil : predicateAll         }     } NOTES Known issue: after cancelling a search, the List refreshes without a predicate applied. Need to develop a workaround for this inconsistency.
Post not yet marked as solved
3 Replies
You've not really identified the context in which the error is appearing, however I can suggest that if it is relation to sorting, then you should replace KeyPathComparator with SortDescriptor. For example... this doesn't work...     @State var sortOrder: [KeyPathComparator<ModelObject>] = [         .init(\.dateCommences, order: SortOrder.forward)     ] ... however this does...     @State var sortOrder: [SortDescriptor<ModelObject>] = [         .init(\.dateCommences, order: SortOrder.forward)     ] where dateCommences is an example property of ModelObject.
Post marked as solved
3 Replies
The problem made me realise that I was force unwrapping an Optional, something I knew but didn't fully comprehend in this context. So the entity attribute sortOrder is of type "Integer 64" and checks "Use Scalar Type". On that last point, I want to work with Int64 in my code, not NSNumber. Albeit that the UI isn't perfect, this works...     TextField("Sort Order",               text: Binding(                 get: { String(account.sortOrder) },                 set: { account.sortOrder = Int64($0.filter{ "0123456789".contains($0)}) ?? 0 }               )     ) I removed the force unwrap and instead "Coalesce using '??' to provide a default when the optional value contains 'nil'". This doesn't work...                 set: { account.sortOrder = Int64($0.filter{ "0123456789".contains($0)}) ?? nil } ... because "'nil' is not compatible with expected argument type 'Int64'". When I traverse my way into the automatically generated property file for the Account entity, I find this line of code for sortOrder...     @NSManaged public var sortOrder: Int64 This file cannot edited so, using CodeGen = Class Definition and "Use Scalar Type", I am stuck with Int64. So I guess I need to revisit my understanding of scalar types and how they work with Core Data CodeGen = Class Definition. It would seem that even though I check the "Optional" type option, the object graph and the automatically generated property file includes an Int64 - not Optional<Int64>. As noted above, in my Core Data object graph, I check the "Use Scalar Type" for the entity attribute sortOrder of type "Integer 64". I do this for a number of reasons that I will not go into here - mainly because I spent a lot of time researching this years ago and made a decision then but acknowledge it may be outdated now - so this will force me to review my use of this option in the object graph. FINALLY... Because CodeGen = Class Definition creates an automatically generated property in my Account property file with an entity attribute of scalar type Int64 instead of the (non-scalar) NSNumber (standard use), I must assume that NumberFormatter requires a non-scalar type NSNumber to function. Sure enough, when I uncheck the "Use Scalar Type" checkbox, NumberFormatter works perfectly for both macOS and iOS targets.  Conclusion: NumberFormatter currently (14 Aug 2021) works only with NSNumber.
Post marked as solved
3 Replies
As hinted in the error message, you're attempting to cast the type ServiceCategory in a manner that SwiftUI cannot manage. The var sectionIdentifier is expecting a reference to a ServiceCategory as shown in this line ) var sectionedServices: SectionedFetchResults<ServiceCategory?, Service> but instead you are passing the faulted object ID _NSCoreDataTaggedObjectID per your line sectionIdentifier: \Service.serviceCategory, Service.serviceCategory holds a reference to an entity instance... in your situation the ServiceCategory entity. If you place a break point in your code and query the value you'll probably see a reference to a record id for that entity, not the actual entity object. Frankly I'd recommend that you change your approach a little write an extension on your Service entity and include a convenience method to grab this value... extension Service: NSManagedObject { @objc var serviceCategoryName: String {             return self.serviceCategory.name ?? "NO NAME" } } then use this as follows... @SectionedFetchRequest(     sectionIdentifier: \.serviceCategoryName,     sortDescriptors: [       SortDescriptor(\.active, order: .reverse),       SortDescriptor(\.displayText)     ],     predicate: NSPredicate(format: "%K = %d", #keyPath(.active), true),     animation: .default   ) var sectionedServices: SectionedFetchResults<String, Service> Note the change from type ServiceCategory to type String. and in the list... ForEach(sectionedServices) { section in Section(header: Text(section.id)) { ForEach(section) { service in Text(service.displayText ?? "") } } }
Post marked as solved
3 Replies
Similar issue with Xcode 12 and target macOS 11.x Universal app written in SwiftUI. Suspect the cause of the issue was my late interruption of a build and run command. Not at all certain about this however. Searched A LOT but no other suggestions worked, including deleting keychain items and build folders and creating new certificates and provisioning profiles. The only solution that worked for me was to let Xcode automatically manage signing and to add the script detailed in the answer by @IndustrialDesigner PS for clarity the script was added for the macOS target under "Build Phases" by clicking the + button and selecting "New Run Script Phase", then copying the script into the black script "box" under the Shell/bin/sh line.
Post marked as solved
34 Replies
@spellcasterdragonborn Hah, Apple uses the underscore to format italic text, so when I type your name in here the underscores cause formatting chaos. Yes, at this current moment in time, I agree with you - there is no need to use @StateObject. So to be clear, at this current moment in time, I understand that ALL WE NEED to access Core Data features throughout our SwiftUI App is an NSManagedObjectContext. To answer your question the long way round... Apple does not let us edit our answers so when I wrote my original answer, this was all fairly new to me. If I could change the answer - as I have in my SwiftUI universal apps that use Core Data - I would change the following...     lazy var persistentContainer: NSPersistentContainer = {...} Change to: &#9;&#9;private lazy var persistentContainer: NSPersistentContainer = {...} My reason is that there are only three properties that I believe should be publicly available in my class implementation of PersistentStore... static let shared, ...and through this one line singleton... var context, to use throughout the app; and func save, for convenience. If you check Apple's response to my forum question with the abstract title "New toolbar appearance in macOS 11", - https://developer.apple.com/forums/thread/650524 you can see their advice "and then your SwiftUI App could hold on to an instance of that (ed. custom) class in a variable." In their response, they also suggest I should watch the SwiftUI Data Essentials - https://developer.apple.com/videos/play/wwdc2020/10040/ WWDC session for inspiration. From my multiple viewings of this session, at the time I deducted that the hint was to use an @StateObject for my PersistentStore class, which is why I included it in my Core Data SwiftUI Xcode projects and in my original answer. It also made it easy for me to call save from the .onChange(of: scenePhase) view modifier. In hindsight and following review of the template that Apple includes in more recent Xcode 12 betas, I was complicating their hint. (As per Apple's beta Core Data template) the instance of that persistent container custom class could be as simple as...     let persistenceController = PersistenceController.shared or, per my previous answer...     let persistentStore = PersistentStore.shared However, at this current moment in time, I would remove the @StateObject instance of my PersistentStore class. Not that I have done that yet in my SwiftUI projects (no reason, just not got around to it). It seems important for me to add subtext here - I am still developing an understanding of how @StateObject property wrapper operates in the App lifecycle, so in the future and with a better understanding, I might change this decision. (PS: I just commented out @StateObject private var persistentStore = PersistentStore.shared in one project as a test and my iOS and macOS targets appear to build and run without issue. There seems to be a minor drop in memory usage, but this is speculative without taking steps to measure it properly.)
Post marked as solved
3 Replies
@JimDovey your quick example is very helpful thank you. Any chance you might be able to explain how to include forward and backward navigation in your example. In func makeUIView(context: Context) -> WKWebView {} I have attempted to include... &#9;&#9;&#9;&#9;view.allowsBackForwardNavigationGestures = true Using your struct Display I can add buttons without too much trouble immediately beneath .navigationBarTitle...         .toolbar {             ToolbarItemGroup(placement: .navigation) {                 Button(action: { &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;// action to navigate back here                 }) {                     Image(systemName: "chevron.left")                 }                 Button(action: { &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;// action to navigate forward here                 }) {                     Image(systemName: "chevron.right")                 }             }         } It is the code that is required to actually communicate between the SwiftUI View and its WebView: UIViewRepresentable that I'm struggling with.