Issues using NSIndexPath as key in NSMutableDictionary? - objective-c

Is there any particular reason why attempting to store and retrieve a value in an NSMutableDictionary using an NSIndexPath as a key might fail?
I originally attempted to do this in order to store an NSMutableDictionary of UITableViewCell heights (self.cellHeights) for a UITableView. Each time you tapped a UITableViewCell, that cell would either expand or contract between two different heights based on the value stored in the NSMutableDictionary for that particular indexPath:
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber *heightNSNumber = [self.cellHeights objectForKey:indexPath];
if (!heightNSNumber)
{
heightNSNumber = [NSNumber numberWithFloat:100.0];
[self.cellHeights setObject:heightNSNumber forKey:indexPath];
}
return [heightNSNumber floatValue];
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSNumber *heightNSNumber = [self.cellHeights objectForKey:indexPath];
if (!heightNSNumber)
{
heightNSNumber = [NSNumber numberWithFloat:100.0];
[self.cellHeights setObject:heightNSNumber forKey:indexPath];
}
if ([heightNSNumber floatValue] == 100.0)
{
[self.cellHeights setObject:[NSNumber numberWithFloat:50.0]
forKey:indexPath];
} else {
[self.cellHeights setObject:[NSNumber numberWithFloat:100.0]
forKey:indexPath];
}
[tableView beginUpdates];
[tableView endUpdates];
}
For reasons unknown to me, getting the cell height within tableView:didSelectRowAtIndexPath: via [self.cellHeights objectForKey:indexPath] works just fine. However, trying to get the cell height within tableView:heightForRowAtIndexPath: via [self.cellHeights objectForKey:indexPath] always returns nil because it seems that the indexPath used to store the height doesn't match the indexPath being used to fetch the cell height, even though they have the same values for indexPath.section and indexPath.row. Because of this, a new object for the "same" index path is added to self.cellHeights (as evident since self.cellHeights.count increases thereafter).
This does not happen when you store the cell heights in the NSMutableDictionary using the row ([NSNumber numberWithInteger:indexPath.row]) as the key...so that's what I'm doing for now, but I'd like to understand why indexPath isn't working as the key.

Although I'm late in the discussion, here's a quick and simple solution that will allow you to use NSIndexPath instances as dictionary keys.
Just recreate the indexPath by adding the following line:
indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
VoilĂ . tableView:heightForRowAtIndexPath: uses NSMutableIndexPath instances internally (as you would see with a breakpoint). Somehow those instances seem uncooperative with NSIndexPath when calculating hash keys.
By converting it back to an NSIndexPath, then everything works.

#Jean's answer seems acceptable, but this question has been answered in more detail here. In short, UITableView sometimes uses instances of NSMutableIndexPath instead of NSIndexPath and instances of these two classes are never equal because [NSMutableIndexPath class] != [NSIndexPath class]. The workaround is to always generate a key NSIndexPath for anything that relies on isEqual or hash, such as looking up dictionary keys:
- (NSIndexPath *)keyForIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath class] == [NSIndexPath class]) {
return indexPath;
}
return [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
}

There are several things that must be implemented for an object to work reliably as a key for NSDictionary, namely isEqual:, hash and Copyable protocol.
I am not very sure that NSIndexPath was ever intented to work as a key for dictionaries (because it was made to be an index for arrays).
My guess is that hash is not implemented correctly for different instances of the class. Also note that some of the table delegate methods are called with NSIndexPath and some with NSMutableIndexPath. That's probably making the difference.

Related

Get row from indexPath in RoboVM

I am trying to write the following objective-c code in robovm:
(NSIndexPath *)indexPath = ...;
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
However, when I look thru RoboVM's calls for NSIndexPath here:
https://github.com/robovm/robovm/blob/e60579613140f4a6d48f108042633fbf17b9c289/cocoatouch/src/main/java/org/robovm/apple/foundation/NSIndexPath.java
or by scrolling thru Eclipse's options, I don't see anything corresponding to row. What can I do?
I have no experience with RoboVM. However, indexPath.section and indexPath.row are just convenience methods/properties for
[indexPath indexAtPosition:0] // section
[indexPath indexAtPosition:1] // row
and the indexAtPosition: method is defined in the Java class.

