bundle sqlite database with app and stringByAppendingPathComponent error - sqlite

There are probably many other questions I don't even know to ask yet since I'm new to app programming.
I initially created the database from within the app, copied it to my working folder (which is probably not where it should ultimately reside), then appended my records (about 1,000 of them) from a text file.
The first two questions that come to mind are:
- what folder should the database be in?
- how does it get deployed with the app?
I found quite few examples using the following lines in persistentStoreCoordinator function:
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"myDatabase.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
But the first line gives me the pre-compile error: "Receiver type 'NSURL' for instance message does not declare a method with selector 'stringByAppendingPathComponent:'. Why is it not working for me?
And is this in fact the best way to bundle my database with the rest of the app?
Thanks!

Easiest solution is to use NSUrl instead of NSString. SO user #trapper already provided a solution in the below link.
importing sqlite to coredata
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Database.sqlite"];
// If the database doesn't exist copy in the default one
if (![storeURL checkResourceIsReachableAndReturnError:NULL])
{
NSURL *defaultStoreURL = [[NSBundle mainBundle] URLForResource:#"Database" withExtension:#"sqlite"];
if ([defaultStoreURL checkResourceIsReachableAndReturnError:NULL])
{
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager copyItemAtURL:defaultStoreURL toURL:storeURL error:NULL];
}
}

Related

Copying plist to App Group Container - iOS 8

