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

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.

Related

Creating a copy of an NSMutableArray with custom objects

I've been searching around for the solution to this for quite a while, and none of the other threads have helped. Basically, what I want to accomplish is creating an array of custom objects as a singleton, load them into my level, then create a copy of them since variables assigned to these objects will be manipulated. When the level is complete (or failed) though, I want these objects to remain the same so I can reload them.
Below are some things I've tried.
- (void)spawnStartTiles {
//where _puzzleGridTilesArray and curLevel.gridTiles are NSMutableArrays
[_puzzleGridTilesArray removeAllObjects];
_puzzleGridTilesArray = [curLevel.gridTiles mutableCopy];
CCLOG(#"tile in curlevel %#", curLevel.gridTiles[0]); //want these to log DIFFERENT objects
CCLOG(#"tile in puzzle array %#", _puzzleGridTilesArray[0]);//want these to log DIFFERENT objects
}
The above logs the same object ID.
- (void)spawnStartTiles {
//where _puzzleGridTilesArray and curLevel.gridTiles are NSMutableArrays
_puzzleGridTilesArray = [self cloneArray:curLevel.gridTiles];
CCLOG(#"tile in curlevel %#", curLevel.gridTiles[0]); //want these to log DIFFERENT objects
CCLOG(#"tile in puzzle array %#", _puzzleGridTilesArray[0]);//want these to log DIFFERENT objects
}
-(NSMutableArray*)cloneArray:(NSMutableArray *)myArray {
return [[NSMutableArray alloc] initWithArray: myArray];
}
Still logs same object ID.
- (void)spawnStartTiles {
//where _puzzleGridTilesArray and curLevel.gridTiles are NSMutableArrays
_puzzleGridTilesArray = [[NSMutableArray alloc] initWithArray:curLevel.gridTiles copyItems:YES];
CCLOG(#"tile in curlevel %#", curLevel.gridTiles[0]); //want these to log DIFFERENT objects
CCLOG(#"tile in puzzle array %#", _puzzleGridTilesArray[0]);//want these to log DIFFERENT objects
}
The above gives a runtime error. I think this is because the objects I am copying are a custom class named Tile. The class is a CCNode, and the .h file is below.
#import "CCNode.h"
#interface Tile : CCNode
#property (nonatomic, assign) NSInteger value;
#property (nonatomic, assign) NSInteger gemLevel;
#property (nonatomic, assign) BOOL mergedThisRound;
- (void)updateValueDisplay:(BOOL)bannerTiles difficultyMode:(int)difficultyMode;
- (void)updateOpacity:(NSInteger)opacityVariable;
- (void)tileHasBeenSelected:(BOOL)tileHasBeenTouched;
#end
Is there a way to somehow transform this class so that is can be copied? I've looked at http://www.techotopia.com/index.php/Copying_Objects_in_Objective-C and Implementing NSCopying and I'm still confused, so further help would be greatly appreciated. Thank you!
You get an error because [[NSMutableArray alloc] initWithArray:curLevel.gridTiles copyItems:YES] calls copyWithZone: on each item in the array. Your items must conform to NSCopying for you to use this method, but this is the correct approach.
To implement NSCopying:
Update your Tile class to add the protocol, i.e. #interface Tile : CCNode <NSCopying>
Implement the method - (id)copyWithZone:(NSZone *)zone in your Tile implementation.
In that method, allocate a new instance of Tile and assign all of it's properties to that of your current instance.

Using UIImageView as key in NSMutableDictionary to look up boolean

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;

NSMutableArray keeps returning null

I have declared a property NSMutableArray in the header file. Then I alloc, init it in the viewDidLoad method. But when I try to add an object to the array in a different method it keeps returning (null). Am I doing some obvious mistake? Please ask if you want to see some more code.
.h
#property (strong, nonatomic) NSMutableArray *myList;
.m
- (void)viewDidLoad
{
self.dataController = [[DataController alloc]init];
self.myList = [[NSMutableArray alloc] init];
[super viewDidLoad];
}
[...]
NSDictionary *myObject = [self.dataController.objectList objectAtIndex:r];
[[cell textLabel]setText:[myObject objectForKey:#"title"]];
[self.myList addObject:myObject];
NSLog(#"myList %#",self.myList);
NSLog(#"myObject %#",myObject);
The output prints myObject but self.myList keeps returning (null). Appreciate all help!
Edit: Fixed, thank you for your answers!
Not sure where you are using this array. If you have to use this array before viewDidLoad is called, you can do it as,
NSDictionary *myObject = [self.dataController.objectList objectAtIndex:r];
[[cell textLabel]setText:[myObject objectForKey:#"title"]];
if (!self.myList)
self.myList = [[NSMutableArray alloc] init];//for the first time, this will initialize
[self.myList addObject:myObject];
Since you are using [cell textLabel] I am assuming that you are doing this in one of the table view delegates. In that case check if you are setting self.myList = nil; any where in the class.
In your posted code i see no error.
Set a breakpoint and look if the code where you init the array is called first.
I bet that viewDidLoad hasn't been called yet when the NSLogs are execute.
To ensure that the array has been initialized, try putting the initialization in your init method.

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

Objects not being added to NSMutableArray Objective -C

I'm trying to simply add objects to a mutable array but they WILL NOT insert. I'm not getting errors or anything and I can't figure out whats going on.
In my main delegate file I split an array into 4 separate strings like so.
NSArray *split=[currentParsedCharacterData componentsSeparatedByString:#"|"];
NSLog([split objectAtIndex:3]);
NSString *date=[split objectAtIndex:0];
NSString *venue=[split objectAtIndex:1];
NSString *event=[split objectAtIndex:2];
NSString *city=[split objectAtIndex:3];
I've traced out the string values and they are definitely there.
Up next I try to add these string values to mutable arrays
[self.promoTabOptionEvents.dates addObject:date];
[self.promoTabOptionEvents.venues addObject:venue];
[self.promoTabOptionEvents.event addObject:event];
[self.promoTabOptionEvents.city addObject:city];
When I check the arrays in the debugger they are empty. What am I doing wrong?
promoTabOptionEvents class looks like this
import
#interface PromoTabOptionEvents : UIViewController {
NSString *event_headline;
NSMutableArray *dates;
NSMutableArray *venues;
NSMutableArray *event;
NSMutableArray *city;
}
#property(nonatomic,retain)NSString *event_headline;
#property(nonatomic,retain)NSMutableArray *dates;
#property(nonatomic,retain)NSMutableArray *venues;
#property(nonatomic,retain)NSMutableArray *event;
#property(nonatomic,retain)NSMutableArray *city;
-(void)applyLabels;
-(id)initWithTabBar;
#end
#import "PromoTabOptionEvents.h"
#implementation PromoTabOptionEvents
#synthesize event_headline;
#synthesize dates;
#synthesize venues;
#synthesize event;
#synthesize city;
-(id) initWithTabBar {
if ([self init]) {
//this is the label on the tab button itself
//self.title = #"Tab1";
//use whatever image you want and add it to your project
self.tabBarItem.image = [UIImage imageNamed:#"events.png"];
// set the long name shown in the navigation bar
self.navigationItem.title=#"Events";
CGRect bgframe;
bgframe.size.width=320; bgframe.size.height=460;
bgframe.origin.x=0; bgframe.origin.y=0;
UIImage* bgimage = [UIImage imageNamed:#"eventsbig.png"];
UIImageView *imagebgview = [[UIImageView alloc] initWithImage: bgimage];
imagebgview.frame=bgframe;
imagebgview.contentMode=UIViewContentModeScaleAspectFit;
self.view.backgroundColor=[UIColor whiteColor];
[self.view addSubview:imagebgview];
}
return self;
}
Can you add the code where you initialize your NSMutableArray instances? I think you might have forgotten to initialise the arrays and your addObject calls are being swallowed up with no effect.
Are you instantiating the properties anywhere, and if so, have you debugged through to verify that is the case? Otherwise you may be sending messages to nil, which will have no effect. Alternatively, you may be doing the array creation after this call, which would make it look like they're not added.