Problem solved.
When compared to another project I’m working on, I noticed something missing from the “Signing & Capabilities” under macOS App Sandbox…
Once I checked Outgoing Connections (Client), Xcode created a new entitlements file (oddly named "AppNameDebug.entitlements") and added the setting "Outgoing Network Connections = YES".
Following my next build and run, sync to CloudKit databases worked perfectly.
Can’t figure why this wasn’t required before. I didn't uncheck this and the previous entitlements file did not contain this line item before.
All I can suggest is that I added an #if DEBUG macro to my project around the same time, so maybe this triggered the requirement?
#if DEBUG
do {
// Use the container to initialize the development schema.
try container.initializeCloudKitSchema(options: [])
} catch {
// Handle any errors.
}
#endif
But main problem solved.
If anyone has an explanation that would be appreciated.
Post
Replies
Boosts
Views
Activity
I've had this problem before and I'm currently experiencing this now.
My issue is that I neglected to update the Schema indexes for each new Entity to add (what seems to be necessary):
modifiedTimestamp - Queryable
modifiedTimestamp - Sortable
recordName - Queryable
Once I have added these indexes to each new Entity, my sync began working again between devices and iCloud.
Although recently, making the same mistake, my macOS instance no longer synced and I'm still trying to solve that problem.
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.
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.
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.
In response to comment by @Claude31, the location of the (second) error message...
let test = Taxon(rawValue: nameType)
where I am attempting to obtain a reference to one of the values in the enum Taxon.
For clarity, this replaces the commented out code - the above noted switch statement in the iteration over setSorted.
So eventually with an accessible initialiser, I might develop that line of code to replace the switch statement entirely and it might look something like this... let test = Taxon(rawValue: nameType).formatted(nameComp) although I am not sure that syntax is correct. Also for clarity, the first error message "Enum with raw type cannot have cases with arguments" is against the line that declares the enum Taxon: String {...} (when I attempt to associate a type with the enum).
@Claude31 good question... I can't update my original post so I'll add some more information below.
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.
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.
I'll be straightforward about this... honestly you have created A LOT more work for yourself than needed. Modern SwiftUI projects that use the Core Data framework can handle ALL the notifications for you when you set up your persistence controller properly and use @FetchRequest or @SectionedFetchRequest. Apple has some great tutorials now that make it easier to understand common coding patterns including how to set up a Core Data stack and add and delete rows in a list. My advice would be to start a new SwiftUI project with the Core Data option checked (and "Host In CloudKit" checked if you want to backup / sync to iCloud), then rebuild your project using that template.
Or is OutlineGroup the means to achieve this? Maybe there is a more suitable API?
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.
As an aside and in case I don't have time to provide an answer to your question, I humbly suggest that you should consider changing the entity name / your NSManagedObject subclass for Attribute to something that does not describe a part of a Core Data object graph.
Unless I'm mistaken, in your function you have declared the instance of I as Hashable, but not the Type. So maybe you should try this...
func validName<I:Hashable>(name: String, focusState: Binding<FocusState<I>>, fieldToFocusNext: I, fieldToFocusOnError: I, showError: Binding<Bool> ) -> Bool?
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.