NSMutableDictionary with UIButton* as keys - iPhone development - uibutton

I'm new to iPhone development and I have a question that may have a very simple answer. I am trying to add buttons to a view and these buttons are associated with a custom class that I defined. When I add the buttons to the view, I would like to know what class these buttons correspond to. This is because when I press the button, I need to get some information about the class, but the receiver of the message is another class. I couldn't find information about an error that I'm getting on the web. The problem I have is that I'm trying to create an NSMutableDictionary where the keys are of type UIButton* and the values are of my custom type:
// create button for unit
UIButton* unitButton = [[UIButton alloc] init];
[sourceButtonMap setObject:composite forKey:unitButton];
Of course, the sourceButtonMap is defined in the class and initialized in the init function as sourceButtonMap = [[NSMutableDictionary alloc] init];
The error I get when I try to add the key-value pair is:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[UIButton copyWithZone:]: unrecognized selector sent to instance 0x3931e90'
Is this happening because I can't store UIButton* as keys?
Can anyone point me why I'm getting this error? Thank you all,
aa

One way I found was to use construct an NSValue to use as the key. To create the that use:
[NSValue valueWithNonretainedObject:myButton].
The caveat here seems to be that if the button is garbage collected, the key will hold an invalid reference.
You can get the reference to the UIButton again while looping through the Dictionary like so:
for (NSValue* nsv in myDict) {
UIButton* b = (UIButton*)[nsv nonretainedObjectValue];
...
}

From Apple docs:
The key is copied (using
copyWithZone:; keys must conform to
the NSCopying protocol).
UIButton does not conform to the NSCopying protocol and so you cannot use it as a key in NSDictionary

I've got a cool trick for this.
I cast the pointer to an int (since thats all a pointer really is) and store it in an NSNumber. Using the NSNumber as a key solves this problem and makes sense fundementally because who cares about storing a copy of the button in the dictionary? It makes more sense to me to store a copy of the pointer's info.
If your like me, you'll probably wrap that bit up into a macro as well. Something like this:
#define BOX_AS_NUM(_ptr_) [NSNumber numberWithInt:(int)_ptr_]
Then it's a little cleaner to use in code...
NSDictionary* btnMap = [NSDictionary dictionaryWithObjectsAndKeys:some_obj, BOX_AS_NUM(some_btn), nil];
-(IBAction)someBtnAction:(id)sender
{
SomeObj* obj = [btnMap objectForKey:BOX_AS_NUM(sender)];
[obj doCoolStuffBecuaseIWasJustClicked];
}

UIButtons have a description property that can be used as a dictionary key:
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] initWithCapacity:1];
UIButton *myButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 10.0f, 10.0f)];
id myObject;
[myDictionary setObject:myObject forKey:myButton.description];
// somewhere else in code
id myLookedUpObject = [myDictionary objectForKey:myButton.description];
// do something with myLookedUpObject

Related

I can't add my data object to my NSArray

