UIManagedDocument can only read documents that are file packages - ios

My app is using a core data SQLite database. I would like to enable my users to use iCloud to sync it between devices - and I was thinking I could use UIManagedDocument.
I subclassed it, following Apple's documentation, and it is works when a new persistent store file needs to be created. However, when I try to use it to open my old persistent store file, I get the following exception thrown error:
"UIManagedDocument can only read documents that are file packages"
Does this mean that I need to migrate the old persistent store to a new store managed by UIManagedDocument? If so, do I need to do this manually (i.e. read each record one-at-a-time from the old store and write it into the new one)?
Thanks in advance!

UIManagedDocument creates packages(folders) rather than atomic stores. The store is still there but its buried in the package. If you right click on the file that is created in your Documents folder in the simulator you'll be able to see the structure. The default is
mydocument.foo
-> StoreContent
-> persistentStore
What you need to do is create a new extension for your app file type so for example if your database extension is .myappdb you need to create a new document type in your project settings which might be .myappdbw. You can copy all settings from the entry for .myappdb
Next at the point where you handle opening your legacy document at mydocumenturl instead of passing that to your persistent store co-ordinator you create the directory structure above.
NSURL *newurl = [[mydocumenturl URLByDeletingPathExtension] URLByAppendingPathExtension:#"myappdbw"];
NSURL *desturl = [newurl URLByAppendingPathComponent:#"StoreContent"];
[[NSFileManager defaultManager] createDirectoryAtURL:desturl withIntermediateDirectories:YES attributes:nil error:NULL];
NSURL *finalurl = [desturl URLByAppendingPathComponent:#"persistentStore"];
and then move the legacy database into the folder system you have created
[[NSFileManager defaultManager] moveItemAtURL:mydocumenturl toURL:finalurl error:NULL];
and then you can pass the bundle url to UIManagedDocument
UIManagedDocument *doc = [[UIManagedDocument alloc] initWithFileURL:newurl];
A link which will be useful for the iCloud integration is
http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-iCloudCoreData/_index.html
Its all a bit mysterious as the most of the promised sample code has failed to appear so far but on the other hand its mostly fairly simple to deduce. Have a look at WWDC2011 sessions 107,116 and 315 for more hints.
But note that if you are going to use this method for migrating your legacy docs DONT set the NSPersistentStoreUbiquitousContentNameKey at point you migrate because the package changes when you do. The doc above describes it quite well.

Thanks for this tip. I think I found an even simpler solution.
I just create a new UIManagedDocument with a different filename than my old persistent store location.
In my subclassed UIManagedDocument, I override the configurePersistentStoreCoordinatorForURL method and do the migration once there:
- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)storeURL ofType:(NSString *)fileType modelConfiguration:(NSString *)configuration storeOptions:(NSDictionary *)storeOptions error:(NSError **)error
{
// If legacy store exists, copy it to the new location
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:legacyPersistentStoreURL.path])
{
NSError* thisError = nil;
[fileManager copyItemAtURL:legacyPersistentStoreURL toURL:storeURL error:&thisError];
[fileManager removeItemAtURL:legacyPersistentStoreURL error:&thisError];
}
return [super configurePersistentStoreCoordinatorForURL:storeURL ofType:fileType modelConfiguration:configuration storeOptions:storeOptions error:error];
}

Related

Prepopulated sqlite file in the app bundle. What happens after the existing users update?

