Bazinga
Bazinga

Reputation: 2466

Saving in NSDocumentDirectory or NSCachesDirectory

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.

Upvotes: 3

Views: 3911

Answers (3)

fishinear
fishinear

Reputation: 6336

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).

Upvotes: 1

user207616
user207616

Reputation:

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

Upvotes: 0

Animesh
Animesh

Reputation: 1010

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

Upvotes: 2

Related Questions