FMDB - Failed to modify DB from Cocoa - objective-c

I am working on a Cocoa application which talks to a local SQLite database with FMDB. I ran into an issue that I can't do any insert or update operation on DB. Select queries run perfectly fine, so I would assume my db connection settings are correct.
The structure of my code is basically like this:
FMDatabase* db=[FMDatabase databaseWithPath:[[NSBundle mainBundle] pathForResource:#"DBName" ofType:#"sqlite"]];
if(![db open])
{
NSLog(#"Could not open db.");
}
db.traceExecution=YES;
[db beginTransation];
[db ExecuteUpdate:"INSERT INTO test (title) VALUES(?)", [NSNumber numberWithInt]:2],nil];
[db commit];
[db close];
No exceptions or warnings were thrown during execution, the console output regarding db.traceExecution is like following:
<FMDatabase: 0x100511fd0> executeUpdate: BEGIN EXCLUSIVE TRANSACTION;
<FMDatabase: 0x100511fd0> executeUpdate: INSERT INTO test (title) VALUES(?);
obj: 2
<FMDatabase: 0x100511fd0> executeUpdate: COMMIT TRANSACTION;
The testing database is simply just a one column table of INT type.
Everything looks fine except that the db file is not updated at all. It's really confusing to me as the Select query works perfectly fine. I checked the path of the database, it is pointing to the right one. First I suspect it's caused by file permission, but the issue remain the same even if I allowed everyone to be able to read/write.
I have been stucked with this problems for many hours and couldn't find a proper solution. Can anyone shed some light on this? Thanks!

Databases in the bundle are read only. If the file doesn't exist at the destination folder where you define, you should copy it from the bundle to the library or documents folder and then connect to that. That means it will copy on first use of that path.
Here's a function to 'prepare' the database by copying it to the destination from the bundle. It copies it to library (from my iOS app) but you can copy wherever you want. In my case, it was contacts.db.
I called this method from ensureOpened.
- (BOOL)ensureDatabasePrepared: (NSError **)error
{
// already prepared
if ((_dbPath != nil) &&
([[NSFileManager defaultManager] fileExistsAtPath:_dbPath]))
{
return YES;
}
// db in main bundle - cant edit. copy to library if !exist
NSString *dbTemplatePath = [[NSBundle mainBundle] pathForResource:#"contacts" ofType:#"db"];
NSLog(#"%#", dbTemplatePath);
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
_dbPath = [libraryPath stringByAppendingPathComponent:#"contacts.db"];
NSLog(#"dbPath: %#", _dbPath);
// copy db from template to library
if (![[NSFileManager defaultManager] fileExistsAtPath:_dbPath])
{
NSLog(#"db not exists");
NSError *error = nil;
if (![[NSFileManager defaultManager] copyItemAtPath:dbTemplatePath toPath:_dbPath error:&error])
{
return NO;
}
NSLog(#"copied");
}
return YES;
}

Related

Delegate for ios app upgrade

Is there any delegate method that will be called when the user upgrades to or reinstalls a newer version of the iOS app?
I use Core Data to cache some information from server. When the schema of any entity is changed, I need to manually delete the SQLite database from the simulator, otherwise the app will crash on startup, with an error "The model used to open the store is incompatible with the one used to create the store." If there is any delegate method for app upgrade, the deletion could be automated.
You need to use CoreData versioning:
http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreDataVersioning/Articles/Introduction.html
Daniel Smith's answer is the proper one, but I just want to add how my app determines its been updated. I look keep a 'current version' string in the defaults. When the app starts up, I compare it to the current version:
defaults has no string - this is the first run of the app
defaults version is different - the user updated the app
defaults is the same - user just restarted the app
Sometimes its nice to know the above. Make sure to save the defaults immediately after you set the tag and do whatever versioning you want, so a crash doesn't have you do it again.
EDIT: how not to crash if he model changes. I use this now, keep the old repository, and tweaking the model, on every tweak it just removes the old one (if it cannot open it) and creates a new one. This is modeled on Apple's code but not sure about what changes I made. In any case you don't get a crash if the model changes.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
//LTLog(#"_persistentStoreCoordinator = %#", _persistentStoreCoordinator);
if (_persistentStoreCoordinator)
{
return _persistentStoreCoordinator;
}
NSFileManager *manager = [NSFileManager defaultManager];
NSString *path = [[appDelegate applicationAppSupportDirectory] stringByAppendingPathComponent:[_dbName stringByAppendingPathExtension:#"SQLite"]];
storeURL = [NSURL fileURLWithPath:path];
BOOL fileExists = [manager fileExistsAtPath:path];
if(!fileExists) {
_didCreateNewRepository = YES;
}
if(_createNewRepository) {
[manager removeItemAtURL:storeURL error:nil];
if(fileExists) _didDestroyOldRepository = YES;
_didCreateNewRepository = YES;
}
while(YES) {
__autoreleasing NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if ([_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
break;
} else {
_persistentStoreCoordinator = nil;
[manager removeItemAtURL:storeURL error:&error];
if(fileExists) {
_didDestroyOldRepository = YES; // caller didn't want a new one but got a new one anyway (old one corrupt???)
_didCreateNewRepository = YES;
}
#ifndef NDEBUG
LTLog(#"CORE DATA failed to open store %#: error=%#", _dbName, error);
#endif
/*
Replace this implementation with code to handle the error appropriately.
abort() 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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
//LTLog(#"Unresolved error %#, %#", error, [error userInfo]);
//abort();
}
}
return _persistentStoreCoordinator;
}
Follow the blog its good:
http://blog.10to1.be/cocoa/2011/11/28/core-data-versioning/

Can create, but not open, a UIManagedDocument with iCloud support

I am creating a new UIManagedDocument with iCloud support as follows:
Alloc and init with local sandbox URL
Set persistent store options to support iCloud: ubiquitousContentNameKey and ubiquitousContentURL. The name I'm generating uniquely and the URL is pointing to my ubiquityContainer / CoreData.
Save locally to sandbox with UIManagedDocument's saveToURL method.
In completion handler, move to iCloud with FileManager's setUbiquitous method.
So far, this dance works. (Well, sort of). After I call setUbiquitous, I get an error that says it WASN'T successful, however the document moves to the cloud. When it's done, I have a new document in the cloud. This appears to be a bug, as I've been able to replicate it with others' code.
I'm actually generating this document in a "Documents View Controller," which lists all of the documents in the cloud. So when this new document's final completion handler is finished, it shows up in the table view thanks to an NSMetadataQuery. So far, pretty standard usage I think.
To edit a document, the user taps and goes to a "Single View Document View Controller."
In this view controller, I need to "reopen" the selected document so the user can edit it.
So I go through series of steps again:
Alloc / init a UIManagedDocument with a fileURL -- this time, the URL is from the cloud.
Set my persistent store options, same as step 2 above, with same settings.
Now, I ATTEMPT step 3, which is to open the document from disk, but it fails. The document is in a state of "Closed | SavingError" and the attempt to open fails.
Does anyone know why my document would create OK, move to the cloud OK, but then fail to open on an immediate subsequent attempt? (Really, an attempt within that launch of the app - see below). Specifically, what would make a UIManagedDocument instance be created but in a closed, non-openable state?
Interestingly enough, if I quit the app and launch again, I can tap and reload the document and edit it.
And very occasionally I can create, then open, and edit very briefly, say insert one managedobject, and then it goes into this close | saving error state.
ERROR INFO:
I've subclassed UIManagedDocument and overrode the -handleError: method to try and get more information, and here's what I get (along with some other debugging logs I put in):
2012-10-05 14:57:06.000 Foundations[23687:907] Single Document View Controller View Did Load. Document: fileURL: file://localhost/private/var/mobile/Library/Mobile%20Documents/7PB5426XF4~com~howlin~MyApp/Documents/New%20Document%2034/ documentState: [Closed]
2012-10-05 14:57:06.052 MyApp[23687:907] Document state changed. Current state: 5 fileURL: file://localhost/private/var/mobile/Library/Mobile%20Documents/7PB5426XF4~com~howlin~MyApp/Documents/New%20Document%2034/ documentState: [Closed | SavingError]
2012-10-05 14:57:06.057 Foundations[23687:5303] UIManagedDocument error: The store name: New Document 34 is already in use.
Store URL: file://localhost/private/var/mobile/Library/Mobile%20Documents/7PB5426XF4~com~howlin~MyApp/Documents/New%20Document%2034/StoreContent.nosync/persistentStore
In Use Store URL: file://localhost/var/mobile/Applications/D423F5FF-4B8E-4C3E-B908-11824D70FD34/Documents/New%20Document%2034/StoreContent.nosync/persistentStore
2012-10-05 14:57:06.059 MyApp[23687:5303] {
NSLocalizedDescription = "The store name: New Document 34 is already in use.\n\tStore URL: file://localhost/private/var/mobile/Library/Mobile%20Documents/7PB5426XF4~com~howlin~MyApp/Documents/New%20Document%2034/StoreContent.nosync/persistentStore\n\tIn Use Store URL: file://localhost/var/mobile/Applications/D423F5FF-4B8E-4C3E-B908-11824D70FD34/Documents/New%20Document%2034/StoreContent.nosync/persistentStore\n";
NSPersistentStoreUbiquitousContentNameKey = "New Document 34";
}
The error seems to think I'm it create a store that already exists on the subsequent opening. Am I now supposed to set those iCloud option on the persistent store on a second opening? I've tried that approach and it didn't work either.
I've studied the Stanford lectures on UIManagedDocument and don't see what I'm doing wrong.
Here's my method to create the doc and move to cloud:
- (void) testCreatingICloudDocWithName:(NSString*)name
{
NSURL* cloudURL = [self.docManager.iCloudURL URLByAppendingPathComponent:name isDirectory:YES];
NSURL* fileURL = [self.docManager.localURL URLByAppendingPathComponent:name];
self.aWriting = [[FNFoundationDocument alloc] initWithFileURL:fileURL];
[self setPersistentStoreOptionsInDocument:self.aWriting];
[self.aWriting saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success == YES) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//create file coordinator
//move document to icloud
NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSError* coorError = nil;
[fileCoordinator coordinateWritingItemAtURL:cloudURL options:NSFileCoordinatorWritingForReplacing error:&coorError byAccessor:^(NSURL *newURL) {
if (coorError) {
NSLog(#"Coordinating writer error: %#", coorError);
}
NSFileManager* fm = [NSFileManager defaultManager];
NSError* error = nil;
NSLog(#"Before set ubiq");
[fm setUbiquitous:YES itemAtURL:fileURL destinationURL:newURL error:&error];
if (!error) {
NSLog(#"Set ubiquitous successfully.");
}
else NSLog(#"Error saving to cloud. Error: %#", error);
NSLog(#"State of Doc after error saving to cloud: %#", self.aWriting);
}];
});
}
}];
}
Here's where I set options for iCloud on the persistentStore:
- (void)setPersistentStoreOptionsInDocument:(FNDocument *)theDocument
{
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
[options setObject:[theDocument.fileURL lastPathComponent] forKey:NSPersistentStoreUbiquitousContentNameKey];
NSURL* coreDataLogDirectory = [self.docManager.coreDataLogsURL URLByAppendingPathComponent:[theDocument.fileURL lastPathComponent]];
NSLog(#"Core data log dir: %#", coreDataLogDirectory);
[options setObject:coreDataLogDirectory forKey:NSPersistentStoreUbiquitousContentURLKey];
theDocument.persistentStoreOptions = options;
}
And here's where I try to reopen it:
- (void) prepareDocForUse
{
NSURL* fileURL = self.singleDocument.fileURL;
if (![[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
NSLog(#"File doesn't exist");
}
else if (self.singleDocument.documentState == UIDocumentStateClosed) {
// exists on disk, but we need to open it
[self.singleDocument openWithCompletionHandler:^(BOOL success) {
if (!success) {
NSError* error;
[self.singleDocument handleError:error userInteractionPermitted:NO];
}
[self setupFetchedResultsController];
}];
} else if (self.singleDocument.documentState == UIDocumentStateNormal) {
// already open and ready to use
[self setupFetchedResultsController];
}
}
Have you recently been testing various version of iOS? Try changing the title of your saved document to something other than "New Document 34", I was experiencing this same issue and I believe it had to do with conflicting documents saved from different sdk compilations of the app using the same document url.
I target iOS7, I use a single UIManagedDocument as my app DB with the goal to better integrate CoreData and iCloud as suggested by Apple in its documentation. I had the same problem, i solved with the following code.
Since I wrote it, I moved the PSC options settings inside the lazy instantiation of the UIManagedDocument.
My original code created, closed and then reopened the document using the callback with success standard functions. I found it on a book by Erika Sadun. Everything seemed ok but I couldn't reopen the just created and then closed document because it was in "savings error" state. I lost a week on it, I couldn't understand what I was doing wrong because until the reopen everything was perfect.
The following code works perfectly on my iPhone5 and iPad3.
Nicola
-(void) fetchDataWithBlock: (void (^) (void)) fetchingDataBlock
{
//If the CoreData local file exists then open it and perform the query
if([[NSFileManager defaultManager] fileExistsAtPath:[self.managedDocument.fileURL path]]){
NSLog(#"The CoreData local file in the application sandbox already exists.");
if (self.managedDocument.documentState == UIDocumentStateNormal){
NSLog(#"The CoreData local file it's in Normal state. Fetching data.");
fetchingDataBlock();
}else if (self.managedDocument.documentState == UIDocumentStateClosed){
NSLog(#"The CoreData local file it's in Closed state. I am opening it.");
[self.managedDocument openWithCompletionHandler:^(BOOL success) {
if(success){
NSLog(#"SUCCESS: The CoreData local file has been opened succesfully. Fetching data.");
fetchingDataBlock();
}else{
NSLog(#"ERROR: Can't open the CoreData local file. Can't fetch the data.");
NSLog(#"%#", self.managedDocument);
return;
}
}];
}else{
NSLog(#"ERROR: The CoreData local file has an unexpected documentState: %#", self.managedDocument);
}
}else{
NSLog(#"The CoreData local file in the application sandbox did not exist.");
NSLog(#"Setting the UIManagedDocument PSC options.");
[self setPersistentStoreOptionsInDocument:self.managedDocument];
//Create the Core Data local File
[self.managedDocument saveToURL:self.managedDocument.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if(success){
NSLog(#"SUCCESS: The CoreData local file has been created. Fetching data.");
fetchingDataBlock();
}else{
NSLog(#"ERROR: Can't create the CoreData local file in the application sandbox. Can't fetch the data.");
NSLog(#"%#", self.managedDocument);
return;
}
}];
}
}

Update read-only Core Data sqlite in main bundle

I am using a read-only Core Data sqlite from the Main Bundle, works well. When i add a new version of the database (more read-only data) to the main bundle it still reads the "Old" version of the database.
Anyone that can help me understand why and what to do to get the new database version the current one when a current user download an update with the new version of the database?
This is part of trying to solve the problem in this post: Same problem when accessing updated database from documents directory
===SOLUTION====
I solved this by changing the name of the new database in the "new" main bundle and it works like a dream. Also, if this is an update i delete the old database in the documents directory to clean up.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
//===READ DATABASE FROM MAIN BUNDLE===//
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *storeUrl = [[NSBundle mainBundle] URLForResource:kNewDB withExtension:#"sqlite"];
//=== IF THE OLD DATABASE STILL EXIST DELETE IT FROM DOCUMENT DIRECTORY ===//
NSURL *oldDatabasePathURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"database.sqlite"];
NSString *oldDatabasePath = [oldDatabasePathURL path];
if ([fileManager fileExistsAtPath:oldDatabasePath]) {
//Remove old database from Documents Directory
[fileManager removeItemAtURL:oldDatabasePathURL error:nil];
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
You must have a place in your code where you check to see if a copy of the database file exists in some writable directory (possibly your Documents directory) and if not, then you copy it there. This is a very common approach to take when you need to make changes to your database. The problem is, when you update your app, the file already exists, so it is never copied over again.
There are two approaches to take to fix your problem:
(Preferable): Don't copy the database in the first place. Since it is read only, you don't need to, and it just takes up extra space on the device. Simply open the database using the path of the file that is in the main bundle.
Instead of checking to see if a file exists in the writable directory, check to see if it is newer than the one in the main bundle. (not by using the date, since they could have installed the program and created the file after your update was submitted to the app store for approval, which would result in the new one not being copied over. You need to check the version of the database, possibly by storing another file in your app bundle which stores the version info, or determining it with version specific code). If not, then copy it over again.
PeterK, I was having the same issue when using the tutorial at http://www.raywenderlich.com/12170/core-data-tutorial-how-to-preloadimport-existing-data-updated to use a read-only sqlite database by Core Data. All was fine, until I had to update my database and re-release my target app. As you know, the proposed code in that tutorial only copies in the new database if no database exists in the Application's Documents directory.
I did not think that renaming my database (and updating the copying code) was a good design approach, so I got my design working by following Inafziger's preferred advise and reading up on iOS file structure. I provide the below only to show how to implement Inafziger's proposal. And of note is that this approach likely only works if your app does not change the contents of the Core Data information as it is read in as read-only.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Updated processing to now just have the NRPersistentStoreCoordinator point to the sqlite db in the
// application's Bundle and not by copying that db to the app's Documents directory and then using
// the db in the Documents directory.
NSURL *refactoredStoreURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#“NameOfYourDatabase ofType:#"sqlite"]];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// Added to ensure the NSPersistentStoreCoordinator reads the Bundle's db file as read-only since
// it is not appropriate to allow the app to modify anything in the Bundle
NSDictionary *readOnlyOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSReadOnlyPersistentStoreOption, nil];
// Use the URL that points to the Bundle's db file and used the ReadOnly options
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:refactoredStoreURL options:readOnlyOptions error:&error]) {
// Your logic if there is an error
}
return _persistentStoreCoordinator;
}
I hope this helps the next reader of this question.

Objective-C: need to call a method in another class from FinishedLaunching

I got this far... here is my code:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
// create the d/b or get the connection value
SQLiteDB *dbInstance = [[SQLiteDB alloc] init];
}
Now, the question is: this bit of code is supposed to check to see if a database exists, and if not, create it. My problem is I am having a problem figuring out exactly how to write the first line of the called method and where to place it in SQLiteDB.m. Is this an instance method (-) or a class method (+)?
I'm sorry for being so lame on this, but once I see it, I'll have the hang of it... the rest of the code is written in C#, and I can handle the conversion to Obj_C.
The following is a method to copy an existing database from your Bundle to the Documents directory, but can easily be adapted for a new database. Just use the fileExistsAtPath: method logic below and replace the actions to take with your custom database creation code.
Put this in your AppDelegate.m file:
- (void)prepareDatabase
{
//add Database Versioning check to see if the resources database is newer
// generally as simple as naming your database with a version on the end
NSFileManager *filemanager = [NSFileManager defaultManager];
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:#"/YOURDATABASE.s3db"];
if(![filemanager fileExistsAtPath:databasePath]) {
//Database doesn't exist yet, so we copy it from our resources
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/YOURDATABASE.s3db"];
if([filemanager copyItemAtPath:defaultDBPath toPath:databasePath error:nil]) {
NSLog(#"Database Copied from resources");
} else {
NSLog(#"Database copy FAILED from %# to %#",defaultDBPath,databasePath);
}
}
}
Then in your applicationDidFinishLaunching: method call this:
[self prepareDatabase];
I'm assuming that by "see if the database exists" you mean "see if the database file exists on disk". For that, you use method fileExistsAtPath: of class NSFileManager. It's an instance method, but you can use [NSFileManager defaultIntance].
Calculate the path to the file first (it's up to you how). Check if the file exists. If yes, open the file, if not, create a new database with that filename.

sqlite db creation issue

Im teaching myself objective-c and currently trying to work out how to integrate a database into my application. Ive looked at many example, forums and tutorial but none have worked, what am i doing wrong?
Most of the examples come ready with a [projectName].sqlite db in the application. How does that get added? Not seen one explanation of how that ends up in the project.
I've created my Object Model and classes. Im running a method which checks if DB exists, if not, it creates the db. Ive found several examples where different paths are used and im not sure which is correct, Some examples copied part of my project files into the documents/mainDatabase.sqlite folder! Is the following correct, if so why am i getting an error?
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to create writable database file with message 'The operation couldn’t be completed. No such file or directory'.
Using these causes the above error, and yes the defaultDBPath does exist
defaultDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/Documents
defaultDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/Documents/mainDatabase.sqlite
writableDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/dbProj12.app/mainDatabase.sqlit
- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence - we don't want to wipe out a user's DB
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentDirectory = [self applicationDocumentsDirectory];
NSLog(#"documentDirectory = %#", documentDirectory);
NSString *writableDBPath = [documentDirectory stringByAppendingPathComponent:#"iBountyHunter.sqlite"];
NSLog(#"defaultDBPath = %#", writableDBPath);
BOOL dbexits = [fileManager fileExistsAtPath:writableDBPath];
if (!dbexits) {
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"iBountyHunter.sqlite"];
NSLog(#"defaultDBPath = %#", defaultDBPath);
NSError *error;
BOOL success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
}
Look forward to your reply.
I'm assuming you are using CoreData, but the solution should be valid for every database type.
The message stated that iBountyHunter.sqlite doesn't exists and it's a true message: that db should be added in the XCode project and packaged when you build the app.
Some apps ships with an already populated database probably modified by the developer on the mac, based on what is created in the simulator (or even in a development device).
Build and run you app on the simulator;
Look in the Documents folder on your device and copy the database;
Edit it adding custom data;
Add the db to XCode;
Add the code you've reported;
Build and let the app copy the populated database on the device.
It might be late, but i wanted to answer this question so that in future no one gets this issue.
The answer has to do with the "Target Membership".
click on the sql file in the left-pane of Xcode
Under "Target Membership", make sure the "check" is "checked" for your target build.
that's it, this solves my issue. I hope this helps you also.
Mac makes a good point, are you using sqlite or core data?
If you are using sqlite then one of the easier things to do is on application launch, copy it into your apps documents directors (unless it already exists of course, in which case do nothing)
I generally create an sqliteDB external to xcode and add it as a file (just like an image). Then in the 'applicationDidFinishLaunchingWithOptions' method (in your app delegate I call the same method, but my code is a little different. Take a look:
- (void)createEditableCopyOfDatabaseIfNeeded
{
//We always need to overwrite the DB, but first we need to extract usedQuestionIds, ObjectiveData and stats
//Once database is replaced, reinsert data
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"yourdatabasename.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success)
{
//might want to check for update and if so, back up users unique data, replace database and reinsert
}
else
{
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"yourdatabasename.sqlite"];
[fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
}
It then create another method in my app delegate that allows me to get a db connection which I can then use:
+(sqlite3 *)getNewDBConnection
{
sqlite3 *newDBconnection;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"yoursqliteDatabaseName.sqlite"];
// Open the database. The database was prepared outside the application.
if (sqlite3_open([path UTF8String], &newDBconnection) == SQLITE_OK)
{
NSLog(#"Database opened successfully");
}
else
{
NSLog(#"Error in opening database ");
}
return newDBconnection;
}
Now wherever you are in your app you can get a connection to you db by calling:
sqlite3 *db = [YourAppDelegateName getNewDBConnection];
//do stuff with your sqlite db connection
sqlite3_close(db);
Hope that helps :)