I have an app on the store which points to the appname.sqlite file in the document directory.Here's the old code:
NSURL *storeURL = [[self applicationDocumentsDirectory]URLByAppendingPathComponent:#"MyApp.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:#{NSMigratePersistentStoresAutomaticallyOption : #YES,NSInferMappingModelAutomaticallyOption : #YES}; error:&error]) {
abort();
}
I now want to give an update. The new feature being a prefilled .sqlite database which is there in the bundle.See the following
NSURL *storeURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"MyNewPrefilled" ofType:#"sqlite"]];
It would work ok on the fresh installation the app.But what happens to the users who have logged in and have a lot of data saved in the db (old sqlite in the app directory)? I would lose it because I now am pointing to the .sqlite in the app bundle (MyNewPrefilled.sqlite) and not the app directory one. How do I get the old data back from the old sqlite file of doc directory to my new one in the bundle?
FYI: I use code data migration already (involving version numbers in datamodels) which works ok when i change datamodel in updates.
Apple recommends to use two persistent stores (in the same model) to separate data that is user generated and data that is static. Presumably, if your data is static, you should do exactly that and have a read-only store for the static data. See for example Configurations in the Core Data Programming Guide.
Another approach is
to copy the bundle seed database into the documents directory, and then
read the data from the old store and
copy it to the new one.
Finally, you can delete the old store.

Unable to create a readonly sqlite store in Core Data: Cocoa Error 260

