I have a problem with retaining data in a nested NSDictionary. Or is it something with NSMutableDictionary that will make this work? Take a look, I will try to explain as good as possible.
My .h file looks like:
#interface MyViewController : UIViewController
<UITableViewDataSource, UITableViewDelegate>
{
NSDictionary *fullData;
IBOutlet UITableView *tableView;
}
#property (nonatomic, retain) NSDictionary *fullData;
#property (nonatomic, retain) UITableView *tableView;
#end
I set up my inits in viewDidLoad
- (void)viewDidLoad {
...
fullData = [NSDictionary dictionaryWithContentsOfURL:url];
[fullData retain];
[super viewDidLoad];
}
And this works fine when I try to insert it into UITableViewCells and whatever I need to do, even if I do an NSLog that prints fullData here, all of the data will be displayed.
like so:
2010-11-24 14:49:53.334 MyApp[25543:40b] {
items = (
{
id = 5;
localId = 1;
name = "A name1";
},
{
id = 8;
localId = 3;
name = "A name2";
},
{
id = 9;
localId = 4;
name = "A name3";
},
{
id = 10;
localId = 5;
name = "A name4";
},
{
id = 11;
localId = 6;
name = "A name5";
}
);
results = 5;
}
Although this works perfectly I want to keep the fullData in my other events, for example didSelectRowAtIndexPath. First of all, I have to retain the , and also if I do, only the first level data will be retained. the dict items will just point to some memory which doesn't exist.
So I try:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Data: %#", fullData);
}
And this sometimes returns this:
2010-11-24 14:44:28.288 MyApp[25493:40b] Data: {
items = (
"<_NSIndexPathUniqueTreeNode: 0x9d2a820>",
"<CALayer: 0x9d27980>",
"<CALayer: 0x9d2bc30>",
"<CALayer: 0x9d29830>",
"<CALayer: 0x9d299e0>"
);
results = 5;
}
It seems like some values are retained, however the data inside items are not possible to access. Is it better for me to store the data to a local file and then access it again at this point, or should be it possible to retain the full dictionary?
I tried adding [[fullData objectForKey:#"items"] retain];
I am pretty new to this, so I need help to make my code follow best practices as well. I have tried many solutions and watched several movies from apple and other places too. I just can't solve it. It might be simple but I dont know where to look.
Thank you.
I am sorry, I did solve the issue myself
I didn't include this function:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Retain count: %i", [fullData retainCount]);
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"cell"];
// create a cell
if( cell == nil )
{
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"cell"];
}
// fill it with content
NSArray *current = [[fullData objectForKey:#"items"] objectAtIndex:indexPath.row];
NSString *rowLabel = [NSString stringWithFormat:#"%#, %#",[current valueForKey:#"localId"], [current valueForKey:#"name"]];
cell.textLabel.text = rowLabel;
[current release];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// return it
return cell;
}
And the issue is that I release the current variable each row in the table. This should be a result of that the instance in the variable current is not a copy, instead it is the real reference.
Thanks anyways.
fullData = [NSDictionary dictionaryWithContentsOfURL:url];
is auto-released. you shouldn't retain it. Use this:
self.fullData = [[NSDictionary alloc] initWithContentsOfURL:url];
And I don't think you need to retain it for your purposes now. Only if you need to access it after MyViewController is released.
Are you releasing the UITableView? It may go through and release it's cells which are in your NSDictionary. W/o more code, I can't tell.
You'll need to post some more code in order to track down the solution. The NSDictionary retains its contents, so unless you are over-releasing the objects somewhere else, retaining the NSDictionary will be sufficient.
I am sorry, I did solve the issue myself
I didn't include this function:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Retain count: %i", [fullData retainCount]);
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"cell"];
// create a cell
if( cell == nil )
{
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"cell"];
}
// fill it with content
NSArray *current = [[fullData objectForKey:#"items"] objectAtIndex:indexPath.row];
NSString *rowLabel = [NSString stringWithFormat:#"%#, %#",[current valueForKey:#"localId"], [current valueForKey:#"name"]];
cell.textLabel.text = rowLabel;
[current release];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// return it
return cell;
}
And the issue is that I release the current variable each row in the table. This should be a result of that the instance in the variable current is not a copy, instead it is the real reference.
Thanks anyways.
Related
A lot of the methods have deprecated in iOS 7 in order to set font, textLabel, and color for UITableView cells. I'm also just having a difficult time populating the view with these values. Here's a snippet of my code:
- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray* jobs = [json objectForKey:#"results"];
for(NSDictionary *jobsInfo in jobs) {
JobInfo *jobby = [[JobInfo alloc] init];
jobby.city = jobsInfo[#"city"];
jobby.company = jobsInfo[#"company"];
jobby.url = jobsInfo[#"url"];
jobby.title = jobsInfo[#"jobtitle"];
jobby.snippet = jobsInfo[#"snippet"];
jobby.state = jobsInfo[#"state"];
jobby.time = jobsInfo[#"date"];
jobsArray = [jobsInfo objectForKey:#"results"];
}
}
I am looping through an array of dictionaries from a GET request and parsed. I am now attempting to fill my UITableView with the following code:
-
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [jobsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSDictionary *jobsDic = [jobsArray objectAtIndex:indexPath.row];
[cell.textLabel setText:[jobsDic objectForKey:#"jobtitle"]];
return cell;
}
Also, I have declared this is in my .h file:
NSArray *jobsDic;
Any ideas on what I'm doing wrong? Is this an iOS 7 problem?
It seems that you reinitialize jobsarray at the end of the forin loop.
You didn't mean ?
NSArray* jobs = [json objectForKey:#"results"];
NSMutableArray *jobsTemp = [[NSMutableArray alloc] initWithCapacity:jobs.count];
for(NSDictionary *jobsInfo in jobs) {
JobInfo *jobby = [[JobInfo alloc] init];
jobby.city = jobsInfo[#"city"];
jobby.company = jobsInfo[#"company"];
jobby.url = jobsInfo[#"url"];
jobby.title = jobsInfo[#"jobtitle"];
jobby.snippet = jobsInfo[#"snippet"];
jobby.state = jobsInfo[#"state"];
jobby.time = jobsInfo[#"date"];
[jobsTemp addObject:jobby];
}
self.jobsArray = jobsTemp; //set #property (nonatomic, copy) NSArray *jobsArray; in the .h
[self.tableView reloadData]; //optional only if the data is loaded after the view
In the cell for row method :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
JobInfo *job = self.jobsArray[indexPath.row];
cell.textLabel.text = job.title;
return cell;
}
And don't forget :
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.jobsArray.count;
}
Update - an user suggested an edit :
It's true that count isn't a NSArray property. But as Objective-C lets us use a shortcut notation for calling method with a dot, this code works. You have to know limitation of this : if you use a NSArray subclass with a count property with a custom getter this could have side effects #property (nonatomic, strong, getter=myCustomCount) NSUInteger count. As I think code readability is to me one of most important things I prefer to use dot notation. I think Apple SHOULD implement count as readonly property so I use it this way (but it's my point of view). So for those which don't agree with me just read return [self.jobsArray count]; in the tableView:numberOfRowsInSection: method.
Change the declaration of jobsArray from NSArray to NSMutableArray.
Add an initialization at the beginning point of fetchedData method like follows.
if(!jobsArray) {
jobsArray = [NSMutableArray array];
}
else {
[jobsArray removeAllObjects];
}
Remove the following line.
jobsArray = [jobsInfo objectForKey:#"results"];
Instead of that, add the initialized object to the array at the end of for loop.
[jobsArray addObject:jobby];
Add a [tableView reloadData]; at the end of your *-(void)fetchedData:(NSData )responseData; method implementation.
Initially when you are loading the view, tableView will get populated. After you received the data, tableView will not be known that it is received.
Everything else seems good. Hope rest will work fine.
I'm working on information retrieval where in the latest information in on top of UITableView. The code goes like this.
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
LoanModel* loan = _feed.products[indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" ];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:#"Cell"];
}
NSString *myString = [NSString stringWithFormat:#"%#",[loan name]];
NSArray *myWords = [myString componentsSeparatedByString:#","];
NSMutableArray *reversed = [NSMutableArray arrayWithCapacity:[myWords count]];
NSEnumerator *enumerator = [myWords reverseObjectEnumerator];
for (id element in enumerator) {
[reversed addObject:element];
}
NSMutableString *reverseString = [reversed componentsJoinedByString:#","];
cell.textLabel.text = reverseString;
return cell;
}
I'm getting the JSON object which contain two fields cid and name respectively. I'm showing only the name is tableview. But I want to show it in reverse i,e last update should show at first. But above code showing the content in reverse. IF "hello" is the content, then it shows "olleh". Please suggest me how to get the UITableView in reverse list. I'm a newbie to iOS.
If you want to have the reversed order of cells / objects they represent, you have to change
LoanModel* loan = _feed.products[indexPath.row];
to
LoanModel* loan = _feed.products[_feed.products.count - 1 - indexPath.row];
assuming that _feed.products is an NSArray.
The array you are using to display the the data in tableView, you'll need to reverse that and then display the data in tableView
NSArray* reversed = [[myArray reverseObjectEnumerator] allObjects];
you can use this to make your array objects order in reverse.
Try This:
NSArray* reversedArray = [[myWords reverseObjectEnumerator] allObjects];
I am trying to build my first iOS app that parses json from a web service.
In my .h file i create an array
NSArray *items;
In my .m file i call the website and store the data into the array. This all goes fine except eventually I am trying to display the data in a uitableview.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ItemsCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSLog(#"%#",items);
NSDictionary *item = [items objectAtIndex:indexPath.row];
NSString *title = [item objectForKey:#"title"];
cell.title.text = title;
return cell;
}
The nslog of items produces:
{
category = code;
keyword = "";
limit = 20;
lowPrice = "";
page = 0;
products = (
{
description = "...";
mobileFriendly = 1;
popularity = 2021;
productId = code;
title = "title";
}, and so on...
)
I am getting an error trying to do NSDictionary *item = [items objectAtIndex:indexPath.row]; It says doing objectAtIndex:indexPath.row is invalid.
What am I doing wrong?
Thanks for your help!
The value stored in items is an instance of NSDictionary, but you're sending it an NSArray message. Also, the code you posted is referring to gift and ignoring the item anyway.
The array you're looking for is actually stored under the key products inside the items dictionary, so you need to do something like this:
NSArray *products = [items objectForKey:#"products"];
NSDictionary *product = [products objectAtIndex:indexPath.row];
// Then use the product, instead of using 'gift' (whatever that is).
NSString *title = [product objectForKey:#"title"];
items is not a NSArray - it is a NSDictionary. so it has no objectAtIndexPath: method.
In rootViewController.h i have a property NSMutableArray mainList:
#interface RootViewController : UITableViewController {
NSMutableArray *mainList;
}
#property (nonatomic, retain) NSMutableArray *mainList;
#property (nonatomic, retain) IBOutlet DetailsViewController *detailsController;
In the m file, when loading, the following works just fine:
- (void)viewDidLoad
{
self.mainList = [[NSArray alloc] initWithObjects:#"Country1", #"Contry2", nil];
}
Populating the array by a class also works fine (on first load):
innehall *myInnehall= [[innehall alloc] init];
[myInnehall fillMain];
self.mainList = myInnehall.theMainList;
[myInnehall release];
(the debugger shows data to be correct in the array)
When scrolling, the app crasches at setting the label of the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [self.mainList objectAtIndex: [indexPath row]];
return cell;
}
In the debugger, the Array is only populated 1-9 instead of up to 19. 10-19 containing strange objects. What can be eating my Array??
First of all, your NSMutableArray property must be initialized properly, like:
self.mainList = [[NSMutableArray alloc] init];
(Not as a NSArray)
Then, you are making your property mainList point to myInnehall.theMainList and you are releasing it afterwards, that is what is causing the crash.
Try just add the myInnehall items to your mainList
[self.mainList addObjectsFromArray:myInnehall.theMainList];
Try to change
self.mainList = myInnehall.theMainList;
to
self.mainList = [myInnehall.theMainList copy];
Can you also put NSLog([self.mainList description]) in your cellForRowAtIndexPath and post the result?
PS: You have NSMutableArray in property declaration and you initialize it as NSArray?
I'm pretty new to objective-c development and I'm to the point I'm beginning to test my application for leaks and patching up anything else I may have done wrong originally. I followed the examples from a book I bought and expanded on those ideas. The Leaks instrument is telling me in my tableView cellForRowAtIndexPath method I have a leak and I'm not sure on how to fix it.
Here is the related .h contents:
#interface NewsListViewController : UITableViewController<UIActionSheetDelegate> {
NSMutableArray *newsList, *account, *playerList;}
And here is the related .m contents:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)ip {
static NSString *CellIdentifier = #"NewsCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
[cell autorelease];
}
NSManagedObject *uNews = [newsList objectAtIndex:[ip row]];
NSManagedObjectContext *playerDBContext = [[AppController sharedAppController] managedObjectContext];
NSFetchRequest *playerDBRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *playerDBEntity = [NSEntityDescription entityForName:#"Players"
inManagedObjectContext:playerDBContext];
[playerDBRequest setEntity:playerDBEntity];
NSPredicate *playerDBPredicate = [NSPredicate predicateWithFormat:#"playerID=%#", [uNews valueForKey:#"playerID"]];
[playerDBRequest setPredicate:playerDBPredicate];
NSError *playerDBError;
NSArray *playerDBList = [playerDBContext executeFetchRequest:playerDBRequest error:&playerDBError];
[playerDBRequest release];
playerList = [playerDBList mutableCopy];
NSString *playerInformation;
if (![playerDBList count] == 0) {
NSManagedObject *playerInfo = [playerList objectAtIndex:0];
playerInformation = [NSString stringWithFormat:#"%#, %# (%#-%#)", [playerInfo valueForKey:#"playerLastName"],
[playerInfo valueForKey:#"playerFirstName"],
[playerInfo valueForKey:#"team"],
[playerInfo valueForKey:#"position"]];
} else {
//NSInteger playerID = (NSInteger *)[uNews valueForKey:#"playerID"];
[self addPlayer:(NSInteger *)[uNews valueForKey:#"playerID"]];
NSLog(#"%#", [uNews valueForKey:#"playerID"]);
playerInformation = [uNews valueForKey:#"playerInfo"];
}
cell.textLabel.text = playerInformation;
cell.detailTextLabel.text = [uNews valueForKey:#"news"];
cell.selectionStyle = UITableViewCellSelectionStyleGray;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;}
It's throwing the error on the playerList = [playerDBList mutableCopy]; line - Help with how to fix and an explanation would be greatly appreciated. It's probably from reallocating without releasing but when I've tried using [playerList release]; at the end of the cellForRowAtIndexPath my app crashes.
Properties would make this 'just work'.
.h:
...
#property (nonatomic, retain) NSMutableArray *playerList;
...
.m:
#implementation MyClass
#synthesize playerList;
... then in your cellForIndexPath method ...
self.playerList = [[playerDBList mutableCopy] autorelease];
...
- (void)dealloc {
[playerList release];
[super dealloc];
}
A property declared 'retain' will automatically handle memory management when the property is assigned, releasing the old value if it exists before retaining the new one.
The release you tried crashed because the first time through playerlist hasn't ever been assigned and you release a nil. But the second time through it has something and you leak it. Whenever I reuse a retaining pointer like that, I do
if( playerList )
[playerList release];
playerList = [playerDBList mutableCopy];
just to be safe.