3

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.

Community
  • 1
  • 1
Bazinga
  • 2,456
  • 33
  • 76
  • The array contents need to be NSCoding compliant, but ALAsset is not! You could try to save the Asset URL instead, but be aware that the URL may become unavailable. – Felix Nov 12 '12 at 12:01
  • Was able to save the url, but i need the alasset object – Bazinga Nov 12 '12 at 15:21
  • Use ALAssetsLibrary (assetForURL method) to retrieve the ALAsset from the URL. – Felix Nov 12 '12 at 16:10
  • Yes I was able to retrieve their urls but, I cant return it as the object above. – Bazinga Nov 13 '12 at 05:25

3 Answers3

2

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
Animesh
  • 1,020
  • 9
  • 8
  • What's the problem? did you trying to save Array having UIImage objects? – Animesh Oct 23 '12 at 09:40
  • the array contains an ALAsset object `ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=92A7A24F-D54B-496E-B250-542BBE37BE8C&ext=JPG' – Bazinga Oct 23 '12 at 09:43
  • 1
    I am not sure but may be ALAsset may not be compliant to NSCoding, in that case you have to make a category of ALAsset. I have made UIImage category to make it compliant with NSCoding. I can share that code if you wish. – Animesh Oct 23 '12 at 09:48
  • my question by the way is bounty. – Bazinga Nov 03 '12 at 15:47
1

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

fishinear
  • 6,101
  • 3
  • 36
  • 84
  • by regenerating them with the same query on ALAssetsGroup as you originally used to generate them. – fishinear Nov 06 '12 at 22:07
  • Store the ALAsset's URL (as a string, so it is a property list object), than restore it from the stored URL by using ALAssetsLibrary's assetForURL:resultBlock:failureBlock:. – Matej Bukovinski Nov 07 '12 at 07:34
  • Still dont know how to get it? i really need to retain it. – Bazinga Nov 08 '12 at 02:48
  • @Superman if you want us to help you, you need to do more than give one line reactions. What action is the user supposed to do with the selected assets? Why can you not save the results of that action? Why do you need to retain the ALAssets? Have you tried Matej's method of saving and retrieving? What did not work with that? What part of it do you not understand? – fishinear Nov 08 '12 at 10:41
  • my AGImagePickerController is a multiple image picker, that puts checkmark on image selected then save to NSDocu. But then when app exited, i need to retain the checkmark when app is restarted again. I have tried saving in plist. Never tried the NSCoding, not too familiar with it. – Bazinga Nov 08 '12 at 10:47
  • @Superman Your problem is not saving the data. The methods you show in the question should all work for that (with possibly minor fixes), and saving to NSUserDefaults should also be possible. Your problem is that you have ALAssets that cannot be saved/retrieved in that way. Matej gave you a way around that, but I understand that you do not grasp want he meant. I will extend the answer to include more details about that. – fishinear Nov 08 '12 at 11:02
  • kindly checked this, it is what im using. https://github.com/arturgrigor/AGImagePickerController – Bazinga Nov 08 '12 at 12:13
  • kindly try my answer. I already checked the link you mention before posting my answer the first time. – fishinear Nov 08 '12 at 13:25
  • @Superman: Have you? I will stop answering now. I have spent a lot of my precious time trying to help you. But you give only very limited information of what you want to do. You do not say what part you do not understand. You are not willing to try out any of the answers that I give. You give only one-line reactions. Good luck with your problem. – fishinear Nov 08 '12 at 13:51
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/19287/discussion-between-superman-and-fishinear) – Bazinga Nov 08 '12 at 13:59
  • Note that dictionaries and arrays in property lists must also contain only property values. is the error. – Bazinga Nov 10 '12 at 05:49
0

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]; 
}
  • hi, thanks for the answer. Should i create the default_filename.plist in my app bundle? or no need? and have you tried saving an `ALAsset`? – Bazinga Nov 01 '12 at 11:34
  • you only need the `default.plist` if you have one. eg. if you want to save some standard settings or something. No haven't tested with `ALAsset`, does it work? –  Nov 01 '12 at 12:26
  • so this will create a new plist in the nsdocuments? havent tried it yet. – Bazinga Nov 01 '12 at 12:28
  • yup. It will delete the existing and save the new one. So if you want to apply changes you first have to read the old one, apply the changes and then save the altered plist. –  Nov 01 '12 at 13:25
  • No need to add it in the appbundle right? Sorry if I have too many questions – Bazinga Nov 01 '12 at 14:35
  • Hi, have tried your answer, but when I try to save to NSDocu. It doesnt appear. – Bazinga Nov 05 '12 at 01:54
  • look at the 'searchPath' parameter, it's in `NSCachesDirectory` not documents. But you can change that parameter if you like –  Nov 05 '12 at 06:53
  • Yes, have look up at `NSCachesDirectory` but still no it doesnt exist. – Bazinga Nov 05 '12 at 07:05