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 :)
Related
Here I want to achieve a function which can copy a res file from main bundle (which is added manually from Mac Finder to Xcode project ) (Fig 1) to Document Folder.
Click here to see the Xcode project structure
And to achieve that goal, I use NSFileManager. Here is the function code:
- (void)addCamConfig {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *destPath = [documentsDirectory stringByAppendingPathComponent:CAM_CONFIG];
if (![fileManager fileExistsAtPath:destPath]) {
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:#"cam" ofType:#"yaml"];
BOOL ret = [fileManager copyItemAtPath:sourcePath toPath:destPath error:&error];
if (error) {
NSLog(#"cam_ret:%d\nsourcePath:%#\ndestPath:%#\nerror:%#",ret,sourcePath,destPath,error.description);
}
}
}
In that case, sourcePath and destPath are both ensured not to be nil.
But strange thing happened in the first time, "copyItemAtPath:sourcePath toPath:destPath error:&error" return "NO" and error log showed below:
cam_ret:0
sourcePath:/private/var/containers/Bundle/Application/3DF49723-E11D-4D67-AD0F-39C2B82B80A4/NitroDemo.app/cam.yaml
destPath:/var/mobile/Containers/Data/Application/181F8B72-402B-4D0C-91D5-0D9759BC607E/Documents/nitro/cam.yaml
error:Error Domain=NSCocoaErrorDomain Code=4 "The file “cam.yaml” doesn’t exist." UserInfo={NSSourceFilePathErrorKey=/private/var/containers/Bundle/Application/3DF49723-E11D-4D67-AD0F-39C2B82B80A4/NitroDemo.app/cam.yaml, NSUserStringVariant=(
), NSDestinationFilePath=/var/mobile/Containers/Data/Application/181F8B72-402B-4D0C-91D5-0D9759BC607E/Documents/nitro/cam.yaml, NSFilePath=/private/var/containers/Bundle/Application/3DF49723-E11D-4D67-AD0F-39C2B82B80A4/NitroDemo.app/cam.yaml, NSUnderlyingError=0x2826476f0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
Surprisingly, when I run project second time, the error disappeared, "copyItemAtPath:sourcePath toPath:destPath error:&error" return "YES".
And if I remove this app from my iPhone, then rebuild the project , reinstall and run the app first time, the same error message appears again. And as I expected, when I run the project second time, the error disappeared.
So I wonder what exactly happened in the first time and second time?
====================
UPDATE: I solved this problem by creating an intermediate folder nitro.
The key point here is that, as you may not noticed in the log, the dest path XXX/Documents/nitro/cam.yaml contains a not existed intermediate folder nitro. So in the first time when I call copyItemAtPath:sourcePath toPath:destPath error:&error, it fails and may create that folder (just for my guess). As a result, when I run second time, the copyItemAtPath:sourcePath toPath:destPath error:&error returns YES.
Ok, based on the update you could solve it then by inserting something like below.
[NSFileManager.defaultManager createDirectoryAtPath:newDir
withIntermediateDirectories:YES
attributes:nil
error:NULL];
The Situation:
I have an iOS app that deals with files and lets the user save, edit, open and perform various operations with these files. I'd like to be able to have some pre-made documents for the user to look at when they open the app (ex. a template) alongside their own custom documents.
The Problem:
How can I create a document (or template file) and have it appear in the Documents folder after the user installs my app and launches it (and all preceding times)?
Background:
This document (the one that'd be installed into the app's documents directory) is created by me, not the user.
I know that to do this you need to save it in your bundle, and then when your app runs for the first time silently copy it into the Documents Directory. Should I copy it in the appDidFinishLaunchingWithOptions method or in my viewDidLoad method and write logic to detect if it's the first time the app has run?
My Code:
At this webpage: http://textsnip.com/d35fbc
But when it runs, it always says: "File does not exist [in documents folder]", then it tells me that it's being copied. The problem is that when I examine the app's documents folder it is never there, it's still in the bundle.
Why won't it copy with this code
How does this work?
the files must in fact be added to the app bundle, then copied silently when the app launches.
The following code copies a text file from the top level of my bundle to the documents directory if it doesn't already exist.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *txtPath = [documentsDirectory stringByAppendingPathComponent:#"txtFile.txt"];
if ([fileManager fileExistsAtPath:txtPath] == NO) {
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"txtFile" ofType:#"txt"];
[fileManager copyItemAtPath:resourcePath toPath:txtPath error:&error];
}
The only flaw in this code is that if the user deletes the file, it immediately reappears when they open the app again.
As you said, you include it in your app bundle (add it to your project and make sure it's part of your target). Then you can access it's path by calling something like this:
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:#"MyTemplateDoc"
ofType:#"extension"];
Then you copy it to your app's documents folder.
NSString *docPath = <a path in your documents folder>
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtPath:bundlePath
toPath:docPath
error:&error];
if (error) {
// handle copy error.
}
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;
}
I have a problem with NSFileManager, because i only can store a file into Application Documents Directory, but i want to create a file into a sub directory this i don't think why, i couldn't create. my code below:
+(NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
+(BOOL)storeFile:(NSData*)file withName:(NSString*)name atDirectory:(NSString*)dir{
NSFileManager *filemgr;
NSString *docsDir;
NSString *newDir;
BOOL create=NO;
filemgr =[NSFileManager defaultManager];
docsDir = [StorageManager applicationDocumentsDirectory];
newDir = [docsDir stringByAppendingPathComponent:dir];
if(![filemgr fileExistsAtPath:newDir]){
if([filemgr createDirectoryAtPath:newDir withIntermediateDirectories:NO attributes:nil error:nil]){
create=YES;
}
}else
create=YES;
if(create){
if(![filemgr createFileAtPath:newDir contents:file attributes:nil]){
[filemgr release];
return YES;
}
}
[filemgr release];
return NO;
}
I am not sure why the file is not created. At first glance, it looks like your code should work. But I may be overlooking something. It also depends on what exactly you are passing as arguments to the storeFile:withName:atDirectory: method.
Nevertheless I am posting an answer because I did spot another error in your code: you should not send release to filemgr since you also did not send retain to it first and you did not create the object. In this case, there is no need to send retain to it either, since you are only using it locally within your method. You may want to review the Apple Developer Connection document "Cocoa Core Competencies: Memory Management".
I don't think this error explains why the file is not created; though I'm surprised your application doesn't crash because of it.
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.