1

The title pretty much says it all. I want to create an NSMutableDictionary where I use UIImageViews to look up boolean values. Here's what it looks like:

My .h file:

@interface ViewController : UIViewController{
    UIImageView *image1;
    UIImageView *image2;
    NSMutableDictionary *isUseable;
}

@property (nonatomic, retain) IBOutlet UIImageView *image1;
@property (nonatomic, retain) IBOutlet UIImageView *image2;
@property (nonatomic, retain) NSMutableDictionary *isUseable;

My .m file:

@synthesize image1, image2;
@synthesize isUseable

- (void)viewDidLoad
{
    isUseable = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                 @NO, image1,
                 @NO, image2,nil];
}

-(void)runEvents{
    if(some condition){
        [isUseable setObject:@YES forKey:(id)image1];
    }

    //Use it later:
    if(isUseable[image1]){
        //Do stuff
    }
}

It compiles but when I run it I get an uncaught exception 'NSInvalidArgumentException', reason: '-[UIImageView copyWithZone:]: unrecognized selector sent to instance.

I'm guessing that the problem lies with the fact that the NSDictionary class copies its keys. Is there a way to get a dictionary working in this case? If not, how should I set up a lookup like the one I want? Any ideas / suggestions?

c31983
  • 449
  • 4
  • 16

5 Answers5

4

Yes, the problem is that keys in an NSDictionary must conform to the NSCopying protocol and UIImage does not.

One solution would be to give each image a unique tag. Then use the image's tag as the key (after wrapping it in an NSNumber).

- (void)viewDidLoad {
    isUseable = [ @{ @(image1.tag) : @NO, @(image2.tag) : @NO } mutableCopy];
}

-(void)runEvents {
    if(some condition) {
        [isUseable setObject:@YES forKey:@(image1.tag)];
    }

    //Use it later:
    if(isUseable[@(image1.tag)]) {
        //Do stuff
    }
}

Just add the code to see each image's tag property.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • Thanks, this helped. I got confused for a while until I realized that I have to set my own tag value (my fault for not reading your reply as carefully as I should have). I kind of like the hash suggestion below too since I wouldn't have to think about coming up with my own ids. – c31983 Feb 18 '13 at 03:30
3

rmaddy said it pretty right: an NSDictionary must conform to the NSCopying protocol and UIImage does not.

I recommend to use @(image.hash) as dictionary key. It's like a fingerprint of UIImage.

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
Ben Lu
  • 2,982
  • 4
  • 31
  • 54
  • Using `hash` as the key may work. Keep in mind that it is possible for two images to have the same `hash` value. Though with only a few images the chances are pretty small. – rmaddy Feb 17 '13 at 22:45
  • It should be noted that `.hash` collisions are fairly common in Objective-c. They do not have the same collision resistance one is used to when dealing with something like SHA256. You can get the SHA256 of a UIImage if you need collision resistance. I've included an answer on how to do that here: https://stackoverflow.com/a/67627996/2057171 – Albert Renshaw May 20 '21 at 21:12
1

Keys in NSDictionaries have to conform to NSCopying and UIImageView doesn't. You will have to find a different key, or you could extend UIImageView to conform to NSCopying. See this answer on SO for how to do it with a UIImage.

Community
  • 1
  • 1
Sebastian
  • 7,670
  • 5
  • 38
  • 50
0

Get the raw data from the image (not a re-rendered PNG or JPG representation) and then run it through built in common-crypto to get SHA256 of the data, this will be your key.

Other answers suggest using the built in instance property .hash but it is prone to collision, and they end up happening frequently (We experienced a string collision in one of our apps and it caused a severe bug).

#include <CommonCrypto/CommonDigest.h>

-(NSString *)keyFromImage:(UIImage *)image {

    NSData *imageData = (__bridge NSData *)CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
    
    unsigned char result[CC_SHA256_DIGEST_LENGTH];
    CC_SHA256((__bridge const void *)imageData, (CC_LONG)imageData.length, result);
    
    NSMutableString *imageHash = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH*2];
    
    for(int i = 0; i<CC_SHA256_DIGEST_LENGTH; i++) {
        [imageHash appendFormat:@"%02x",result[i]];
    }
    
    return imageHash;

}
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
0

Depending on how your object is managed you may be able to use its address in memory as a key (This applies to both UIImageView or UIImage since the general discussion on this question seems to revolve around either/or):

myDictionary[@((intptr_t)myObject)] = myValue;

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
  • I use this in the past when dealing with attaching images to a NSMutableString... I use the pointer to the NSTextAttachment as a key to reference the original HD source image to the thumbnail used in the NSTextAttachment so that I can display LQ image in text and HD image upon tap. Pointer value doesn't change since this all is within one UITextView – Albert Renshaw May 20 '21 at 21:51