I’ve written an iOS app in SwiftUI (using the SwiftUI life cycle in Xcode 12) that uses my CloudKit container’s public database with the following lines of code:
let container = NSPersistentCloudKitContainer(name: "my_ios_container_name")
guard let description = container.persistentStoreDescriptions.first else {
print("Can't set description")
fatalError("Error")
}
description.cloudKitContainerOptions?.databaseScope = .public
I’m in the process of writing a macOS app in SwiftUI that accesses the same CloudKit container’s public database. But when I try to build the code below in my mac app's AppDelegate.swift file, Xcode gives me an error saying that it’s unable to find the ‘databaseScope’ member. Is this member variable only available for iOS apps? If this is the case, how do you access a Cloudkit container's public database in a macOS app?
Try adding "import CloudKit" to the top of the file.
Resolved this issue for me.
Related
I currently have an app using Core Data in the App Store: the app allows people to record their water and sailing activities (think of it like Strava for sailors). I have not updated the app for 3 years, the app seems to be still working fine on latest iOS versions but I recently planned to improve the app.
I am currently working on an update for this app, and need to change the data model and schema. I would like to have an automatic lightweight migration. I renamed some entities, properties and relationships, but I made sure to put the previous ids in the Renaming ID field in the editor.
I want to take advantage of the opportunity to sync the updated schema on CloudKit. I followed the instruction on Apple Developer documentation to setup the sync. I also made sure to initialize the schema using initializeCloudKitSchema(). When I visit the dashboard, I see the correct schema. The container is only in development mode, not pushed into production.
When I launch the app with a sqlite file generated by the available app, it seems the migration works well because the data is still here and correct. I can navigate in the app normally and when I visit the CloudKit dashboard, the data is correctly saved.
But sometimes, the app crashes at launch with the following error:
UserInfo={reason=CloudKit integration forbids renaming 'crewMembers' to 'sailors'.
Older devices can't process the new relationships.
NSUnderlyingException=CloudKit integration forbids renaming 'crewMembers' to 'sailors'.
Older devices can't process the new relationships.}}}
Fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration."
The concerned entities were renamed, as the relationships and the relationship is a many-to-many, optional on both sides. This is occurring even if I reset the CloudKit development container. I don’t really have a clear idea of when this is appearing (seems random, after I updated some data or after I update the Core Data model). Any idea why the app is crashing? I would like as much as possible to keep the new naming for my entities and relationships.
SKPRCrewMemberMO renamed to Sailor
SKPRTrackMO renamed to Activity
crewMembers <<--->> tracks renamed sailors <<--->> activities
Here are some screenshots of the previous and updated data model for the entity at the origin of the migration issue, as well as some code regarding my Core Data stack initialization and the console error il getting.
PS: the app is used by few hundreds of people. That’s not a lot, but still, some of them have dozens of recorded activities and I don’t want to break anything and lose or corrupt data. I could launch a new app but users would lose their progress as it’s only saved locally in a shared container (app group was used as I wanted to share the Core Data with an Apple Watch extension). And I would lose the user base and App Store related things.
private init() {
container = NSPersistentCloudKitContainer(name: "Skipper")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
let id = "iCloud.com.alepennec.sandbox20201013"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: id)
description.cloudKitContainerOptions = options
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
do {
try container.initializeCloudKitSchema()
} catch {
print("Unable to initialize CloudKit schema: \(error.localizedDescription)")
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
I am using PCLStorage to store image in localStorage and access them via path returned.
Its working fine but the problem is, whenever I again start debugging the application, images are not accessible.
Where actually it stores images in Local Storage? Is it not permanent location?
I want to store images in fileSystem and related data and image path in sqlite. Its an offline application so need to store this data permanently.
Any suggestions for this would be helpful.
Thanks
Try below steps.
The interface is fairly simple because we're really only interested in passing the byte array and a filename when we save the image to disk.
public interface IPicture
{
void SavePictureToDisk (string filename, byte[] imageData);
}
DependencyService will delegate image saving to the appropriate class. The usage for the DependencyService is:
DependencyService.Get<IPicture>().SavePictureToDisk("ImageName", GetByteArray());
Create Classes in Platform-Specific Projects
iOS Project
[assembly: Xamarin.Forms.Dependency(typeof(Picture_iOS))]
namespace ImageSave.iOS
{
public class Picture_iOS: IPicture
{
public void SavePictureToDisk(string filename, byte[] imageData)
{
var MyImage = new UIImage(NSData.FromArray(imageData));
MyImage.SaveToPhotosAlbum((image, error) =>
{
//you can retrieve the saved UI Image as well if needed using
//var i = image as UIImage;
if(error != null)
{
Console.WriteLine(error.ToString());
}
});
}
}
}
I got the answer for this question on Xamarin Forum so just updating the link here so it can help others.
https://forums.xamarin.com/discussion/comment/217282/#Comment_217282
As explained here that everytime we redeploy app the core path changes thats why on redeploying I was not able to find images on the path where I saved it. So now I am only saving the partial path like the folderName\the image name and rest of the core path I am finding on runtime.
This solved my problem.
I'm witnessing some strange behaviour when opening iCloud Enabled CoreData store from Apple Watch Extension.
I'm using the same iCloud Container across all targets.
Here is a picture that shows what folder (ubiquity container) structure looks like inside the ubiquity container :
It looks like it creates different stores for iPhone & Watch
I'm sharing the same CoreData Stack between iPhone app & Watch Extension. Any ideas why this is happening ?
If I understand this correctly it treats iPhone app & Watch Extension as a separate users ?
I would really appreciate if someone could give an advice.
You should use app groups to share the same Core Data store between Watch and iPhone. Enable app groups for both targets, configure it in your provisioning profiles and then get your persistent store URL like this:
NSURL *storeURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:appGroupIdentifier];
The watch would be accessing the Core Data store via a WatchKit extension also enabled for app groups. See e.g. Figure 4.1 in Apple's App Extension Programming Guide.
Consider having your WatchKit Extension use openParentApplication to communicate with the parent app. Using openParentApplication is simple to implement and helps keep the code in the WatchKit extension simple and fast.
From the WatchKit Extension InterfaceController, call openParentApplication.
NSDictionary *requst = #{#"request":#"myRequest"};
[InterfaceController openParentApplication:requst reply:^(NSDictionary *replyInfo, NSError *error) {
if (error) {
NSLog(#"%#", error);
} else {
// DO STUFF
}
}];
Then, reply from the app using
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply{
Consider also using JSON data (NSJSONSerialization) in the main app to respond to the watch extension.
I have been unsuccessful in getting core data to work on an app and today widget on my device.
let url = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.mygroup.name").URLByAppendingPathComponent("fileName.sqllite")
var error: NSError? = nil
let options = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true,NSPersistentStoreUbiquitousContentNameKey:"SharedContainerName"
]
let s = coordinator?.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: options
, error: &error)
I have added a group container that I use for the URL of the stores. I have noticed on the simulators that my persistent coordinator points to the same sqllite file
(URL: file:///Users/xxxx/Library/Developer/CoreSimulator/Devices/6Cxxxxxx/data/Containers/Shared/AppGroup/E65xxxxxx/fileName.sqllite))
This seems to work fine on the simulator and I can store data in my main app and fetch it in today widget. When I run the code on my device the files are at different locations and the databases are not synchronized (no data on the today widget).
My Main App
(URL: file:///private/var/mobile/Containers/Shared/AppGroup/2CCXXX/CoreDataUbiquitySupport/mobile~F74XXX/SharedContainerName/0E8XXXX/store/fileName.sqllite))
Today Widget
(URL: file:///private/var/mobile/Containers/Shared/AppGroup/2CCXXX/CoreDataUbiquitySupport/mobile~F74XXX/SharedContainerName/2FBYYYY/store/fileName.sqllite))
I am assuming this should be fine as they should be synchronized by iCloud. The widget runs fine, however it has no data (like it has not been synchronized). Now debugging this has been tricky as I am unable to get console output while running the today widget. When I run the widget from Xcode as opposed to attaching to the running process (The only way I can get any output on the console) I receive an error core data iCloud: Error: initial sync notification returned an error BRCloudDocsErrorDomain error 12. I receive no notifications. Maybe iCloud and Core Data do not work at this time with a today widget? The core data code in my app and extension are identical so I do not think I have a bug.
According to this Apple Developer Forum message from an Apple employee:
None of iCloud is accessible from within an Extension in iOS 8.0
and he adds in another message:
Document syncing, I should clarify, or anything which requires file coordination. I'm not sure about KVS or CloudKit
The recommendation is to expose the application state to extensions using some other method (plist, separate files, etc), which is a bit of a bummer.
I'm developping an iPad application that uses CoreData with iCloud. It works great!
I'm able to open ~/Library/Mobile Documents" with the folder that matches my Team ID and iCloud container.
I'm making also a macOS app that needs to access the iCloud. In macOS app, I added the iOS app container ID. So i have two container ID: ca.company.MacContainerID and ca.company.IPadContainerID which works for iPad app.
When i execute the following code in MacOS app, URLForUbiquityContainerIdentifier: returns nil.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *containerID = #"A1B2C3E4F5.ca.company.IPadContainerID";
NSURL *url = [fileManager URLForUbiquityContainerIdentifier:containerID];
url is nil
I don't know what to do to access to iCloud with core data inside from iPad app.
Do you have an idea ?
It must be a problem with your containerID. Did you double-check the ID string?
I found the solution! I was building the Mac application with an iOS provisionning file. So i put a Mac provisioning and it works; URLForUbiquityContainerIdentifier return me a non nul value.