Hey everyone,
I'm trying to get the user's name to display in a welcome screen, but unfortunatelly no success so far.
For that, I'm using CKContainer.default().shareParticipant(forUserRecordID: recordID).userIdentity.nameComponents, but the returned nameComponents are empty, despite receiving no error and accountStatus of .available.
Here's my code:
struct Helper {
static func getUserInformation() async throws -> Models.UserInfo {
let container = CKContainer.default()
let accountStatus = try! await container.accountStatus()
var accountStatusDescription = ""
switch accountStatus {
case .couldNotDetermine:
accountStatusDescription = "couldNotDetermine"
case .available:
accountStatusDescription = "available"
case .restricted:
accountStatusDescription = "restricted"
case .noAccount:
accountStatusDescription = "noAccount"
case .temporarilyUnavailable:
accountStatusDescription = "temporarilyUnavailable"
@unknown default:
accountStatusDescription = "default"
}
print("[Helper] CKContainer accountStatus: \(accountStatusDescription) ")
// Prints "[Helper] CKContainer accountStatus: available"
do {
let recordID = try await container.userRecordID()
let id = recordID.recordName
let participant = try await container.shareParticipant(forUserRecordID: recordID)
guard let nameComponents = participant.userIdentity.nameComponents else {
throw Models.ServiceError.userIdentityUnknownName
}
print("[Helper] CKShare.Participant nameComponents \(nameComponents)")
// Prints "[Helper] CKShare.Participant nameComponents - "
print("[Helper] CKShare nameComponents.givenName \(String(describing: nameComponents.givenName))")
print("[Helper] CKShare nameComponents.nickname \(String(describing: nameComponents.nickname))")
print("[Helper] CKShare nameComponents.familyName \(String(describing: nameComponents.familyName))")
print("[Helper] CKShare nameComponents.namePrefix \(String(describing: nameComponents.namePrefix))")
print("[Helper] CKShare nameComponents.nameSuffix \(String(describing: nameComponents.nameSuffix))")
print("[Helper] CKShare nameComponents.middleName \(String(describing: nameComponents.middleName))")
let name = PersonNameComponentsFormatter().string(from: nameComponents)
return Models.UserInfo(
id: id,
name: name
)
} catch {
throw error
}
}
}
Other than that, this project is using CloudKit for persistence through SwiftData and everything seems to be duly setup and working fine.
Any idea of what I might be missing? Any user permissions required? As far as I understood, from iOS 17 on and using this code, no permissions are required anymore but I may be wrong.
Any hint / help would be much appreciated!
Cheers,
Jorge
CloudKit
RSS for tagStore structured app and user data in iCloud containers that can be shared by all users of your app using CloudKit.
Posts under CloudKit tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
I implemented to my app iCloud backup and restore services. Basically App uploads it's document directory files to iCloud using NSURL containerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; and [fileManager copyItemAtURL:fileURL toURL:destinationURL error:&error]; and everything has been OK to upload files. By checking my iCloud account, it has taken the space needed to store those files.
However, when I changed my decice, I'm unable to get those files back. At first, files had .icloud extra extension added, but now the function just reports only 5 files from few hunder. The function used to get backup files is:
`- (NSArray) getBackupFiles {
NSError *error;
NSURL *containerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (!containerURL) return nil;
// NSArray *keysArray = [[NSArray alloc] initWithObjects: nil];
NSArray *resultsArray = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:containerURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:&error];
if (error) {
[self myAlertView:error.localizedDescription];
return nil;
}
else return resultsArray;
}
Any ideas, what went wrong?`
I'm having some trouble with the following function from the CKSyncEngineDelegate protocol.
func nextRecordZoneChangeBatch(_ context: CKSyncEngine.SendChangesContext,
syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch? {
The sample code from the documentation is
func nextRecordZoneChangeBatch(
_ context: CKSyncEngine.SendChangesContext,
syncEngine: CKSyncEngine
) async -> CKSyncEngine.RecordZoneChangeBatch? {
// Get the pending record changes and filter by the context's scope.
let pendingChanges = syncEngine.state.pendingRecordZoneChanges
.filter { context.options.zoneIDs.contains($0) }
// Return a change batch that contains the corresponding materialized records.
return await CKSyncEngine.RecordZoneChangeBatch(
pendingChanges: pendingChanges) { self.recordFor(id: $0) }
}
init?(pendingChanges: [CKSyncEngine.PendingRecordZoneChange], recordProvider: (CKRecord.ID) -> (CKRecord?)) works fine for the sample app which only has one record type, but it seems incredible inefficient for my app which has a dozen different record types. The recordProvider gives you a CKRecord.ID, but not the CKRecord.RecordType. Searching each record type for a matching ID seems very inefficient.
Doesn't the CKSyncEngine.PendingRecordZoneChange contain an array of CKRecords, not just CKRecord.IDs? According to the documentation CKSyncEngine.RecordZoneChangeBatch has a recordsToSave property, but Xcode reports 'CKSyncEngine.PendingRecordZoneChange' has no member 'recordsToSave'
I'm looking for someway to get the CKRecords from syncEngine.state.pendingRecordZoneChanges.
I get this error even though everything is turned on, how can I solve it?
It works on IOS but I get this error on VisionOS
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _recoverFromPartialError:forStore:inMonitor:]_block_invoke(2726): <NSCloudKitMirroringDelegate: 0x600003b0c700>: Found unknown error as part of a partial failure: <CKError 0x600000cce460: "Permission Failure" (10/2007); server message = "Invalid bundle ID for container"; op = 7FE8CD52A7B2E8FC; uuid = D8D1F2C9-2C01-45B2-BECC-270CA5520D55; container ID = "iCloud.Multitools">
let previewContainer:ModelContainer = {
do {
let config = ModelConfiguration(cloudKitDatabase: .private("iCloud.Multitools"))
let container = try ModelContainer(for: NoteModel.self, configurations: config)
return container
}
catch {
fatalError("Error to create container")
}
}()
I want to make icloud backup using SwiftData in VisionOS and I need to use SwiftData first but I get the following error even though I do the following steps
I followed the steps below
I created a Model
import Foundation
import SwiftData
@Model
class NoteModel {
@Attribute(.unique) var id: UUID
var date:Date
var title:String
var text:String
init(id: UUID = UUID(), date: Date, title: String, text: String) {
self.id = id
self.date = date
self.title = title
self.text = text
}
}
I added modelContainer
WindowGroup(content: {
NoteView()
})
.modelContainer(for: [NoteModel.self])
And I'm making inserts to test
import SwiftUI
import SwiftData
struct NoteView: View {
@Environment(\.modelContext) private var context
var body: some View {
Button(action: { // new Note
let note = NoteModel(date: Date(), title: "New Note", text: "")
context.insert(note)
}, label: {
Image(systemName: "note.text.badge.plus")
.font(.system(size: 24))
.frame(width: 30, height: 30)
.padding(12)
.background(
RoundedRectangle(cornerRadius: 50)
.foregroundStyle(.black.opacity(0.2))
)
})
.buttonStyle(.plain)
.hoverEffectDisabled(true)
}
}
#Preview {
NoteView().modelContainer(for: [NoteModel.self])
}
I remember long time ago I could click on a button in Xcode to launch a Web page to manage iCloud data. But now I cannot find the button.
I am checking the Push Notification Console of Cloudkit to understand my apps push delivery metrics. There are nice metrics in there to see push sends and discards. But I couldn't see any metric to understand "How many of the users opened the notiifications I send". Is there any way me to understand this statistic?
Also can we export this metrics without logging in to CloudKit like an API request?
Identify and resolve synchronization issues when working with NSPersistentCloudKitContainer.
View Technote TN3164 >
Explore the details inside the synchronization of NSPersistentCloudKitContainer by capturing and analyzing a sysdiagnose.
View Technote TN3163 >
Learn how to identify and handle CloudKit throttles.
View Technote TN3162 >
My apps are set up to store data in a SQLite database on the device. The user is also able to add images and those are also stored on the device. The database and images are stored in the apps documents folder. The database is set up with four tables, one of them containing a list of selectable items so the information in that table is constant. The other three are read/write to the user. The database also contains a field, which contains true/false as to whether the app has been purchased or not.
My thought behind was that this would make the users data private and secure.
My apps are set up using UIKit so SwiftData is not an option unless I rewrite the entire app in SwiftUI. Or is there a good way to use SwiftData in UIKit?
Is there a way to store/move this information into the cloud so that the data can be synced across multiple devices?
Or maybe set up an import/export scenario using a CSV file for the database using Dropbox?
Any help or advice would be appreciated.
Thanks in advance.
I have a shopping list app that shares a list of Product records between users. The app on my phone shared its private database with another user account (running on a simulator) which accepted it. Both devices can see changes from the other if the device does a full refresh from iCloud. Now I want to have the app on the devices be notified when the other changes/adds/deletes a Product record. I did the following to get push notifications to work (but haven't been successful yet)
I have enabled background fetch and remote notifications in my app capabilities.
The app registers for remote notifications with UIApplication.shared.registerForRemoteNotifications() and receives the didRegisterForRemoteNotificationsWithDeviceToken callback.
The app sets a subscription with CKModifySubscriptionsOperation for the "Product" recordType. I set the QoS to .utility on the appropriate private or shared database.
However, when I add a Product record on my record, the didReceiveRemoteNotification callback doesn't execute.
What am I missing?
I have created a simple app where a user is tracking their mood. A simple click of the button inserts the necessary data to a CloudKit database. So I consider each record as a 'transactional' record.
I am able to successfully query the data and present in a list view. I am able to sort and incorporate simple predicates.
I am now at a point in my development that I would like to add a pie chart based on the users data and I am not sure how to roll-up the data / group by the data / aggregate the data [I am not sure what the correct terminology is within Swift]
The pie chart would show the various moods that the exists in the CloudKit database and the slices would be sized based on the count of each mood.
Any guidance that you can provide would be greatly helpful!
I'm currently facing an interesting issue. A customer is reporting back that my app is crashing on launch for them. I can see the crash logs (using AppCenter for crash management), and the reason the app is crashing seems to be in CoreData:
(Attaching text for seachability, and screenshot for readability):
libsystem_platform.dylib
_platform_memmove$VARIANT$Haswell
CoreData
-[_PFExternalReferenceData initForExternalLocation:safeguardLocation:data:protectionLevel:]
CoreData
-[NSSQLSavePlan _populateRow:fromObject:timestamp:inserted:shouldAddToRowCache:]
CoreData
-[NSSQLSavePlan _createRowsForSave]
CoreData
-[NSSQLSaveChangesRequestContext executePrologue]
CoreData
-[NSSQLCore dispatchRequest:withRetries:]
CoreData
-[NSSQLCore executeRequest:withContext:error:]
CoreData
-[NSPersistentStoreCoordinator executeRequest:withContext:error:]
CoreData
-[NSPersistentStoreCoordinator _routeHeavyweightBlock:]
CoreData
-[NSPersistentStoreCoordinator executeRequest:withContext:error:]
CoreData
-[NSManagedObjectContext save:]
I have never seen that crash before, it's the first time it is appearing since CoreData has been incorporated into the app (in 2016 or so). Any hints on what could possibly be happening? I am unable to provoke the crash on my end, thus debugging is quite interesting.
When I add or delete data of my entity type I can see the updates (between the simulator and the phone) but when i edit I dont see the updates although table view is reloaded. The console says
Ignoring remote change notification because it didn't change any entities tracked by persistent history
When I rebuild the app for both device and simulator I see it reflecting the most current changes.
Any help?
Neerav
I have iCloud sync working in the simulator, where I signed in with my Apple ID on two simulators and it syncs fine. But when I upload the same build to TestFlight and install it on two of my physical devices, it does not sync. Each device behaves as if they only have a local store, eg. each can save and load fine locally. I already tried pushing my schema to production, but it still does not work, even when given 24 hours to see if it would sync. I have also tried restarting both devices and deleting and re-installing the app on both devices. I have also confirmed that iCloud is turned on in the settings app for my app.
I saw this post on Stackoverflow, which seems to be related to my issue, but the suggestions there did not work either. I also saw this post:
https://www.hackingwithswift.com/forums/swiftui/swiftui-app-failing-to-sync-cloudkit-data-but-only-in-testflight-version/10714
However I did not understand the accepted answer, and none of the other suggestions worked either.
I need to track user actions, for example video view count. Then the data is used to get most popular videos for last 7 days, 30 days and for a year. For this purpose I have created a Downloads table with timestamp and video fields.
Each time user opens the catalog, I'm running queries to get Downloads and sort the videos based on them. This is a working, but not the efficient solution. A good option is to add aggregated data table storing summary counts for the popular queries - countFor7Days, etc. This will improve query performance. But it requires a job that would update the aggregate table every day.
The question is how to implement this job in CloudKit? Is there are such built-in feature, or I need a custom service running somewhere?
I'm experiencing an unresponsive UI since MacOS 14.0 and iOS 17.0 when calling record(for: ) or recordID(for:) on the instance of NSPersistentCloudKitContainer.
On MacOS, the UI freeze almost always happens when calling the function.
On iOS, it is necessary that the device (or simulator) does not have any network connection.
I would like to ask if anyone experienced the same problem. I have posted the problem twice to Apple via the Feedback app (once for iOS and once for MacOS). No reply yet on MacOS but on iOS Apple marked it as resolved because apparently no one but me has experienced this problem.
In the meantime, I have set up a minimum reproducible example app (MRE).:
https://github.com/DominikButz/NotesApp-Cloud-Kit-Record-UI-Freeze-
Anyone interested, please read the readme of the repository. It includes step by step instructions on how to reproduce the bug.
I can't rule out I have misunderstood the usage of CoreData and CloudKit - in that case please point me in the right direction.
The app I'm working on should also work offline (and on MacOS!) but it doesn't do so properly as long as this bug exists.
I'm exploring switching to SwiftData (which would mean no one using macOS 13 / iOS 16 can use my app...) but I would still need to access cloud kit records even with SwiftData and I fear the bug also exists in SwiftData.
Thanks
In Core data public configuration, added new attribute to entities, new entities, but the changes are neither synchronized nor data is transferred to existing container schema in cloudkit.
private var _publicPersistentStore: NSPersistentStore?
var publicPersistentStore: NSPersistentStore {
return _publicPersistentStore!
}
private var _privatePersistentStore: NSPersistentStore?
var privatePersistentStore: NSPersistentStore {
return _privatePersistentStore!
}
private var _sharedPersistentStore: NSPersistentStore?
var sharedPersistentStore: NSPersistentStore {
return _sharedPersistentStore!
}
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: “GS”)
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
guard let defaultDescription = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): failed to retrieve a persistent store description.")
}
let containerIdentifier = defaultDescription.cloudKitContainerOptions!.containerIdentifier
print(containerIdentifier)
print(defaultDescription.url as Any)
let url = defaultDescription.url?.deletingLastPathComponent()
print(url as Any)
// Public
let publicDescription = NSPersistentStoreDescription(url: url!.appendingPathComponent("public.sqlite"))
publicDescription.configuration = "Public"
print(publicDescription.url)
let publicOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
publicOptions.databaseScope = .public
publicDescription.cloudKitContainerOptions = publicOptions
publicDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
publicDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
// Private
let privateDescription = NSPersistentStoreDescription(url: url!.appendingPathComponent("private.sqlite"))
privateDescription.configuration = "Private"
print(privateDescription.url)
let privateOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
privateOptions.databaseScope = .private
privateDescription.cloudKitContainerOptions = privateOptions
privateDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
privateDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
// Shared
guard let sharedDescription = privateDescription.copy() as? NSPersistentStoreDescription else {
fatalError("#\(#function): Copying the private store description returned an unexpected value.")
}
sharedDescription.url = url!.appendingPathComponent("shared.sqlite")
print(sharedDescription.url)
sharedDescription.configuration = "Shared"
let sharedOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
sharedOptions.databaseScope = .shared
sharedDescription.cloudKitContainerOptions = sharedOptions
sharedDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
sharedDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.persistentStoreDescriptions = [publicDescription, privateDescription, sharedDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
else {
if let cloudKitContainerOptions = storeDescription.cloudKitContainerOptions {
if #available(iOS 16.0, *) {
if .public == storeDescription.cloudKitContainerOptions?.databaseScope {
print("loaded public store")
// self._publicPersistentStore = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url!)
} else if .private == storeDescription.cloudKitContainerOptions?.databaseScope {
print("loaded private store")
//self._privatePersistentStore = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url!)
} else if .shared == storeDescription.cloudKitContainerOptions?.databaseScope {
print("loaded shared store")
//self._sharedPersistentStore = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url!)
}
} else {
// Fallback on earlier versions
}
}
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy // external changes trumping in-memory changes.
}
func save() {
let context = container.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Show some error here
print("save error")
}
}
}
}
Tried new container on cloudkit, problem persists. Working previously until I updated Xcode to 15.2 and iOs 16.2. Can you please tell me why coredata is not synchronized for public configuration.
How to save some data in SwiftData to CloudKit, and others on local device only?