1

I've created a UIImage class that also contains coordinate data of where the image should be drawn:

#import "UIImageExtras.h"
#import <objc/runtime.h>

@implementation UIImage (Extras)

static char UII_ORIGINDATA_KEY;

@dynamic originData;

- (void)setOriginData:(NSValue *)originData {
    objc_setAssociatedObject(self, &UII_ORIGINDATA_KEY, originData, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSValue *)originData {
    return (NSValue *)objc_getAssociatedObject(self, &UII_ORIGINDATA_KEY);
}

All good so far. I have a class containing an array of these such images. The class is called BookDoc and the array is called illustrations. When I copy an instance of BookDoc, I copy the array. The originData I have defined for each image copies fine.

However, when I then save this new copy to file, I lose the originData. This is my save method:

- (void)saveIllustrations {
if (_illustrations == nil) {
    NSLog(@"Nil array");
    return;
}

[self createDataPath];
NSString *illustrationsArrayPath = [_docPath stringByAppendingPathComponent:kIllustrationsFile];
BOOL result = [NSKeyedArchiver archiveRootObject:_illustrations toFile:illustrationsArrayPath];
if (!result)
    NSLog(@"Failed to archive array");

//This is not saving the originData. 

self.illustrations = nil;

}

My question is - how can I ensure that the originData for each image is saved? Many thanks.

UPDATE:

Ok, I've changed to subclassing UIImage in a class called UIImageExtra, and made it conform to NSCoding as follows:

    - (void)encodeWithCoder:(NSCoder *)aCoder {
    NSLog(@"Encoding origin data!");
    [aCoder encodeObject:originData forKey:kOriginData];
    [super encodeWithCoder:aCoder];
}

    - (id)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super initWithCoder:(NSCoder *) aDecoder]) {
            NSLog(@"Decoding origin data");
            self.originData = [aDecoder decodeObjectForKey:kOriginData];
        }
        return self;
    }

Now when I save the illustrations array containing these UIImageExtra instances, shouldn't it automatically save the origin data? My code for saving the array looks like this:

- (void)saveIllustrations {
if (_illustrations == nil) {
    NSLog(@"Nil array");
    return;
}

[self createDataPath];
NSString *illustrationsArrayPath = [_docPath stringByAppendingPathComponent:kIllustrationsFile];
BOOL result = [NSKeyedArchiver archiveRootObject:_illustrations toFile:illustrationsArrayPath];
if (!result)
    NSLog(@"Failed to archive array");

//This is not saving the originData. 

self.illustrations = nil;

}

Smikey
  • 8,106
  • 3
  • 46
  • 74

1 Answers1

3

I don't think you can do it for properties added through associated object API, because UIImage's encodeWithCoder: and initWithCoder: have no idea that your properties are there.

If you could create your custom MyUiImage by inheriting UIImage instead, you would be able to override encodeWithCoder: and initWithCoder: of your class, and encode/decode your properties along with the UIImage ones.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Oh, that's a shame... It's not nearly as convenient using inheritance. But I guess it makes sense, thanks. – Smikey May 02 '12 at 11:16
  • I've updated my answer if you could please take another look? – Smikey May 02 '12 at 12:50
  • @Smikey Your update should work. I assume that you have explicitly adopted the `` protocol in your subclass, right? Also you may want to move `[super encodeWithCoder:aCoder];` ahead of the rest of the code to be consistent with Apple's examples (I do not think that it matters in this case, though). – Sergey Kalinichenko May 02 '12 at 13:20
  • Yep to NSCoding. I added NSLog statements to the encodeWithCoder/initWithCoder messages but they never print. I tried instead: [self createDataPath]; NSString *illustrationsArrayPath = [_docPath stringByAppendingPathComponent:kIllustrationsFile]; NSMutableData *data = [[NSMutableData alloc] init]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [archiver encodeObject:_illustrations forKey:kIllustrationDataKey]; [archiver finishEncoding]; [data writeToFile:illustrationsArrayPath atomically: YES]; – Smikey May 02 '12 at 13:23
  • ...but it still doesn't work. The originData ivar I added always comes up as null. – Smikey May 02 '12 at 13:26
  • @Smikey This is really, really strange. Do you not see `NSLog` for both encoding and decoding?.. Could you try encoding just the first object from the array, and see if you get the logging at least for the `encodeWithCoder:`? – Sergey Kalinichenko May 02 '12 at 13:52
  • Ah, I think I know why. I asked it as a separate question - http://stackoverflow.com/questions/10415111/saving-an-nsarray-of-custom-objects. It's because my array doesn't actually contain UIImageExtra objects because of how I constructed it. Although still not entirely sure how best I should be creating the instances of UIImageExtra... But thanks for the help! – Smikey May 02 '12 at 14:58
  • 1
    @Smikey Ah, I see what's going on. You can define a class method `+(UIImageExtra*)imageNamed:` in your new class, and also override `-(id)imageWithContentsOfFile:`. The `+(UIImageExtra*)imageNamed:` would call `imageWithContentsOfFile:`, which would call `UIImage`'s `imageWithContentsOfFile:`, and then set the extra fields that you have added. – Sergey Kalinichenko May 02 '12 at 15:08
  • Ok I sort of see but am also still a bit confused. Do you mean initWithContentsOfFile not 'image'? Should I not do {+(UIImageExtra *)initWithUIImage:(UIImage *)image} But then I'm not sure what to do with the UIImage I pass in... Or if I override and call {- (id)initWithContentsOfFile:(NSString *)path { UIImage *image = [super initWithContentsOfFile:path]; return image; }} Then I get a compiler warning saying Incompatible pointer types initializing 'UIImageExtra *__strong' with an expression of type 'UIImage *' Gargh I'm so confused o_O – Smikey May 02 '12 at 15:28
  • @Smikey Inside `initWithContentsOfFile` you do `self = [super initWithContentsOfFile:path]; if (self) ...` and so on, following the usual `init` pattern. – Sergey Kalinichenko May 02 '12 at 15:31