Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIImage and NSCoding iOS 5.1

Prior to iOS 5.1 if you wanted to use NSCoding protocols with UIImage you had to do something like this.

@interface UIImage (NSCoding)

-(id)initWithCoder:(NSCoder *)deocder;
-(void)encodeWithCoder:(NSCoder *)encoder;

@end

Then implement it yourself. However with iOS 5.1 this generates a warning "Category is implementing a method which will also be implemented by its primary class" in the .M file for this protocol. Now if I remove this extension class iOS5.1 will be happy, however this should crash and burn on iOS4.0. So what is the best course of action?

like image 453
odyth Avatar asked Mar 08 '12 07:03

odyth


2 Answers

I have the same issue, and since it is too late to use subclass rather than cateogry, I followed zoul's suggestion to use class_addMethod, and below is my implementation:

#import "UIImage-NSCoding.h"
#include <objc/runtime.h>
#define kEncodingKey        @"UIImage"

static void __attribute__((constructor)) initialize() {
    @autoreleasepool {

        if (![[UIImage class] 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.");
                                }

        } 
    }
}

@implementation UIImage(NSCoding)

- (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

So far I have not noticed any further issue. Hope it helps!

[ATTENTION]

If you use this UIImage category in order to archive UIImageViewer objects, beware that NSCoding implementation of UIImageViewer in iOS 5 seems to be broken. The image property of the UIImageViewer is lost after save and load, when it is specified in XIB (I have not tried to see if there is the same issue when UIImageViewer object is being created in code). This has been fixed in iOS 6.

[UPDATES]

I changed my code to add methods in +load instead initialize(), it is still being executed only once, but earlier. My current implementation:

#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
like image 184
szemian Avatar answered Nov 06 '22 16:11

szemian


Instead of using a category on UIImage, wouldn't it be cleaner to subclass it?

Then you could implement initWithCoder and encodeWithCoder and use UIImage's NSCoding implementation on 5.1 and your own on pre-5.1.

like image 38
Sveinung Kval Bakken Avatar answered Nov 06 '22 15:11

Sveinung Kval Bakken