iOS Adding Initial Core Data file using UIManagedDocument, NOT appdelegate - ios

I am trying to load an initial database into my app so my core data db is not empty upon install. I'm now using this code:
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"%#_InitialData", SQL_DATABASE_NAME] ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
(from http://code.google.com/p/coredatalibrary/wiki/LoadingInitialData)
to try and load an initial sqlite file for my core data to use. It isn't working and my program differs from the type used in that link in a few ways.
It appears the tutorial uses a file created to "use core data", which I am not. I just didn't happen to learn it that way (watched the stanford cs193p videos) and instead I'm using UIManagedDocument and performing this code in my top view controller. Because of this, I've run into a few problems.
I loaded up my app to create the initial data base so I could save the file to use for initial values. Upon doing so, I found that the way things are saved are different from in the tutorial. For example, if my url for my UIManagedDocument is .../Documents/Test , then my database file is .../Documents/Test/Store Content/persistentStore, where "persistentStore" is the database file. For one thing, a "Store Content" directory has been added. In addition, the sqlite file is named persistentStore and has no file extension. When I open the file it says
SQLite format 3���# �����������������������������������������������������������������-‚%
on the top though (I'm not familiar with SQLite or any db format for that matter but I assume this means it is an sqlite file).
I save this "persistentStore" file to use to load into my app using the code above. Upon doing so, I found that copyItemAtPath:toPath:error: will not copy as I expect. For example, if the storePath is .../Documents/Test , then my sqlite file that I'm copying over becomes renamed to Test.sqlite and is located at .../Documents/ instead of copying my file to a location of .../Documents/Test/.
Because of this, when I try to open the UIManagedDocument at that url (.../Documents/Test) I get this error
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'UIManagedDocument can only read documents that are file packages'
I've tried creating a directory to mimic the one created by core data before copying over my intial data (that is, I create the directories to have a path of .../Documents/Test/Store Content/ and then copy my initial data to be in the "Store Content" folder with a name of persistentStore) but that also doesn't work. UIManagedDocument can't open the document.
So how can I load in initial values to my core data db without having a project that is set to "use core data"? I have the (presumably) sqlite file with the initial data (when I open it and skim it it appears to have my initial values), so I just need to know how to copy it over properly so that I can still used UIManagedDocument to open the document and save via the UIManagedObjectContext.

It turns out my way of recreating the path created by core data worked. That is, creating the directories of .../Test/Store Content/ and copying the file as "persistentStore". My error was a small one. It was suppose to be StoreContent instead of "Store Content"; I added a space...

Related

Shipping core data with iOS app

I am developing an dictionary app that has around 10,000 words in it. I am using core data to store all the data and the language that I am using is swift.
All I want is to ship this data with the app so that when the app is downloaded from the app store, it contains all the data.
I have already searched on this and found that it can be done by including SQLite file to the project. But really do not know how to do that and where is the simulator directory in El Capitan.
I am just a beginer in iOS, so kindly someone explain it to me in very simple steps.
This could be quite a lengthy answer. It would be best to explain it as a tutorial. The following link will show you what to do.
http://www.appcoda.com/core-data-preload-sqlite-database/
I found the start of the article/tutorial and the end useful for what you are trying to do.
I recently preloaded data into an app for a Quiz, using a separate Xcode simulator to create the 3 SQLite files needed. This article will show you how to find them, how to "bundle" them into your Xcode project that you want to 'Pre-Load' and will tell you the code to add inside your 'AppDelegate.swift' file and where.
There are 3 things that will help you avoid problems in completing your project...
Ensure the Entity and Attribute names (in your Data Model (Core Data)) for the data you are creating in the Simulator (separate project) are identical to the ones in the project you want to 'PreLoad'. [ Otherwise the Preloading WONT work ]
The code to add in 'AppDelegate.swift' is designed to direct your App as to where to go for the preLoaded Data (see the attached Tutorial), however, I found I needed to tweek the code to make it work...(noted below)
Nothing is deleted from the 'AppDelegate.swift' file, merely added to and the names of the 3 SQLite files added to it... e.g.
In 'AppDelegate.swift' in the Core Data stack... you will find...
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("EoMQ", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
Leave this alone, but direcly underneath this you need to have the following code...
// ---------------------------------------------------
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("Q100cdCoreData.sqlite") // originally = "SingleViewCoreData.sqlite" - then changed to new name... from imported sqlite files
// ---------------------------------------------------
// ** Load the Already made DB from Simulator **
if !NSFileManager.defaultManager().fileExistsAtPath(url.path!) {
let sourceSqliteURLs = [NSBundle.mainBundle().URLForResource("Q100cdCoreData", withExtension: "sqlite")!, NSBundle.mainBundle().URLForResource("Q100cdCoreData", withExtension: "sqlite-wal")!, NSBundle.mainBundle().URLForResource("Q100cdCoreData", withExtension: "sqlite-shm")!]
let destSqliteURLs = [self.applicationDocumentsDirectory.URLByAppendingPathComponent("Q100cdCoreData.sqlite"), self.applicationDocumentsDirectory.URLByAppendingPathComponent("Q100cdCoreData.sqlite-wal"), self.applicationDocumentsDirectory.URLByAppendingPathComponent("Q100cdCoreData.sqlite-shm")]
for index in 0 ..< sourceSqliteURLs.count {
do {
try NSFileManager.defaultManager().copyItemAtURL(sourceSqliteURLs[index], toURL: destSqliteURLs[index])
} catch {
print(error)
}
}
}
The 3 files I had created in the simulator were called "Q100cdCoreData" but with three different extensions... (a) .sqlite (b) .wal (c) .shm
But you need to go through the tutorial and understand the process.
Add the sqlite file to project.
You will call
[[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&error]
Before:
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error]
Where preloadURL will be the URL for file in application bundle. The storeURL is the path to your sqlite file for core data (normally in application directory).
So the file will be copied from bundle to app directory if it doesn't exist and fail otherwise.

Large files downloaded to Documents and backed up to iCloud

I have an iOS app in the app store that can download relatively large files that need to stay on the device for offline use. Those files are currently stored in the app's Documents folder but I'm just now reading that the Documents folder is backed up and should really only be used for user-generated content. This Apple technical Q&A states that the NSURLIsExcludedFromBackupKey should be set to prevent backup. This states that an app's /Library/Caches is the right place to put these kinds of files although further reading suggests that the folder may be cleared when the device is low on storage which is unacceptable for this app. I believe /Library/Application Support/ is then the best location for them -- does this sound right?
Unfortunately, this mistake got through the app review process. What are some best practices for fixing this now that people are using the app and already have some files persisted to the Documents folder and to their backups? It seems I need to move all the existing files and set their NSURLIsExcludedFromBackupKey on app update. How do I guarantee that this is done exactly once and that it isn't interrupted? Is moving the files out of the Documents folder important or could I leave them there? Will changing the files' backup status remove them from existing backups?
I'm using Swift 2.1.1 and targeting iOS 8.0+.
As stated in the technical Q&A, you best bet could be create a subdirectory in the Documents, and exclude that subdirectory once.
I don't believe you can write a 'do it once and be sure it is done' routine, since you can't guarantee your app doesn't crash while it is running. You certainly could set a completion flag when you are sure it is done so that once it is done you don't have to run it again.
Exclude your directory from backup, not the individual files.
From Xcode:
You can use this property to exclude cache and other app support files which are not needed in a backup. Some operations commonly made to user documents cause this property to be reset to false; consequently, do not use this property on user documents.
Here is the strategy I have used with good results
(sorry, its in objective-c -- I'm not a swift guy. Hopefully it will give you the idea):
- (BOOL)moveAllFiles{
// Searches through the documents directory for all files ending in .data
BOOL success = true;
NSString *myNewLocation = #"/store/my/files/here/now";
// Get the documents directory
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
// Get all files ending in .data (whatever your file type is)
NSArray *dataFilesArray = [NSArray arrayWithArray:[NSBundle pathsForResourcesOfType:#"data" inDirectory:documentDirectory]];
// If you have multiple resource types, use this line once each for each resource type, then append the arrays.
// Iterate the found files
NSString *fileName = [NSString string];
for (int i=0; i<[dataFilesArray count]; i++) {
fileName = [[dataFilesArray objectAtIndex:i] lastPathComponent];
// Move file, set success to false if unsuccessful move
if (![[NSFileManager defaultManager] moveItemAtPath:[dataFilesArray objectAtIndex:i]
toPath:[myNewLocation stringByAppendingPathComponent:fileName]
error:nil]) {
success = false; // Something went wrong
}
}
return success;
}
Now use the value of success to set a key in the user defaults file. Check for that key on startup. If it is false (or absent), run this routine (again).
This example is with file paths. You can do the same thing with file URLs if you wish.

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);
}
}

Load data into my app xcdatamodel

My app must start for end users with data already in the database so that info is displayed to them when they use the app.
My problem: how can I load the data into the app database?
There's an "import..." option on xcode (on Editor when selecting xcdatamodeld) but I cannot understand what is the file type required - I've tried .xls, .csv and .sqlite and none is "importable".
Help!
Add the database to your bundle when you ship your app. On initial startup, you look to see if the database lives in the local file system. If not (and it won't be the first time you start up), you copy the database from the bundle to the local file system, open it, and use it from there. Don't try to use it from the bundle, that's set to read only.
I'm assuming you're using a SQLite database or some other type of file. You can add a file to your bundle using these instructions: How do I add files to the resources folder in XCode?
If the data is already in the .sqlite format (see note below), copy that file to the project (move the file to the Xcode project directory, and then from Xcode choose File -> Add files to "Project name", and find the file in your computer directory.
In the main AppDelegate.m file under the persistentStoreCoordinator function (see this tutorial for setting up sqlite/Core Data in your project), write the code to copy from the main bundle resources to the app resources directory, if the file doesn't exist:
NSURL *dbUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"yourDBName.sqlite"]];
if (![[NSFileManager defaultManager] fileExistsAtPath:[dbUrl path]]) {
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"yourDBName" ofType:#"sqlite"]];
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:bundleURL toURL:dbUrl error:&err]) {
NSLog(#"Could not copy main bundle data");
}
else { NSLog(#"Main bundle data successfully copied"); }
}
It is important that your preloaded .sqlite database from the main bundle have been created properly from within an Xcode project (iOS apps only recognize specially-formatted .sqlite files).
Once this is loaded, it will be recognized as the app's database as long as the Core Data managed object context has its persistent store coordinator set to this .sqlite file, and that its structure matches that of the core data model.

UIManagedDocument can only read documents that are file packages

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];
}

Resources