UITableView only shows data from JSON array when I scroll up and down. When I load the table view it shows cells but they are blank and as I scroll up and down it then starts showing the data.
How do I show the cells with data without scrolling?
- (void)requestFinished:(ASIHTTPRequest *)request
{
if (request.responseStatusCode == 400) {
NSLog( #"Code already used");
} else if (request.responseStatusCode == 403) {
NSLog( #"Code already used");
} else if (request.responseStatusCode == 200) {
NSLog(#"%#",[request responseString]);
NSString *response = [request responseString];
const char *convert = [response UTF8String];
NSString *responseString = [NSString stringWithUTF8String:convert];
responseArray = [responseString JSONValue];
Nrows = [responseArray 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];
}
NSDictionary *dict = [responseArray objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [dict objectForKey:#"allfeeds"];
cell.textLabel.adjustsFontSizeToFitWidth = YES;
cell.textLabel.font = [UIFont systemFontOfSize:12];
cell.textLabel.minimumFontSize = 10;
cell.textLabel.numberOfLines = 4;
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.text = [dict objectForKey:#"allfeeds2"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
When you finish your request and you set the array, make sure to call [self.tableView reloadData];
For performance and animation purposes, table views don't reload their data when the source changes.
Instead of starting the request asynchronously (non-blocking)
[request startAsynchronously];
Try doing it synchronously (blocking)
[request startSynchronously];
I had a similar issue and got my answer at the Apple's Developer Forums a while ago, this solved my problem:
From what you've described, I'd guess the problem is more likely to be in cellForRowAtIndexPath or wherever you do the actual configuration of a cell than in the code you've posted. A problem like yours can happen when dequeueReusableCellWithIdentifier gives you back an existing cell, but you don't reset its existing state completely.
The full question and answers can be found here (requires login): https://devforums.apple.com/message/502483#502483
These problems are usually caused by the UITableView cache, that dequeues his available cached UITableViewCell with the same CellIdentifier (in your case - #"Cell").
Try creating a cell identifier for each and everyone of your table's cells. You can create this variance using the NSIndexPath param in the following way :
NSString* cellIdentifier = [NSString stringWithFormat:#"Cell%d",indexPath.row];
That of course, under the assumption you're not changing the content of your cells after you load the data.
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 am using async to get my images from an xml. Parser is working correctly and I can output the URLs. In the async I am trying to cache the images to a mutable dictionary. I am stuck in a loop and the images will not output at all any more. Here is my code that I am stuck on.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Got Here");
static NSString *CellIdentifier = #"NewsCell";
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSLog(#"Got Here 2");
// Configure the cell...
NewsItem *item = [newsItemsArray objectAtIndex:indexPath.row];
cell.newsTitle.text = item.title;
NSLog(#"Got Here 3");
NSMutableDictionary *record = [_records objectAtIndex:[indexPath row]];
if ([record valueForKey:#"actualImage"]) {
NSLog(#"Record Found");
[cell.newsImage setImage:[record valueForKey:#"actualImage"]];
} else {
NSLog(#"Record Not Found");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void)
{
NSLog(item.image);
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.image]];
dispatch_async(dispatch_get_main_queue(), ^(void){
[record setValue:[UIImage imageWithData:imageData] forKey:#"actualImage"];
[cell.newsImage setImage:[record valueForKey:#"actualImage"]];
if (tableView) {
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
});
});
}
return cell;
}
Thanks in advance for you help.
Create a UITableViewCell subclass. When you need to load an image in it, create an NSOperation subclass which does the actual network connection. (see http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/ for an example). Alternatively, use a 3rd party package which handles this, there are millions of them. My current favorite is MKNetworkKit. Store a reference to your operation. In the cell's prepareForReuse, cancel the connection if it hasn't completed. This will prevent your cell getting the wrong image when scrolling if the first request completes after the second (happens more often then you'd think).
First of all replace
[record setValue:[UIImage imageWithData:imageData] forKey:#"actualImage"];
with
[record setObject:[UIImage imageWithData:imageData] forKey:#"actualImage"];
and your code should work. Since the dictionary is always empty it always goes to else block.
Print NSMutableDictionary and you will realize this.
This is not the way caching is done. It's not only about memory but also about coding practices.
Please use a clean approach like NSCache or SDWebImage
SDWebImage takes care of caching as well.
Use NSOperation to make request rather than GCD. to avoid nested blocks and concerrency issues.
Here is how a clean code looks like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NewsItem *item = [newsItemsArray objectAtIndex:indexPath.row];
cell.newsTitle.text = item.title;
// category from library
[cell.imageView setImageWithURL:newsItem.imageURL];
if ([item needsRefresh] )
{
cell.newsTitle.text = item.title;
NewsOperation *operation = [[NewsOperation alloc] initForNewsItem:newItem completion:^(NewsItem *newsItem, NSError *error) {
if (newsItem)
{
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
}];
[[self newsItemQueue] addOperation:operation];
}
return cell;
}
You may try the following code. You should also research your problem and try to find other existing answers and explanations. This is a very frequent question, countless times answered on SO ;)
For example: loading images in table using blocks
The code tries to fix the most obvious issues, but there is no guarantee that it works in all edge cases. You need to debug and test.
The main problem with the original code is, that it tries to set the cell's newsImage property within the completion block. When that gets executed, the captured cell is likely not be associated to the same row anymore. Remember: cells will be reused!
Thus, in the completion block, the changed code re-evaluates the corresponding cell, from the indexPath which has been captured at the start of the block:
NewsCell* cell2 = [tableView cellForRowAtIndexPath:indexPath]; // cell equals nil, if not visible
The returned cell may be not visible, in which case the table view returns nil.
Basically, the same is applied to the record variable. Just for different reasons: the record variable is a variable with automatic scope, that is it gets released when the method returns. Capturing it in the block is probably not a good idea.
Thus, it needs to be re-evaluted in the block. Thats basically a short hand for [self.records objectAtIndex:[indexPath row]];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NewsCell";
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
NewsItem *item = [newsItemsArray objectAtIndex:indexPath.row];
cell.newsTitle.text = item.title;
NSMutableDictionary *record = [_records objectAtIndex:[indexPath row]];
if ([record valueForKey:#"actualImage"]) {
NSLog(#"Record Found");
[cell.newsImage setImage:[record valueForKey:#"actualImage"]];
} else {
NSLog(#"Record Not Found");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void)
{
NSLog(item.image);
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.image]];
dispatch_async(dispatch_get_main_queue(), ^(void){
NSMutableDictionary *record2 = [_records objectAtIndex:[indexPath row]];
[record2 setValue:[UIImage imageWithData:imageData]
forKey:#"actualImage"];
NewsCell* cell2 = [tableView cellForRowAtIndexPath:indexPath]; // cell equals nil, if not visible
if (cell2) {
[cell2.newsImage setImage:[record2 valueForKey:#"actualImage"]];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
});
});
}
return cell;
}
I have an HTML Data which looks like:
<div id="foo"><a class="someClass" href="http://somelink">Some Title</a></div>
<div id="foo"><a class="someClass" href="http://somelink1">Some Title 1</a></div>
<div id="foo"><a class="someClass" href="http://somelink2">Some Title 2</a></div>
I'm able to show Link title in the tableView, but how can I get URL of these specific title to load a new view (which is pushed through navigation controller upon selecting table cell) ?
Here's the code I'm using to parse HTML Data:
HTMLParser * parser = [[HTMLParser alloc] initWithData:responseData error:&error];
HTMLNode * bodyNode = [parser body];
someArray = [[bodyNode findChildrenWithAttribute:#"id" matchingName:#"someID" allowPartial:NO] retain];
And this one to show the array data in the table:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [someArray 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];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
}
HTMLNode* someNode = [someArray objectAtIndex:[indexPath row]];
cell.textLabel.text = [NSString stringWithFormat:#"%#",[someNode allContents]];
return cell;
}
When you get a node and want information about a child node within it, you have to continue parsing to find the child node and then get the attribute you are looking for. For example:
HTMLNode *anchorNode = [someNode findChildTag:#"a"];
NSString *anchorHref = [anchorNode getAttributeNamed:#"href"];
It might help to mention that you are looking at Ben Reeves' HTMLParser. (At least I think that is the one you are using).
Hey guys I have a problem, I have a NSDictionary of NSArrays and im trying to set up alphabetical sections so AAAAA, BBBB, CCCC.... etc however when I pass my values over its printing out all of the A values right into one uitableviewcell then the B's in the next... which is not right what Im after. Im hoping to have alphabetical sections with one NSArray/Dictionary value per UItableviewcell..
this is how Im currently setting it up, I think maybe I might be missing an If statment however Im just not exactly sure how to do this so would like some help if possible.. thanks in advance.
- (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.accessoryType = UITableViewCellAccessoryNone; //make sure their are no tickes in the tableview
cell.selectionStyle = UITableViewCellSelectionStyleNone; // no blue selection
// Configure the cell...
if(indexPath.row < [self.arraysByLetter count]){
NSArray *keys = [self.arraysByLetter objectForKey:[self.sectionLetters objectAtIndex:indexPath.row]];
NSString *key = [keys description];
NSLog(#"thingy %#", key);
cell.textLabel.text = key;
}
return cell;
}
This is what it looks like on the emulator
UPdate I made chages to my code as suggested below and now this is what happens...
when emulator first loads
I then scroll to the bottom of the list and back to the top and it looks like this.
NSString *key = [keys description];
Here you're setting the text to be the string representation of ALL the keys. :)
I think you want this (and I hope I've interpreted your code properly)
NSArray *keys = [self.arraysByLetter objectForKey:[self.sectionLetters objectAtIndex:indexPath.section]];
NSString *key = [keys objectAtIndex:indexPath.row];
NSLog(#"thingy %#", key);
So, get the KeysArray using the IndexPath section,
then get the value of the cell using the IndexPath row.
All,
When my table view loads, it accesses several delegate methods. When I configure the cell, I have it calling this method (where "linkedList" is an array of dictionarys):
- (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];
}
// Configure the cell...
VUWIManager *vuwiManager = [VUWIManager sharedVuwiManager];
NSLog(#"%#", [[vuwiManager linkedList]objectAtIndex:indexPath.row]);
NSLog(#"TESTZOMGOFDSOJFDSJFPODJSAPFDS");
cell.textLabel.text = [[vuwiManager linkedList]objectAtIndex:indexPath.row];
return cell;
}
It crashes at the line cell.textLabel.text = [[vuwiManager linkedList]objectAtIndex:indexPath.row]; - I know I'm doing something wrong here but I'm not sure what it is. Again, linkedList is a NSMutableArray of NSDictionarys.
Edit: if I call cell.textLabel.text = [[vuwiManager linkedList]objectAtIndex:indexPath.row]; it returns:
{
IP = "192.168.17.1";
desc = "description";
}
in the debugger. Just thought I'd give a little bit of formatting details.
Thanks
You are trying to assign an object NSDictionary to cell.textLabel.text, which must be passed a NSString.
Did you want :
NSString *s = [NSString stringWithFormat:#"%#",
[[vuwiManager linkedList]objectAtIndex:indexPath.row]];
cell.textLabel.text = s;
?
Setting an NSString * to an NSDictionary * will likely result in a crash when it tries to access any string methods that are not implemented in the dictionary. If you want that string you are logging add a call to description.
cell.textLabel.text = [[[vuwiManager linkedList]objectAtIndex:indexPath.row] description];
It looks like you are setting cell.textLabel.text to a NSDictionary instead of an NSString. If linkedList is an NSMutableArray of NSDictionaries, then you need to add on objectForKey:#"String key" to access the string
cell.textLabel.text = [[[vuwiManager linkedList]objectAtIndex:indexPath.row] objectForKey:#"STRING_KEY_HERE"];