I'm trying to create a NSSQLiteStoreType with the readonly option (NSReadOnlyPersistentStoreOption). This fails if the sqlite file doesn't exist (see code below). If it does exist, the store is added without any errors.
The error I get is Cocoa Error 260:
NSFileReadNoSuchFileError = 260, // Read error (no such file)
So it looks like CoreData tries to read a file that doesn't exist, instead of creating a new one...
It seems that when adding NSReadOnlyPersistentStoreOption you can only open a previously existing store, but not create one. This doesn't make sense to me.
Is there any way to create a brand new readonly store in Core Data?
If not, is there some workaround?
// DB URL
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *dbURL = [[fm URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
dbURL = [dbURL URLByAppendingPathComponent:#"store.sqlite"];
// Object Model
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Model" withExtension:#"momd"];
NSAssert([fm fileExistsAtPath:[modelURL path]], #"File not found");
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// Store Coordinator
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// Add a readonly SQLite store
NSError *err = nil;
NSPersistentStore *store = [coord addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:dbURL
options:#{NSReadOnlyPersistentStoreOption : #YES}
error:&err];
if (store == nil) {
// I get a Cocoa Error 260.
NSLog(#"Error: %#", err);
}
Creating a new, empty, read-only store makes no sense, and the results you're seeing are exactly what would be expected. By specifying read-only you are specifically indicating that no file should be written, so as a result.... no file is written.
It's hard to tell what you're trying to accomplish. If the file were created, you would not be able to use it, since it would contain no data and since the read-only flag would prevent you from adding any data. An empty file would be exactly as useful.
But no, there is no way to tell Core Data to create a new persistent store file but have that file be read only, mainly because such an operation would be nonsensical and useless.
If you have some reason to want a persistent store file which is both empty and unwritable (and if you do, please share), you would need to
Add the persistent store without the read-only flag
Call removePersistentStore:error: to remove that persistent store
Add the persistent store again, with the read-only flag.
You will now have a persistent store which contains no data, and which you are prevented from adding data to.
A simpler alternative that is just as effective is to not create the file in the first place. An empty read-only persistent store serves literally no purpose at all, so the easy approach is to just not bother creating it.

Using Core Data created in one project as input to another project

I’ve found a couple of similar questions to this, but nothing that quite fits my needs. I’ve developed a simple digitizing app in Swift that presents an image and allows me to digitize specific points in the image using the simulator.
I use Core Data to save the data. One entity (“Coursemap”) has attributes for the image (Binary Data) and some meta data (Strings). The Coursemap entity has a one-to-many relationship with a “Points” entity. The Points entity defines attributes for the x/y coordinates for specific points in the image. The digitizer app is working well; I can digitize and save several images using Core Data. In the final version of my app I’ll probably need about 50-60 digitized images.
What I’d like to do is use the data from the output of my digitizer app as input to an iPad game I’m developing. So I basically want to copy the Core Data files created in my digitizer project over to my game project so my game app can use the digitized images. Is there a way to do this? Is Core Data the best way to handle this?
What I’ve tried so far: I name the two projects the same, but keep them in separate folders, e.g.:
~/digitizer/myApp and ~/games/myApp.
The digitizer core data files are created in the folder:
~/Library/Developer/CoreSimulator/Devices/…/Documents.
When I run the game app, it seems to expect the Core Data files to be in the same directory (which is why I named the two projects the same). When the game app tries to fetch data, I get the error, “The model used to open the store is incompatible with the one used to create the store”. I’ve double-checked the data models and they are identical.
Any ideas on this? Thanks.
I realized something similar:
- pick your prefilled sqlite file from the simulator folder
- copy it over to your other project
- during runtime, check on your other project if a sqlite structure exists and if it doesn't create it by copying the sqlite file from your bundle to the documents folder:
if(![[NSFileManager defaultManager]fileExistsAtPath:coreDataDatabase.path])
{
url = coreDataDatabase;
[[NSFileManager defaultManager] createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:NULL];
url = [url URLByAppendingPathComponent:#"StoreContent"];
[[NSFileManager defaultManager] createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:NULL];
url = [url URLByAppendingPathComponent:#"persistentStore"];
// copy the sqlite file to /Documents/<YourApp>/StoreContent/persistentStore
NSArray *stringParts = [kCoreDataPreloadedDatabase componentsSeparatedByString:#"."];
NSString *sqlitePath = [[NSBundle mainBundle]
pathForResource:[stringParts objectAtIndex:0] ofType:#"sqlite"];
NSError *anyError = nil;
BOOL success = [[NSFileManager defaultManager]
copyItemAtPath:sqlitePath toPath:[url path] error:&anyError];
if(!success)
{
Error(#"Unable to copy sqlite file: %#", anyError);
}
}

Move local Core Data to iCloud

How can I enable iCloud Core Data in an app which already uses local storage Core Data?
I've tried to use NSPersistentStoreUbiquitousContentNameKey in my persistent store options. Unfortunately, this option enables iCloud but does not transfer any of the local data to iCloud. I can't seem to get migratePersistentStore:toURL:options:withType:error: to work either. I provide the persistent store, its URL, iCloud options, etc. and it still will not migrate the existing local data to iCloud. Here's how I'm using the method:
- (void)migratePersistentStoreWithOptions:(NSDictionary *)options {
NSError *error;
self.storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:#"%#.sqlite", self.SQLiteFileName]];
NSPersistentStore *store = [self.persistentStoreCoordinator migratePersistentStore:self.persistentStoreCoordinator.persistentStores.firstObject toURL:self.storeURL options:options withType:NSSQLiteStoreType error:&error];
if (store) NSLog(#"[CoreData Manager] Store was successfully migrated");
else NSLog(#"[CoreData Manager] Error migrating persistent store: %#", error);
}
The local storage remains separate from the iCloud storage. If possible, I'd like to move the local Core Data to iCloud without manually transferring each entity.
Any ideas? I can find lots of articles, tutorials, and posts about moving back to local storage from iCloud - but I want to move from local storage to iCloud.
Here's what you'll need to do
Create a local NSPersistentStoreCoordinator
Add your existing persistent store to that coordinator and store a reference to this new returned store.
Call that handy migratePersistStore:... providing the store from #2, a URL for the store in the documents directory with a different file name and the all important options including the NSPersistentStoreUbiquitousContentNameKey key.
Here's the code, notes in-line.
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
//This is the path to the new store. Note it has a different file name
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"TestRemote.sqlite"];
//This is the path to the existing store
NSURL *seedStoreURL = [documentsDirectory URLByAppendingPathComponent:#"Test.sqlite"];
//You should create a new store here instead of using the one you presumably already have access to
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
NSError *seedStoreError;
NSDictionary *seedStoreOptions = #{ NSReadOnlyPersistentStoreOption: #YES };
NSPersistentStore *seedStore = [coord addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:seedStoreURL
options:seedStoreOptions
error:&seedStoreError];
NSDictionary *iCloudOptions = #{ NSPersistentStoreUbiquitousContentNameKey: #"MyiCloudStore" };
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//This is using an operation queue because this happens synchronously
[queue addOperationWithBlock:^{
NSError *blockError;
[coord migratePersistentStore:seedStore
toURL:storeURL
options:iCloudOptions
withType:NSSQLiteStoreType
error:&blockError];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
// This will be called when the migration is done
}];
}];
Note that after you do this migration, you'll need to configure the persistent store you use with your MOC with the new URL and always include the iCloudOptions above with the NSPersistentStoreUbiquitousContentNameKey key.
This was based on Apple's documentation.
After completion, you should see a new folder in your Documents folder in the simulator folder (~/Library/Application Support/iPhone Simulator/...) labeled CoreDataUbiquitySupport. Nested deep in there is your iCloud synced sqlite store.
Tada!
EDIT: Oh and make sure you have created an iCloud entitlement and included it in your bundle. You should be able to do that all within Xcode, but you can update it on the development portal too.
Take a look at this sample app which includes code to migrate a local core data store to iCloud and back again. Best read the associated docs and build the sample apps in your environment to get them working and once they are working then try and refactor your code to use a similar approach.
Feel free to send me an email for further help. Apologies for not giving you an answer here but it can be quite a complicated issue to deal with.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/

How can I save document files to the SQLite Database in iOS to view them later in the program?

I'm having a problem with the code I'm writing.
I'm writing an iOS program (I'm an iOS rookie) which basically requires me to use quick look framework to view some documents on the iPhone (*.doc, *.ppt, *.pdf etc..) which are stored in the database (Core Data - SQLite, nothing external). I need to add the files somehow, but since iOS isn't really allowing me to browse through its file system I can't find and save the documents i need in database. Which kinda blocks everything else i need to do until I can get those documents from the database. (to set up table views that list the files and the details about the files etc.)
This is for a class project so it doesn't need to be perfect condition, I just need to be able to browse through a few documents while I'm presenting the project. I can add all the documents I'm going to use at one time while I'm coding and I won't need to be able to add any new files when I'm using the program during the presentation. And I don't want it to make it more complicated if i don't have to. Like connecting to an external database with the files already saved in and use a php buffer-page to connect to that database or anything like that. I don't have the necessary server system to execute that php file. I want this operation to be done inside the phone.
The solutions I was able to think of so far:
Grab some random office files from the internet and save them into the database. Then use them later.
Create image scans of some office files and "cheat" by using the scanned image instead of actual documents.
I would really appreciate it if someone can tell me another and much easier way to handle this. Please just keep in mind that while I have a programming background with Java and C#, I'm still an iOS rookie and just barely moving on from scratching the surface. So it is likely that I don't know about something iOS provides by default and I'm just pulling my hair out for nothing.
I think thats it, I hope I didn't forget anything. If you need more details I'm going to be here and I can provide them almost instantly. Thanks everyone in advance for your help.
It sounds like NSFileManager will help you.
If you place your documents into your project tree, they will be available to your app. Use NSFileManager to copy them into the app's Documents folder using something like:
- (void)placeBundleFileInDocuments:(NSString *)filename
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:[[filename lastPathComponent] stringByDeletingPathExtension] ofType:[filename pathExtension]];
NSString *documentsFolderPath = [NSString stringWithFormat:#"%#/", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
NSString *path = [documentsFolderPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#", filename]];
if ([fileManager fileExistsAtPath:path])
return;
NSError *error = nil;
[fileManager copyItemAtPath:bundlePath toPath:path error:&error];
if (error) {
NSLog(#"Unable to copy file (%#).", error.localizedDescription);
}
}
Then, you can use NSFileManager to retrieve details about the files. You might find this method useful:
- (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error
I hope this helps!

Resources