I am adding an App Group to my app for sharing a single plist between the app and the watch. I used to copy a plist from the bundle to Documents for when the app first started up. But with the watch I am now trying to convert it to save to the container but it always seems to be empty. The targets have app group enabled, and I am using the right name in my code. What could be going wrong?
Old Way
// COPY PLIST TO DOCUMENTS
NSFileManager *fileManger=[NSFileManager defaultManager];
NSError *error;
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *destinationPath= [doumentDirectoryPath stringByAppendingPathComponent:#"badger.com.vacations.plist"];
NSLog(#"plist path %#",destinationPath);
if (![fileManger fileExistsAtPath:destinationPath]){
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"badger.com.vacations.plist"];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
}
New way - not working
// COPY PLIST TO CONTAINER
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com.xxxxx.xxx.container"];
containerURL = [containerURL URLByAppendingPathComponent:#"name.com.data.plist"];
NSString *destinationPath= containerURL.path;
NSLog(#"destinationPath %#", containerURL);
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"name.com.data.plist"];
[fileManger copyItemAtPath:sourcePath toPath:containerURL.path error:&error];
You can´t share a plist as a file (or I simply don´t know about this feature), instead you just generate a new NSUserDefaults instance, which is shareable between targets.
Have a look here:
https://devforums.apple.com/message/977151#977151
or the Apple documentation
https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html
In the Apple Member Center, under 'Identifiers > App Groups' register a new App Group (e.g. group.de.myApp.sharedGroup)
In the Apple Member Center, under 'Identifiers > App IDs' refresh your App Ids for the targets that need sharing to use the App Groups feature
Regenerate all needed Provisioning Profiles and get them into Xcode
Back to Xcode: Under 'Capabilities' in each of your targets that need to share data, set 'App Groups' to on and add the previously registered App group.
Talk to the shareable NSUserDefaults container like this:
Store stuff:
NSUserDefaults *groupDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.de.myApp.sharedGroup"];
[groupDefaults setInteger:1337 forKey:#"testEntry"];
[groupDefaults synchronize];
Read stuff:
NSUserDefaults *groupDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.de.myApp.sharedGroup"];
NSInteger testEntry = [groupDefaults integerForKey:#"testEntry"];
NSLog(#"testEntry: %ld", (long)testEntry);

How to override the NSPersistentStoreCoordinator in UIManagedDocument

I have an application that uses Core Data through UIManagedObjectDocument. I am trying to add encryption to the underlying SQLite database by using Encrypted Core Data (https://github.com/project-imas/encrypted-core-data). From the description for ECD, I need to create a new type of NSPersistentSroreCoordinator. However, I cannot seem to do this with a UIManagedObjectDocument since it creates its own internal NSPersistenStoreCoordinator (which s marked private).
I create the database with this line:
UIManagedDocument* managedDoc = [[UIManagedDocument alloc] initWithFileURL:url];
I tried subclassing UIManagedDocument and creating it this way with no luck:
UIManagedDocument* managedDoc = [[EncryptedManagedDocument alloc] initWithFileURL:url];
And my implementation of the class:
#interface EncryptedManagedDocument()
#property (nonatomic,retain,readonly) NSPersistentStoreCoordinator *encryptedStoreCoordinator;
#end
#implementation EncryptedManagedDocument
#synthesize encryptedStoreCoordinator = _encryptedStoreCoordinator;
-(NSPersistentStoreCoordinator*)encryptedStoreCoordinator
{
if (_encryptedStoreCoordinator)
return _encryptedStoreCoordinator;
_encryptedStoreCoordinator = [EncryptedStore makeStore:[self managedObjectModel]:#"SOME_PASSCODE"];
return _encryptedStoreCoordinator;
}
-(NSPersistentStoreCoordinator*)persistentStoreCoordinator
{
return self.encryptedStoreCoordinator;
}
#end
Does anyone know the right way to do this?
Thanks!
I'm going to say this is not possible. UIManagedDocument wraps things up nicely and is a great time saver for the common case, but to enable my scenario I created an EncryptedManagedDocument class that is similar to UIManagedDocument but gave me control to create my own persistent store coordinator.
Thanks, everyone.

Unrecognized selector occurring after Lightweight Core Data migration

I'm getting an unrecognized selector on a newly added field to my Core Data (SQLite) db on iOS. I added a new model version as directed using Xcode's Editor menu, and then verified that new version is the current one. I made sure also that the .h and .m files for the modified table were updated, though I did this by hand (how to have these generated for you?). Nothing unusual though, just a String type field.
The problem seems to be that the lightweight migration never takes place by the time code is run that tries to reference the database object. Tring to access newFieldName gives:
-[MyEntity newFieldName]: unrecognized selector sent to instance 0x852b510
2014-03-27 13:26:21.734 ASSIST for iPad[41682:c07] *** Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason: '-[MyEntity newFieldName]:
unrecognized selector sent to instance 0x852b510'
The line of code that generates the above error is the only line in the for loop below:
DataStoreCoreData *dStore = [[DataStoreCoreData alloc] initWithDataItemDescription:did];
for (MyEntity *myEnt in [dStore objects])
NSString *name = [myEnt newFieldName];
As mentioned, when I examine the SQLite db it has no new field in it, which makes sense given the error. So I also stepped through the execution of the code that is supposed to do the migration and it seems to work fine. Success. It looks like the following:
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"ASSIST.sqlite"]];
// handle db upgrade
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error])
NSLog(#"\r\n Fail. [error localizedDescription]: %#", [error localizedDescription]);
else
NSLog(#"\r\n Success");
This is the 6th db version upgrade. All have been lightweight migrations with no unusual problems. Should not the above code force the SQLite db to reflect the new schema? How do I get it to do so, or is there another problem here?
I finally discovered the answer. The code I provided with the question is only part of the code required to be modified when making minimal changes to the database (lightweight migration). You also need to specify the new version when you create the object model. Notice "MyNewVersion" in the code below. You are supposed to update this parameter to reflect the new version you created and then selected as the current Model Version:
NSString *path = [[NSBundle mainBundle] pathForResource:#"MyNewVersion" ofType:#"mom" inDirectory:#"ASSIST.momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];

AVAudioPlayer EXC_BAD_ACCESS

I am trying to pause the AVAudioPlayer if it is currently playing. When I debug my code and check AVAudioPlayer, I see it is allocated. When I try to access a method/property on it (i.e. myAudioPlayer isPlaying), I get an EXC_BAD_ACCESS error. This happens if the AVAudioPlayer does not have a sound loaded. Is there a way I can do a check to see if it has loaded a sound? I tried accessing myAudioPlayer.data, but I still get the same error.
For me it was because I wasn't calling the one of the designated initialisers. I was instantiating it with AVAudioPlayer() instead of the designated initialisers which are public init(contentsOfURL url: NSURL) throws and public init(data: NSData) throws
As of iOS 13, make sure you are removing the initialization on AVAudioPlayer before asigning it with AVAudioPlayer(contentsOf: URL(...))
i.e. change
var audioPlayer = AudioPlayer() to var audioPlayer: AVAudioPlayer!
I guess you have to use prepareToPlay method to find whether it has loaded or not.
First you have to add AVFoundation Framework.Then,import AVFounadtion framework in .h file.
.h:
AVAudioPlayer *audioPlayer;
.m:
NSURL *url1 = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/audio1.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url1 error:&error];
//audioPlayer.numberOfLoops = 0;
[audioPlayer play];
Try this code it may help you..

sqlite database resets on each compile and run

I have built up a basic database application in which user can insert records onto a table and app displays them on a TableView.
Everything is working as it is supposed to be. For example, the new records do display even if we kill the app from app switcher and relaunch it from the springboard.
BUT every time I build and run using Xcode, the database just goes to default records! The new records are just not there.
Is it normal?... but what if I want to test my app for new records? Any fix?
BTW, JFYI, below is the code I use to make editable DB.
-(NSString *)createEditableDatabase{
// Check if DB already exists
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *writableDB = [documentsDir stringByAppendingPathComponent:#"Database.db"];
success = [fileManager fileExistsAtPath:writableDB];
//The editable DB already exists
if (success) {
return writableDB;
}
//The editable DB does not exist
//Copy the default DB into App's Doc Dir.
NSString *defaultPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Database.db"];
success = [fileManager copyItemAtPath:defaultPath toPath:writableDB error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable DB file: '%#'", [error localizedDescription]);
}
return writableDB;
}
While digging deeper, I noticed that the database modify date in finder was not updating when I inserted a record. So, I found out that I was still using old path to perform DB operations (not the Documents one). :) Now everything working fine. Anyways thanks Nick

Resources