So I have this method:
-(void)addLaneToRacingLanes:(UITapGestureRecognizer*)sender{
laneDataObject *data=[self.laneDataObjects objectAtIndex:sender.view.tag];
[self.racingLanes addObject:data];
NSLog(#"%i",self.racingLanes.count);
[sender.view setBackgroundColor:[UIColor yellowColor]];
}
It uses the tag from the senders view to find out which data object corresponds to that view.I'm using this to add to my racingLanes which is how I update these views, but my problem is that for some reason I cant add my laneDataObjects to my array racingLanes. Any ideas?
This is how the properties are set up:
#property (strong,nonatomic)NSArray *laneDataObjects;
#property (strong,nonatomic) NSMutableArray *racingLanes;
I have already run through the tags and they all work. The tags work such that lane 1 is tag 0 with its data object at 0, then lane 2 is tag 1 and its data is 1, so on and so forth. I already pre-tested this. And I have checked that both the laneDataObject array has been properly set up. Is it because my racingLanes isn't using a custom getter or setter? How would I go about changing that?
Incase it matters I used
NSLog(#" %i",self.racingLanes.count);
to find out if the array was empty.
It is a near certainty that the racingLanes has not been initialized: since the objects that you are adding are non-nil (you'd see an exception thrown otherwise) the racingLanes must be nil then.
You need to set racingLanes to NSMutableArray in the designated initializer:
_racingLanes = [NSMutableArray array];
Did you make sure to initialize your NSMutableArray in your class's -init or -viewDidLoad function?
// WITH ARC
self.racingLanes = [NSMutableArray array];
// WITHOUT ARC
self.racingLanes = [[NSMutableArray alloc] init];

arraycontroller nsmutablearray add programmatically

I trying to add a data on table view via Array Controller thats bind to a NSMutableArray.
On the IB property it looks like this :
and on the code I tried to add the NSMutableArray dynamically then reload the view, bu nothings happened.
for(int i=0;i<10;i++){
NSMutableDictionary *group = [[NSMutableDictionary alloc]init];
[group setValue:[NSString stringWithFormat:#"%#-%d", #"Group", i] forKey:#"groupname"];
[contentArray addObject:group];
}
[tableContent reloadData];
I have been google it and browse the same question in stackoverflow, not found a useful one.
any idea ?
Thanks
updated
I wrote above code in File's owner class.
I think the problem is that the array needs to send a KVO notification to the array controller (or maybe it's the table view, I'm not sure). The way to do that is:
self.contentArray = contentArray; (or _contentArray if that's what your ivar is called). I'm assuming that contentArray is a property, if not, you should make it one.

Proper Memory Management for Objective-C Method

I'm programming an iPhone app and I had a question about memory management in one of my methods. I'm still a little new to managing memory manually, so I'm sorry if this question seems elementary.
Below is a method designed to allow a number pad to place buttons in a label based on their tag, this way I don't need to make a method for each button. The method works fine, I'm just wondering if I'm responsible for releasing any of the variables I make in the function.
The application crashes if I try to release any of the variables, so I'm a little confused about my responsibility regarding memory.
Here's the method:
FYI the variable firstValue is my label, it's the only variable not declared in the method.
-(IBAction)inputNumbersFromButtons:(id)sender {
UIButton *placeHolderButton = [[UIButton alloc] init];
placeHolderButton = sender;
NSString *placeHolderString = [[NSString alloc] init];
placeHolderString = [placeHolderString stringByAppendingString:firstValue.text];
NSString *addThisNumber = [[NSString alloc] init];
int i = placeHolderButton.tag;
addThisNumber = [NSString stringWithFormat:#"%i", i];
NSString *newLabelText = [[NSString alloc] init];
newLabelText = [placeHolderString stringByAppendingString:addThisNumber];
[firstValue setText:newLabelText];
//[placeHolderButton release];
//[placeHolderString release];
//[addThisNumber release];
//[newLabelText release];
}
The application works fine with those last four lines commented out, but it seems to me like I should be releasing these variables here. If I'm wrong about that I'd welcome a quick explanation about when it's necessary to release variables declared in functions and when it's not. Thanks.
Yes, you need to release them, but you need them just a little longer than beyond the end of your function.
The solution is called autorelease. Just replace release with autorelease and the objects stay around until the program gets back to the runloop.
When the program gets back there, everybody interested in one of the objects should have sent a retain message to it, so the object will not be deallocated when released by the NSAutoreleasePool.
edit actually, looking at your code, there's a lot more wrong with it. E.g. this:
UIButton *placeHolderButton = [[UIButton alloc] init];
placeHolderButton = sender;
doesn't make sense. First you allocate an object, then assign (a pointer to) it to variable placeHolderButton. That's fine.
Then you assign sender to that same variable. The reference to the object you just created is now lost.
Not sure if I get what you want, but this would be better:
-(IBAction)inputNumbersFromButtons:(id)sender {
UIButton *placeHolderButton = sender; // this is still a little useless, but ok
int i = placeHolderButton.tag;
NSString *addThisNumber = [NSString stringWithFormat:#"%i", i];
NSString *placeHolderString = firstValue.text;
NSString *newLabelText = [placeHolderString stringByAppendingString:addThisNumber];
[firstValue setText:newLabelText];
}
No allocs, so no releases necessary. The strings returned by those functions are already added to the autoreleasepool, so they will be deallocated automatically (if needed).
Well. Release them when you are done with them. The sooner the better. Some objects are tricky if you are new to memory management.
Release them in the dealloc method then.
The auto release pool can be handy, some people might disagree according to the performance issues.
you need to release anything containing the word new, alloc/init or copy.
also, you don't need to alloc/init this:
UIButton *placeHolderButton = [[UIButton alloc] init];
placeHolderButton = sender;
another way of doing this is:
UIButton *placeHolderButton = (UIButton *)sender;
in your version, it is allocating an instance with a retain count of +1, but you are immediately replacing the reference, so there is no way of releasing the memory later.
you are creating a lot of instances with alloc/init, and then replacing their references with autoreleased instances.
you could use
NSString *placeHolderString = [placeHolderString stringByAppendingString:firstValue.text];
instead of
NSString *placeHolderString = [[NSString alloc] init];
placeHolderString = [placeHolderString stringByAppendingString:firstValue.text];
which is again replacing a manually managed instance created on the first line, with an autoreleased instance on the second.
infact you could replace every alloc/init in this with the factory method and not have to deal with memory at all in it as they would be autoreleased instances.
-(IBAction)inputNumbersFromButtons:(id)sender {
//cast sender as a UIButton to suppress compiler warning, and allow us to reference it as placeholder button
UIButton *placeHolderButton = (UIButton *) sender;
int i = placeHolderButton.tag;
NSString *addThisNumber = [NSString stringWithFormat:#"%i", i];
[firstValue setText:[firstValue.text stringByAppendingString:addThisNumber]];
}
If you look at the class docs for NSString, any method with a + next to it(ie +stringWithString:(NSString *)string) is a class method, don't use these methods on a reference after you have called alloc/init on it.
I find it puzzling that you use alloc/init on a UIButton.
I always use the factory methods, e.g.
UIButton* aButton = [UIButton buttonWithType:UIButtonTypeCustom];
This returns an autoreleased button which I immediately add to its intended parent view.
Can't confirm it right now, but it looks as if the SDK caches UIButton instances and performs some optimizations behind the scenes. Every time I tried to retain a UIButton ivar, performance has degraded (especially when there is many sub views on screen)

Accessing property inside non-init methods gives bad access

I've begun work on a side project, so the codebase is very small, very little that could go wrong. Something strange is happening. In viewDidLoad I initialise an array set as a property:
#property (nonatomic, retain) NSMutableArray * story_array;
And fill it with data. This printout is fine:
NSLog(#"%#", ((ArticlePreview *)[self.story_array objectAtIndex:0]).article);
I have a gesture recognizer:
UITapGestureRecognizer * openStory = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showStory)];
Tapping on it calls a method whose first line is this (i.e. the same NSLog):
NSLog(#"%#", ((ArticlePreview *)[self.story_array objectAtIndex:0]).article);
But this causes a bad access. Accessing story_array itself is fine (it'll say it has however many ArticlePreview objects inside) but accessing their fields is a no-no.
The story_array is init'ed as follows:
self.story_array = [[NSMutableArray alloc] init];
Assignment to the fields of the ARticle Preview object were not done properly. I had:
someField = someValue;
I needed:
self.someField = someValue;
I still find that a bit crazy, but there you go. Solved.

How to view contents of NSDictionary variable in Xcode debugger?

Is there a way to view the key/value pairs of a NSDictionary variable through the Xcode debugger? Here's the extent of information when it is fully expanded in the variable window:
Variable Value Summary
jsonDict 0x45c540 4 key/value pairs
NSObject {...}
isa 0xa06e0720
I was expecting it to show me each element of the dictionary (similar to an array variable).
In the gdb window you can use po to inspect the object.
given:
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject:#"foo" forKey:#"bar"];
[dict setObject:#"fiz" forKey:#"buz"];
setting a breakpoint after the objects are added you can inspect what is in the dictionary
(gdb) po dict
{
bar = foo;
buz = fiz;
}
Of course these are NSString objects that print nicely. YMMV with other complex objects.
You can right-click any object (ObjC or Core Foundation) variable and select “Print Description to Console” (also in Run->Variables View). This prints the result the obejct’s -debugDescription method, which by default calls -description. Unfortunately, NSDictionary overrides this to produce a bunch of internal data the you generally don’t care about, so in this specific case craigb’s solution is better.
The displayed keys and values also use -description, so if you want useful information about your objects in collections and elsewhere, overriding -description is a must. I generally implement it along these lines, to match the format of the default NSObject implementation:
-(NSString *) description
{
return [NSString stringWithFormat:#"<%# %p>{foo: %#}", [self class], self, [self foo]];
}
You can use CFShow()
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject:#"foo" forKey:#"bar"];
[dict setObject:#"fiz" forKey:#"buz"];
CFShow(dict);
In output you will see
{
bar = foo;
buz = fiz;
}
XCode 4.6 has added the following functionality which may be helpful to you
The elements of NSArray and NSDictionary objects can now be inspected in the Xcode debugger
Now you can inspect these object types without having to print the entire object in the console. Enjoy!
Source: http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/WhatsNewXcode/Articles/xcode_4_6.html
Click on your dict, then click on the little "i" icon, it should do the job :-)
If you would like to print these in a breakpoint action in modern XCode (yes, I am 10 years after the original post!) use the following breakpoint expression in a "Log Message" action:
#myDictionary.description#
Below is a screenshot of my breakpoint action where the variable event is an NSString and the variable contextData is the NSDictionary that I am logging the contents of:
:
You can also use NSLog.
Also you can go in Debug area or xcode, then find out All Variables, Registers, Globals and Statics then select your variable. Right click on it. Then select Print description of "...."
Hope it helps!