Using UIImageView as key in NSMutableDictionary to look up boolean - objective-c

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?

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

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.

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

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;

Related

How to initialize the NSArray in ARC?

I tried initializing the array :
In .h file
#property (nonatomic, retain) NSArray *accounts;
In .m file :
#synthesize accounts;
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *arrList = [acAccountStore accountsWithAccountType:accountType];
// This returns array
self.accounts = [NSArray arrayWithArray:arrList]; // I tried debug after
// this and it gives me data in debugger.
// Note array List have 3 data in it.
}
Now On button click i call a method:
- (IBAction) ButtonClicked :(id) sender {
NSLog(#" data : %#",[self.accounts objectAtIndex:0]); // Breaks at this point.
// When i tried with debug it gives me (no Objective-C description available)
}
Is the initialization of array correct Or If the code is not right please let me know.
Main concern is when i do debug in viewDidLoad, the self.accounts show me proper values. But after doing the click event its empty and throws EXEC_BAD_ACCESS error.
Thanks for help in advance
hm looks fine. A couple of questions then:
Where are you calling the self.accounts = [NSArray arrayWithArray:arrList];
I assume that the array is being setup before your button is being pressed?
There's no real reason that arc should be clearing out the variable. Have you set a strong reference to it or a weak one? If you're using self. on a variable, you should have :
#property (nonatomic, strong) NSArray *accounts;
or similar to that in the .h file and then
#synthesize accounts;
in the .m file.
If you've got weak instead of strong then ARC may possibly clear the memory but it still shouldn't.
Update:
Create a property for your account store as well. I had this exact issue recently and this fixed it.
#property (nonatomic, strong) ACAccountStore *accountStore;
Original Answer
Because you're using ARC, you need to change your property declaration from
#property (nonatomic, retain) NSArray *accounts;
to:
#property (nonatomic, strong) NSArray *accounts;
With the latest LLVM compiler, you don't need to synthesize properties either. So you can remove #synthesize accounts.
You should always use defensive coding as well, so in your - buttonClicked: method, you should do:
- (IBAction)buttonClicked:(id)sender {
if (self.accounts) {
NSLog(#"data: %#", [self.accounts objectAtIndex:0]);
}
}
This makes sure that the pointer to the array is valid.
You can also check to make sure an item in an array exists before trying to read it by doing:
- (IBAction)buttonClicked:(id)sender {
if (self.accounts.count > 0)
NSLog(#"data: %#", [self.accounts objectAtIndex:0]);
}
}

Objective-c: Singleton - passing variables

I have a singleton that I'd like to use to manage the onscreen animation of my views. Here's my.
#import <Foundation/Foundation.h>
#interface OAI_AnimationManager : NSObject {
NSMutableDictionary* sectionData;
}
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
.m file
#import "OAI_AnimationManager.h"
#implementation OAI_AnimationManager
#synthesize sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
static OAI_AnimationManager* sharedAnimationManager;
#synchronized(self) {
if (!sharedAnimationManager)
sharedAnimationManager = [[OAI_AnimationManager alloc] init];
return sharedAnimationManager;
}
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", sectionData);
}
#end
You'll see in the .h file I added a NSMutableDictionary and am using #property/#synthesize for it's getter and setter.
In my ViewController I instantiate the animation manager as well as a series of subclasses of UIView called Section. With each one I store the data (x/y w/h, title, etc.) in a dictionary and pass that to the dictionary delcared in animation manager. In the Section class I also instantiate animation manager and add a UITapGestureRecognizer which calls a method, which passes along which section was tapped to a method (checkToggleStatus) in animation manager.
As you can I see in the method I am just logging sectionData. Problem is I am getting null for the value.
Maybe my understanding of singletons is wrong. My assumption was the class would only be instantiated once, if it was already instantiated then that existing object would be returned.
I do need all the other Section classes data as if one animates others animate in response and I can get around it by passing the tapped Section to the animation manager and doing [[Section superview] subviews] and then looping and getting the data from each that way but it seems redundant since that data is available in the ViewController when they are created.
Am I doing something wrong in trying to transfer that data? Is there a better solution? I am open to suggestions and criticisms.
Thanks
h file
#interface OAI_AnimationManager : NSObject
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
m file
static OAI_AnimationManager* _sharedAnimationManager;
#implementation OAI_AnimationManager
#synthesize sectionData = _sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
#synchronized(self) {
if (!_sharedAnimationManager) {
_sharedAnimationManager = [[OAI_AnimationManager alloc] init];
}
}
return _sharedAnimationManager;
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", _sectionData);
}
#end
Notice I moved your sectionData variable from the header and moved it to the implementation file. A while back, they changed it to where you can synthesize properties and specify their instance variable names along side it... hence:
sectionData = _sectionData;
I also added and underscore to the instance variable... this is a universal convention for private variables and it also will throw a compile error now if you try to type just sectionData as you did in the return statement of checkToggleStatus:. Now you either have to type self.sectionData or _sectionData.
You didn't include the code that creates an instance of your dictionary but I bet you didn't set it as self.sectionData = [[NSDictionary alloc] init] which means it would not retain the value and you would get null the next time you called it. Classic memory management mistake... I know it well because I learned the hard way hehehe