Dictionary retrieval of value for key IOS

I am sorry i am new to IOS,i couldnt figure out the solution to this problem
This is just a beginner restaurant menu
There is a tableview containing items and price and when i click one item,it displays another view where the user has to input the quantity and click a done button,so when the user clicks done i want to multiply the quantity times the price,how do i retrieve that particular price and multiply it with the quantity user input in the textfield.
Here's my code
I have declared NSDictionary in the Menu header file called
NSDictionary *dict;
My viewdidload method
dict=[[NSDictionaryalloc]initWithObjectsAndKeys:
#"TomatoSoup",#"20.00",#"VegManchowSoup",#"12.00",nil];
NSLog(#"%#",dict);
[super viewDidLoad];
I have displayed this contents in a table view
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section{
return [[dict allKeys]count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
NSArray *sortedkeys=[[dict allKeys]sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
NSString *key=[sortedkeys objectAtIndex:indexPath.row];
NSString *value=[dict objectForKey:key];
cell.textLabel.text=value;
cell.detailTextLabel.text=key;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath{
if(indexPath.row==0){
VegQuantity *vegetarian1 = [[VegQuantity alloc] initWithNibName:#"VegQuantity" bundle:nil];
vegetarian1.m_SelectedIndexPath=indexPath.row;
vegetarian1.pass=dict;
[self presentModalViewController:vegetarian1 animated:YES];
}
if(indexPath.row==1){
VegQuantity *vegetarian1 = [[VegQuantity alloc] initWithNibName:#"VegQuantity" bundle:nil];
vegetarian1.m_SelectedIndexPath=indexPath.row;
[self presentModalViewController:vegetarian1 animated:YES];
}
}
VegQuantity.h
There is a View that has a textfield and a button saying done,
now when I click the done button I need to retrieve the value for that particular soup and multiply it with the number of quantity I input.
My problem is how am I supposed to retrieve the price(value) for that particular key and multiply it with the quantity.
dict=[[NSDictionary alloc]initWithObjectsAndKeys:
#"TomatoSoup",#"20.00",#"VegManchowSoup",#"12.00",nil];
The method is initWithObjectsAndKeys, which means first is the object then key, (key "20.00", object - "TomatoSoup") - in your case it's the opposite.
Second, instead of having an NSString for the price (I suppose it's price or quantity) use NSNumber - [NSNumber numberWithFloat:20.0f].
Then, make your VegQuantity view controller (btw it's good idea to call it VegQuantityViewController, in order to keep the naming conventions) 2 properties:
#property (nonatomic, strong) NSString *itemName; //Use strong if using ARC, otherwise retain
#property (nonatomic, strong) NSNumber *price;
and pass those values to the view controller before you show it. Then inside it you can do whatever you want with them.
P.S. It's a good practice to use properties to manipulate the values of the instance variables.
You retrieve a value from a Dictionary by using.
[dict objectForKey:#"someDummyKey"];
But to be honest. You should use a NSMutableArray as datasource for you UITableView and not a NSDictionary.

How can you fill a UITableView with data from two arrays of two different object types?

Currently I fill a UITableView using this method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CharNameCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
MyObject *obj = (MyObject*)[self.characters objectAtIndex:indexPath.row];
cell.textLabel.text = obj.name;
return cell;
}
But what could one do if you had two different arrays from two different types and you wanted to display a property from each in the cells?
Pseudocode:
MyObject1
MyObject2
cellTextLabel.text = Myobject1.name;
cellTextLabel.text = MyObject2.name;
Assuming that each object has a name property. I know my syntax above isn't correct, but I think you should get the drift.
I would suggest then that you store all of your objects in an NSMutableArray. This will be your data model. Then you can just iterate through the array to display the data in the UITableView. If need be, use introspection to find out what kind of class your object is.
id currentObject = [myArray objectAtIndex:indexPath.row];
if ([currentObject isKindOfClass:[MyObject1 class]]){
//set properties or do stuff with MyObject1
}else if ([currentObject isKindOfClass:[MyObject2 class]]){
//do stuff with Object2
}
This is just one suggestion. There are many ways to do this, but it will all depend on your app and what kind of persistence you are using, etc. Hope this helps.

Adding data to a tableView using a NSMutableArray

I'm having a problem adding an item to my tableView.
I used to initialize an empty tableView at the start of my App and get it filled with scanned items every time the tableView reappears and there is an item in my variable.
Initialization of the tableView:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:nil];
self.listArray = array;
TableView Data Source:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.listArray count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if(section == 0)
return #"Eingescannte Artikel:";
else
return nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"testCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
NSUInteger row = [indexPath row];
cell.textLabel.text = [listArray objectAtIndex:row];//[NSString stringWithFormat:#"Das ist Zeile %i", indexPath.row];
return cell;
}
(Not the whole thing but the ones I changed)
As you may have seen I use an NSMutableArray to add items to my tableView.
So if an item ist scanned I'm adding it to my array like this:
[listArray insertObject:sharedGS.strEAN atIndex:0]; //using a shared Instance where I implemented my variable.
I also tried to use an variable to extend my Index every time a new Item is added, but it won't work both ways.
I'm quite new to programming so an not-too-hard-to-understand-answer would be quite nice ;)
If there's any information missing, feel free to ask.
/edit: Trying to specify my question: The data from the variable is written in a TableViewCell, but if I scan another one the other one is just being replaced. Not sure if it's a problem with my array or my tableView...
/edit No.2: Found out(thanks to fzwo) that my array isn't working correctly. It just doesn't grow by an addObject: or insertObject:atIndex: command. But I just don't get why... :(
All I'm doing: [listArray addObject:sharedGS.strEAN]; not that much space for errors in one simple line. Maybe I'm just too stupid to recognize what I'm doing wrong:D
You state that your problem is "adding an item to my tableView" , since you are adding the object to your array i am guessing the problem is that you are not reloading the table or that it is missing the dataSource binding.
You have not actually asked any question (even if you added info to "specify your question") so a wild guess, after
[listArray insertObject:sharedGS.strEAN atIndex:0];
put
[yourTableView reloadData];
Are you intentionally adding new items to the top of the table ? otherwise you could do
[listArray addObject:sharedGS.strEAN]; to add new items to the bottom
Otherwise it's worth noting that you are misusing dequeueReusableCellWithIdentifier, look at the example below for proper usage:
// Try to retrieve from the table view a now-unused cell with the given identifier
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
// If no cell is available, create a new one using the given identifier
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}

EXC_BAD_ACCESS error when removing object from NSMutableDictionary

I have a NSMutableDictionary as the datasource for my UITableView. I am trying to implement the delete mode and having an issue.
I am logging the key I am trying to remove as well as the object that it corresponds to as this issue seems like it might be related to my trying to access unallocated memory or something. Here is my implementation of tableView:commitEditionStyle:forRowAtIndexPath:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source.
NSArray * keys = [userList allKeys];
NSNumber *keyToRemove = [keys objectAtIndex:indexPath.row];
NSLog(#"Key to remove: %#",keyToRemove);
NSLog(#"Object at key: %#",[userList objectForKey:keyToRemove]);
[userList removeObjectForKey:keyToRemove];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[keys release];
[keyToRemove release];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
This method runs and then I get the error. The two NSLog statements output the correct key and it's corresponding value.
Can someone tell me what I'm doing wrong?
Thanks
You don't own keys or keysToRemove, so you should not be releasing them. I strongly suggest reading up on the Cocoa memory management rules.
Here's your problem:
[keys release];
[keyToRemove release];
You are releasing keys and keyToRemove, even though you never allocated it, retained it, or copied it, so it's reference count is decreasing more than it should.
As a general rule, you should only release an object if you called alloc, retain (not init, sorry) or copy on it, I recommend you read on reference counting here: Practical Memory Management