I have a database with 10 tables. As I need to access this database in different view controllers, I have to declare the two methods shown below in each of them. Is there a way I can avoid this by declaring these methods in the application delegate. If yes, how can I go about using these methods in different classes.
- (NSString *) getWritableDBPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
return [documentsDir stringByAppendingPathComponent:DATABASE_NAME];
}
-(void)createEditableCopyOfDatabaseIfNeeded
{
// Testing for existence
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:DATABASE_NAME];
NSLog(#"%#",writableDBPath);
success = [fileManager fileExistsAtPath:writableDBPath];
if (success)
return;
// The writable database does not exist, so copy the default to
// the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:DATABASE_NAME];
success = [fileManager copyItemAtPath:defaultDBPath
toPath:writableDBPath
error:&error];
if(!success)
{
NSAssert1(0,#"Failed to create writable database file with Message : '%#'.",
[error localizedDescription]);
}
}
in your view controller first of all create a delegate variable
YourAppDelegate *appDelegate=(YourAppDelegate *)[[UIApplication sharedApplication]delegate];
then u can call any methods that you have define in your delegate
like [appDelegate methodName];
This just screams to be implemented as a separate controller with class level methods. I would highly recommend creating a Database controller with a definition like so:
#interface DatabaseController: NSObject
+ (NSString *) getWritableDBPath ;
+ (void) createEditableCopyOfDatabaseIfNeeded ;
#end
Then in your code using it as so:
#import "DatabaseController.h"
NSString * somePath = [DatabaseController getWritableDBPath];
[DatabaseController createEditableCopyOfDatabaseIfNeeded];
Set them as public, so you can call them with [ ]
You just need to change the minus for +
+(void)createEditableCopyOfDatabaseIfNeeded;
You will need to define a protocol for this class and add a variable of that protocol to the member variable of this class as follows:
The classes where the object is created can either call this method using the object. The Best option is to use the app delegate class to implement these methods.
You can then assign the objects's delegate as the app delegate and call the methods.
#protocol mySqlDelegate ;
#interface mySqlClass {
id <mySqlDelegate> delegate;
}
#property (nonatomic, assign) id <mySqlDelegate> delegate;
#end
#protocol mySqlDelegate
- (void) delegateMethodsForThisClass;
#end
first create a common instance for appdelegate.
otherwise in constant.h file create a instance like
mAppDelegate=(YourAppDelegate*)[[UIApplication sharedApplication] ];
then just import constant.h and you may use mAppdelegate anywhere so using this you easily call
Related
Have tried storing my NSMutableArray's object to NSUserDefaults but, no luck.
My NSMutableArray contains this log right here:
`ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=92A7A24F-D54B-496E-B250-542BBE37BE8C&ext=JPG`
I know that its a ALAsset object, in the AGImagePickerController it is compared as NSDictionary, so what I needed to do is save the NSDictionary or the Array I used to where I store my ALAsset object then save it in either in NSDocu or NSCaches as a file then retrieve it again (This was my idea).
But the problem is,Though I tried this code but not working, and doesn't display anything in NSDocu or NSCache Directories.
First try (info is the one that contains ALAsset object):
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString *filePath = [basePath stringByAppendingPathComponent:#"filename.plist"];
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:filePath];
NSString *error;
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
if(plistData) {
[info writeToFile:filePath atomically:YES];
} else {
NSLog(error);
}
Second try:
- (NSString *)createEditableCopyOfFileIfNeeded:(NSString *)_filename {
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableFilePath = [documentsDirectory stringByAppendingPathComponent: _filename ];
success = [fileManager fileExistsAtPath:writableFilePath];
if (success) return writableFilePath;
// The writable file does not exist, so copy the default to the appropriate location.
NSString *defaultFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: _filename ];
success = [fileManager copyItemAtPath:defaultFilePath toPath:writableFilePath error:&error];
if (!success) {
NSLog([error localizedDescription]);
NSAssert1(0, #"Failed to create writable file with message '%#'.", [error localizedDescription]);
}
return writableFilePath;
}
Save it this way:
NSString *writableFilePath = [self createEditableCopyOfFileIfNeeded:[NSString stringWithString:#"hiscores"]];
if (![info writeToFile:writableFilePath atomically:YES]){
NSLog(#"WRITE ERROR");
}
Third try:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:??????];
[info writeToFile:filePath atomically:YES];
Fourth try(Unsure of because of its modifying in the appbundle):
https://stackoverflow.com/a/6311129/1302274
Is there other way? Hope someone would guide me.
You can store your NSMutableArray to NSUserDefault by archiving it to NSData and than retrieving it by Unarchiving it back to NSMutableArray.
-(NSData*) getArchievedDataFromArray:(NSMutableArray*)arr
{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
return data;
}
-(NSMutableArray*) getArrayFromArchievedData:(NSData*)data
{
NSMutableArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return arr;
}
For saving array to NSUserDefault :
[[NSUserDefaults standardUserDefaults] setObject:[self getArchievedDataFromArray: yourArray] forKey:#"YourKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
For retrieving array back from NSUserDefault :
NSMutableArray *yourArray = [self getArrayFromArchievedData:[[NSUserDefaults standardUserDefaults]objectForKey:#"YourKey"]];
Also you can save Array in form of NSData to a file in NSDocumentDirectory or NSCachesDirectory. Hope this helps....
Edited: An UIImage+NSCoding category
.h file
#import <UIKit/UIKit.h>
#interface UIImage (NSCoding)
- (id) initWithCoderForArchiver:(NSCoder *)decoder;
- (void) encodeWithCoderForArchiver:(NSCoder *)encoder ;
#end
.m file
#import "UIImage+NSCoding.h"
#import <objc/runtime.h>
#define kEncodingKey #"UIImage"
#implementation UIImage (NSCoding)
+ (void) load
{
#autoreleasepool {
if (![UIImage conformsToProtocol:#protocol(NSCoding)]) {
Class class = [UIImage class];
if (!class_addMethod(
class,
#selector(initWithCoder:),
class_getMethodImplementation(class, #selector(initWithCoderForArchiver:)),
protocol_getMethodDescription(#protocol(NSCoding), #selector(initWithCoder:), YES, YES).types
)) {
NSLog(#"Critical Error - [UIImage initWithCoder:] not defined.");
}
if (!class_addMethod(
class,
#selector(encodeWithCoder:),
class_getMethodImplementation(class, #selector(encodeWithCoderForArchiver:)),
protocol_getMethodDescription(#protocol(NSCoding), #selector(encodeWithCoder:), YES, YES).types
)) {
NSLog(#"Critical Error - [UIImage encodeWithCoder:] not defined.");
}
}
}
}
- (id) initWithCoderForArchiver:(NSCoder *)decoder {
if (self = [super init]) {
NSData *data = [decoder decodeObjectForKey:kEncodingKey];
self = [self initWithData:data];
}
return self;
}
- (void) encodeWithCoderForArchiver:(NSCoder *)encoder {
NSData *data = UIImagePNGRepresentation(self);
[encoder encodeObject:data forKey:kEncodingKey];
}
#end
The documentation of NSArray for the "writeToFile:atomically:" method, shows that all members must be property list objects. ALAsset is not a property list object, so writing that to a file is not going to work.
I know that its a ALAsset object, in the AGImagePickerController it is
compared as NSDictionary
If you looked carefully then you would have seen that it does not compare ALAsset's, but their 'ALAssetPropertyURLs' property. The value of that property is an NSDictionary.
As ALAsset does not have a public constructor, there is no way you can reconstruct it after reading from a file or NSUserDefaults, even if you manage to write it.
So the best thing you can do is to re-fetch the ALAssets from the source that you originally got them from. I assume that is an ALAssetsGroup? Instead of saving to file and retrieving again, why don't you just regenerate them with the same query on ALAssetsGroup as you originally used to generate them?
EDIT:
So you say you got the original ALAsset's from an AGImagePickerController. In order to store them, you can take Matej's advice in the comments and store the URLs that identify them.
But keep in mind that AGImagePickerController is a means for the user to pick a number of photos and then do something with them. That is, the ALAssets are simply intermediare results pointing to the original locations of the photos. If you store the URL's and retrieve them later, there is no guarantee at all that the originals are still there.
So ask yourself: what is it that you want the user to do with the photos, and store the result of that action, rather than the assets themselves. For example, one reasonable action you could do is to create a new ALAssetGroup (with the addAssetsGroupAlbumWithName: method on ALAssetsLibrary), and store the assets in there. ALAssetGroups are automatically saved, so you don't need to do anything yourself for that.
EDIT 2 - after more information from the OP
What Matej hints at in the comments, is to convert the array of ALAssets that you have into an array of dictionaries by retrieving the urls from the assets. As you can read in the ALAsset class documentation you can do that in the following way:
NSArray *assetArray = // your array of ALAssets
NSMutableArray *urls = [NSMutableArray arrayWithCapacity:assetArray.count];
for( ALAsset *asset in assetArray ) {
NSDictionary *urlDictionary = [asset valueForProperty:#"ALAssetPropertyURLs"];
[urls addObject:urlDictionary];
}
The resulting array of dictionaries you can save in any way you like.
After restart of your app, you read the array of dictionaries back from where you stored it. Then Matej suggests to use ALAssetsLibrary's assetForURL:resultBlock:failureBlock: to recreate the ALAssets. But as we now know you want to put a checkmark on the original assets again, it is better to fetch the original array of ALAssets, and check whether any of them are present in the recovered urls. The following should work for that:
NSArray *assetArray = // the full array of ALAssets from AGImagePickerController
NSArray *urls = // the recovered array of NSDictionaries
for( ALAsset *asset in assetArray ) {
NSDictionary *urlDictionary = [asset valueForProperty:#"ALAssetPropertyURLs"];
if( [urls containsObject:urlDictionary] ) {
... // set checkmark on asset
}
}
This assumes the original assets have not changed, which is not under your control (the user has removed/added photos, for example).
This is the method I use for storing array or dictionary objects.
- (NSArray*)readPlist
{
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *plistPath = [[documentPaths lastObject] stringByAppendingPathComponent:#"filename.plist"];
NSFileManager *fMgr = [NSFileManager defaultManager];
if (![fMgr fileExistsAtPath:plistPath]) {
[self writePlist:[NSArray array]];
}
return [NSArray arrayWithContentsOfFile:plistPath];
}
- (void)writePlist:(NSArray*)arr
{
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *plistPath = [[documentPaths lastObject] stringByAppendingPathComponent:#"filename.plist"];
NSFileManager *fMgr = [NSFileManager defaultManager];
if ([fMgr fileExistsAtPath:plistPath])
[fMgr removeItemAtPath:plistPath error:nil];
[arr writeToFile:plistPath atomically:YES];
}
I'm an Objective-C newbie and I'm studying iPhone programming.
In my appDelegate, in the -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method, I've a class member (#syntethized) called databasePath.
I set its value this way:
databasePath = [self copyDatabaseToDocuments];
I copied the entire copyDatabaseToDocuments method from a wonderful book by Alasdair Allan and made very little changes (the name of the db is the only thing I changed):
-(NSString *)copyDatabaseToDocuments{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath=[paths objectAtIndex:0];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"myDb.sqlite"];
//
if(![fileManager fileExistsAtPath:filePath]){
NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"myDb.sqlite"];
[fileManager copyItemAtPath:bundlePath toPath:filePath error:nil];
}
return filePath;
}
I NSLog the databasePath and I regularly get its value (it is a string path and it is not null) after the assignment.
Then, I have a method -(NSMutableArray*)readDatabase:(char*)querySQL I call from a ViewController through a delegate reference.
Anything works fine if -inside this last method- I assign again the value of databasePath.
But, if I don't assign it again AND I want to use its value (that I suppose it was set in the -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method) the app crashes.
Why?
Make sure that your #property for databasePath looks like this:
#property (nonatomic, retain) NSString *databasePath;
And then set it in this way:
self.databasePath = [self copyDatabaseToDocuments];
It is probably crashing because copyDatabaseToDocuments returns an autoreleased string, and unless you use the self. notation to set databasePath, that autoreleased string can go away at any time.
At a guess, since you don't show the relevant code, you are not retaining the value of databasePath. You assign it directly to the ivar in your code sample above, yet the method you show returns an autoreleased string.
I'll guess your property is defined as retain or copy. You should therefore set the value as
self.databasePath = [self copyDatabaseToDocuments];
This will then retain or copy the value for you. Synthesizing property accessors does you no good if you don't use them!
I am probably not seeing something here, that is why I am asking for help :)
Here is the deal I have a NSMutable array of items that fulfill the NSCoding protocol, but NSKeyedArchiver always fails to archive it... here is my object implementation:
#implementation YTVideo
#synthesize URL,thumb,titulo;
#pragma mark NSCoding
#define kTituloKey #"titulo"
#define kURLKey #"URL"
#define kThumbKey #"thumb"
-(id)initWithData:(NSString *)ktitle :(UIImage *)kThumb :(NSURL *)kURL{
self.titulo = ktitle;
self.thumb = kThumb;
self.URL = kURL;
return self;
}
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:titulo forKey:kTituloKey];
[encoder encodeObject:URL forKey:kURLKey];
NSData *thumbData = UIImagePNGRepresentation(thumb);
[encoder encodeObject:thumbData forKey:kThumbKey];
}
- (id)initWithCoder:(NSCoder *)decoder {
NSString* ktitulo = [decoder decodeObjectForKey:kTituloKey];
NSURL* kURL = [decoder decodeObjectForKey:kURLKey];
NSData* kThumbdata = [decoder decodeObjectForKey:kThumbKey];
UIImage* kThumb=[UIImage imageWithData:kThumbdata];
return [self initWithData:ktitulo:kThumb:kURL];
}
#end
During the program execution I have a NSMutable array of those objects called videosArray.
then, eventually, I try:
NSString* path =[NSHomeDirectory() stringByAppendingPathComponent:#"teste.wrapit"];
NSLog(#"PATH =%#",path);
bool teste = [NSKeyedArchiver archiveRootObject:videosArray toFile:path];
NSLog(#"aramzenamento:%#",teste ? #"sucesso!" :#"Nope");
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:path];
NSLog(#"Arquivo armazenado existe?%#",fileExists ?#"Sim":#"Nao");
And I always get a fail on my boolean checks...
Any Ideas where I am completely wrong??
Thanks!!
The problem you're experiencing has nothing to do with NSKeyedArchiver. By the looks of it, you're trying to archive your object at the root-level of your sandbox (the directory returned by NSHomeDirectory()). Try replacing the first line of the second block of code with
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
path = [path stringByAppendingPathComponent:#"teste.wrapit"];
Another (perhaps cleaner) way to get the path of your Documents folder is to use the C function NSSearchPathForDirectoriesInDomains, which returns an array of paths that match the first argument:
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"teste.wrapit"];
It's worth pointing out that, on iOS, the function NSSearchPathForDirectoriesInDomain is guaranteed to return an NSArray with a single element when you use a built-in constant (like NSDocumentDirectory) for the first argument, so you can safely use objectAtIndex:0 on the array.
I'm trying to use the following code to persist the current list of local notifications. NSArray explicitly lists the kind of objects it will work with, which implies I can not use this with an array full of UILocalNotification objects. However, UILocalNotifications does implement NSCoding, which led me to believe there must be an easy way to serialize/deserialize this list of objects. Do I need to do the encoding and file persistence myself? Also, is there a way to get more information about why the write failed?
- (NSString*)getSavedNotifsPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingString:#"saved_notifs.plist"];
}
- (void)prepareToHide {
UIApplication* app = [UIApplication sharedApplication];
NSArray *existingNotifications = [app scheduledLocalNotifications];
if (! [existingNotifications writeToFile:[self getSavedNotifsPath] atomically:NO] ) {
// alert
[self showSomething:#"write failed"];
}
}
First, change the code
return [documentsDirectory stringByAppendingString:#"saved_notifs.plist"];
to
return [documentsDirectory stringByAppendingPathComponent:#"saved_notifs.plist"];
stringByAppendingPathComponent: will ensure a slash (/) is included, if needed, before the file name.
NSArray can only save property list objects, which UILocalNotification is not. Instead, try using NSKeyedArchiver. For example:
- (void)prepareToHide {
UIApplication* app = [UIApplication sharedApplication];
NSArray *existingNotifications = [app scheduledLocalNotifications];
NSString *path = [self getSavedNotifsPath];
BOOL success = [NSKeyedArchiver archiveRootObject:existingNotifications toFile:path];
if (! success ) {
// alert
[self showSomething:#"write failed"];
}
}
Use NSKeyedUnarchiver to retrieve the array from the saved file.
Note: I have not actually tested this so I'm not 100% sure it will work. But give it a try and see what happens.
Can anyone please tell me how to write NSMutableArray of custom class objects to file?
below is the code which i am using to write my array "medicationsArray" to file.
medicationsArray contains the objects of below medicationData class
#interface medicationData: NSObject {
NSString *drName;
NSString *date;
NSString *description;
}
#end
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentDir = [path objectAtIndex:0];
NSString *pathWithFileName;
NSString *pathWithFileName = [NSString stringWithFormat:#"%#/medicationsArray",documentDir];
[medicationsArray writeToFile:pathWithFileName atomically:NO];
by using this code i am not able to create a file.
can anyone help me in this, thanks in advance
Instead of constructing the pathWithFileName "manually" you should use the stringByAppendingPathComponent: method instead:
NSString *pathWithFileName = [documentDir stringByAppendingPathComponent:#"medicationsArray"];
This will take care of any extra slashes, etc. in the path. This might be the reason your path may be wrong. I presume that the extra declaration of pathWithFileName in your snippet is just a typo.
Thank you for the reply Claus,
Now i am able to store the data, i am using NSArchiver and NSUnArchiver.
below is the code please find.
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentDir = [path objectAtIndex:0];
NSString *pathWithFileName;
pathWithFileName = [NSString stringWithFormat:#"%#/medicationsArray",documentDir];
NSData *savedData = [NSKeyedArchiver archivedDataWithRootObject:appDelegate.medicationsArray];
[savedData writeToFile:pathWithFileName atomically:YES];
to extract the data
if([[NSFileManager defaultManager] fileExistsAtPath:pathWithFileName])
{
NSData *savedData = [[NSData dataWithContentsOfFile:pathWithFileName] retain];
appDelegate.medicationsArray = [[NSKeyedUnarchiver unarchiveObjectWithData:savedData] retain];
[objMedicationDetailsTVController.tableView reloadData];
}