iOS: issues with a always null NSString

I have a doubt about initializing string with synthesize keyword.
In my Event.h class I have
#interface Event : NSObject {
NSString *title;
}
#property (nonatomic, retain) NSString *title;
and in Event.h I have
#synthesize title;
However when I want to set the title from my main class and I display the content in the console, I get null:
[self.currentEvent.title stringByAppendingString:#"hello"];
NSLog(#"%#", self.currentEvent.title); //this is null
Is because I don't properly initialize the title variable in Event? Isn't synthesize initializing it for me?
[self.currentEvent.title stringByAppendingString:#"hello"];
You call stringByAppendingString: on a null object (since it was never initialized), so it doesn't do anything. Plus, even if it were to return something, you're not storing the return value anywhere.
if(self.currentEvent.title==nil){
self.currentEvent.title = #"hello";
}
else{
self.currentEvent.title = [self.currentEvent.title stringByAppendingString:#"hello"];
}
#synthesize creates the setter and getter methods for you, but does not initialize
Fastest way to get up to speed with this stuff is to watch "Developing Apps for iOS" by Paul Hegarty / Stanford University, available free on iTunes.
You are not storing the result of your call into a variable. I also suggest using this method since it's a little bit cleaner because you do not need to have an if statement.
[self setTitle:[NSString stringWithFormat:#"hello %#", [self title]]];

How to add and retrieve data from NSDictionary inside a view controller

I'm trying to cache some images in my view controller using NSDictionary but I'm not having much luck.
for starters my .h looks like this
...
NSDictionary *images;
}
#property (nonatomic, retain) NSDictionary *images;
and in my .m I synth the property and attempt to add the image as follows:
[self.images setValue:img forKey:#"happy"];
and later I attempt to grab the image by key
UIImage *image = [self.images objectForKey:#"happy"];
if (!image) {
NSLog(#"not cached");
}else {
NSLog(#"had cached img %#", image);
}
Yet each time I NSLog the dictionary it's null. If I #synthesize the property should I be ready to go out of the box? or did I not add this to the dictionary correctly?
Thank you in advance
Synthesizing doesn't instantiate the variable so you still need to alloc+init it at some point.
But if you want to add objects to a dictionary after creating it, you need to use NSMutableDictionary instead.
Then alloc+init it in viewDidLoad using something like:
self.images = [[[NSMutableDictionary alloc] initWithCapacity:10] autorelease];
Then to set a value, use setObject:forKey: (not setValue:forKey:):
[images setObject:img forKey:#"happy"];
Remember to release the dictionary in dealloc.

NSMutableDictionary not retaining values

I am semi-new to Objective-c and confused with why my NSMutableDictionary is not retaining information. I am declaring my variable in the header file:
#interface view_searchResults : UIViewController <UITableViewDataSource, UITableViewDelegate> {
NSMutableDictionary *imageDicationary;
}
#property (nonatomic, retain) NSMutableDictionary *imageDictionary;
Then in my .m file, I have the following:
#synthesize imageDictionary;
-(UIImage *)getImageForURL:(NSURL*)url {
UIImage*image;
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
[imageDictionary setObject:image forKey:#"test"];
if([imageDictionary objectForKey:#"test"]){
NSLog(#"Exists");
}
}
There is obviously other code to support this, but I can confirm that a URL is being passed, and the file is downloading correctly elsewhere. Also, I can confirm that this function is being executed, and I am not referring to the NSMutableDictionary anywhere else in the document.
Thanks!
Where do you create your NSMutable dictionary? If this really is all the code you have you need to create the dictionary:
#implementation view_searchResults
- (id) init;{
self = [super init];
if(self) {
imageDicationary = [NSMutableDictionary alloc] init]; // should also be released in dealloc.
}
return self;
}
If this is the error then the reason you are not causing a crash is because in objective-C it is valid to send a message to the nil object - it just does nothing.
You havent told us whether the "Exists" NSLog is executed, you also are NOT returning the image.
In other words, I fail to see your problem
Has imageDictionary been initialized? (alloc/init?)