I have a UITableView with a custom cell which i fill (with array infoservices) after parsing the xml data.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
ApplicationCell *cell = (ApplicationCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[self.cellNib instantiateWithOwner:self options:nil];
cell = tmpCell;
self.tmpCell = nil;
}
infoService *e = [self.infoservices objectAtIndex:indexPath.row];
cell.name = [e infoName];
NSString *infodetails = [e infoDetails];
if ( infodetails == nil ) {
cell.details = #"Loading...";
[self startInfoDownload:e forIndexPath:indexPath];
NSLog(#"Loading...");
} else {
cell.details = infodetails;
NSLog(#"Show info detail: %#", infodetails );
}
return cell;
}
- (void)infoDidFinishLoading:(NSIndexPath *)indexPath
{
infoDownloader *infoserv = [imageDownloadsInProgress objectForKey:indexPath];
if (infoserv != nil)
{
[infoservices replaceObjectAtIndex:[indexPath row] withObject:infoserv.appRecord];
NSIndexPath *a = [NSIndexPath indexPathForRow:indexPath.row inSection:0]; // I wanted to update this cell specifically
ApplicationCell *cell = (ApplicationCell *)[self.tableView cellForRowAtIndexPath:a];
cell.details = [[infoservices objectAtIndex:[indexPath row]] infoDetails];
NSLog(#"Updating=%#", [[infoservices objectAtIndex:[indexPath row]] infoDetails]);
}
}
For each cell i'm using NSURLConnection sendAsynchronousRequest to retrieve and parse xml data from object infoDownloader with
- (void)startDownload
for each individual cell.
After data has been successfully parsed delegate method from infoDownloader is called
- (void)infoDidFinishLoading:(NSIndexPath *)indexPath
The problem is that, while the
- (void)infoDidFinishLoading:(NSIndexPath *)indexPath
gets called after parsing each cell and i can see the
NSLog(#"Updating=%#", [[infoservices objectAtIndex:[indexPath row]] infoDetails]);
in the debugger with the correct details, the cell does not get refreshed immediately but after 6 or 7 seconds. Also cellForRowAtIndexPath does not get called from
- (void)infoDidFinishLoading:(NSIndexPath *)indexPath
for some reason because there is not debug output after the infoDidFinishLoading. Also i don't understand how the cell.details gets actually refreshed since cellForRowAtIndexPath isn't called again.
I've tried to setup this function using Apple's LazyTableImages loading example, which i have used successful, but i don't know what's going wrong.
You may need to call reloadRowsAtIndexPaths once the data has been loaded. What could be happening is that the cell data has been loaded but the cell drawing is not updated.
Also I believe your code can request the same data multiple times because if [e infoDetails] is nil a request is made, but the cell could be requested multiple times before the data is loaded so [self startInfoDownload:e forIndexPath:indexPath] would be called multiple times, downloading the same data. You should look into keeping track of which rows you have requested data for.
Check out this code for some ideas on how to solve this: https://github.com/kgn/Spectttator/blob/master/SpectttatorTest-iOS/RootViewController.m#L123
Any changes affecting UI must be performed on main thread.
Refreshing tableView, by changing cells details, realoading tabe view, realoding rows... are changes affecting UI.
In particular, you should perform your changes in the main thread using :
dispatch_async(dispatch_get_main_queue(), ^{
cell.details = [[infoservices objectAtIndex:[indexPath row]] infoDetails];
}
or prior to iOS 4 :
cell performSelectorOnMainThread:#selector(setDetails:) withObject:[[infoservices objectAtIndex:[indexPath row]] infoDetails];
Related
I am currently trying to update my table view when a new chat message is sent. I set a notification block on my collection to notify the table view to begin updating but I keep getting an error saying there were 0 insertions:
Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Invalid update: invalid number of rows in section 0.
The number of rows contained in an existing section after the update (8)
must be equal to the number of rows contained in that section before the update (7),
plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted)
and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Here is my notification code:
- (void)setUpRealmNotifications {
// Observe RLMResults Notifications
__weak typeof(self) weakSelf = self;
self.notificationToken = [[TERMessage objectsWhere:#"conversationID == %#", self.model.serverID] addNotificationBlock:^(RLMResults<TERMessage *> *results, RLMCollectionChange *change, NSError *error) {
if (error) {
NSLog(#"Failed to open Realm on background worker: %#", error);
return;
}
UITableView *tableView = weakSelf.tableView;
// Initial run of the query will pass nil for the change information
if (!change) {
[tableView reloadData];
return;
}
// Query results have changed, so apply them to the UITableView
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[change deletionsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:[change insertionsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:[change modificationsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}];
}
From the looks of it, the change notification is coming down and updating the UITableView correctly, but it would seem that your UITableView data source isn't matching the same changes.
To ensure both the results of the change notification and the UITableView data source method in charge of managing the cells doesn't fall out of sync, I'd recommend keeping a single RLMResults instance around that both the change notification and table data source refer to.
#property (nonatomic, strong) RLMResults *items;
#property (nonatomic, strong) RLMNotificationToken *token;
// ---
self.items = [TERMessage objectsWhere:#"conversationID == %#", self.model.serverID];
self.token = [self.items addNotificationBlock:^...];
// ---
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TERMessage *message = self.items[indexPath.row];
UITableViewCell *cell = ...; //configure cell
return cell;
}
I have a custom tableview cell. But when I want to call this cell in my cellForRowAtIndexpath, it gives the following error.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITableViewCell lblPosition]: unrecognized selector sent to instance 0xa62a600'
I checked a 100 times if everthing is hooked up correctly and named correctly but still doesn't find what is wrong. This is what I am doing in code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"KlassementCell";
KlassementCell *cell = (KlassementCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"KlassementCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// ask NSFetchedResultsController for the NSMO at the row in question
Klassement *klassement = [self.fetchedResultsController objectAtIndexPath:indexPath];
// Then configure the cell using it ...
NSString *pointDiff = [NSString stringWithFormat:#"%#", klassement.goalsDiff];
NSLog(#"position %#",klassement.position);
NSLog(#"name %#",klassement.name);
cell.lblPosition.text = klassement.position;
cell.lblName.text = klassement.name;
cell.lblgamesPlayed.text = klassement.gamesPlayed;
cell.lblGamesWon.text = klassement.gamesWon;
cell.lblGamesTied.text = klassement.gamesTied;
cell.lblGamesLost.text = klassement.gamesLost;
cell.lblGoalsPos.text = klassement.goalsPos;
cell.lblGoalsNeg.text = klassement.goalsNeg;
cell.lblGoalsDiff.text = pointDiff;
cell.lblPoints.text = klassement.points;
return cell;
}
Can anybody help?
Kind regards.
Try
if ((cell == nil) || (![cell isKindOfClass: KlassementCell.class])) {
The cell returned in the queue might not be KlassementCell.
Have you set the class of 0 Index object in "KlassementCell" nib as KlassementCell and mapped all the subviews??
If not then do that and it may work.
maybe this helps in your viewDidLoad (or any other place for setup...) method:
UINib* myCellNib = [UINib nibWithNibName:#"KlassementCell" bundle:nil];
[tableView registerNib:myCellNib forCellReuseIdentifier:#"KlassementCell"];
and then instantiate the cell like this
cell = (KlassementCell*)[myCellNib instantiateWithOwner:nil options:nil];
myCellNib should be available in the class scope
Make sure in the nib that the class of the cell is set to "KlassementCell" AND also that the identifier is set to "KlassementCell".
You should not need to do anything else since only use 1 type of tableviewcell and only 1 identifier in cellForRowAtIndexPath
The identifier has to be the exact same as the class name, so both have to be "KlassementCell". That way there are no regular reusable UIcells in the queue. That's also why if ((cell == nil) || (![cell isKindOfClass: KlassementCell.class])) { works because you make sure you are not assigning things to properties that don't exist on a regular cell like lblPosition, lblName and lblgamesPlayed..
So, I'm using RestKit to access a webservice and retrive data from there.
So far I have two views, and that part of retrieving the data is fine, and I get the 100 objects I need, saving them into an array called "songs".
Here is didLoadObjects:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
NSLog(#" Reached didLoadObjects: %d", [objects count]);
self.songs = objects;
NSLog(#"%#",self.songs);
}
Ok, I have two views, and the problem is, of course, on the second one. I'm using Storyboards. I gave the tableView cell an identifier called "TopListCellIdentifier".
So, I get the objects, all the 100 of them are "printed" in the command line, but the problem starts when I try to access the data from the array inside cellForRowAtIndexPath, something which has to be done because I have a custom tableViewCell displaying the info I need (sons, artists, covers, stuff like that). So, when I start the app, the first view is fine, but the second has 100 cells, but no info. Here is the cellForRowAtIndexPath code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *TopListCellIdentifier = #"TopListCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TopListCellIdentifier];
// Loading the Cover Images in the Background
// Cover Image: Tag 1
[((HJManagedImageV*)[cell viewWithTag:1]) clear];
HJManagedImageV* thumbImage = ((HJManagedImageV*)[cell viewWithTag:1]);
NSString *thumbUrl = [[self.songs objectAtIndex:[indexPath row]] thumbnail];
thumbImage.url = [NSURL URLWithString:thumbUrl];
[[ImageHandler sharedHandler].imgManager manage:thumbImage];
//Song and Artist: Tag 2 and 3
((UILabel*)[cell viewWithTag:2]).text = [[self.songs objectAtIndex:[indexPath row]] title];
((UILabel*)[cell viewWithTag:3]).text = [[self.songs objectAtIndex:[indexPath row]] artist];
//Arrow Up/Down/Same: Tag 4
//TODO
//Position Number: Tag 5
((UILabel*)[cell viewWithTag:5]).text = [NSString stringWithFormat:#"%d.", [indexPath row]+1];
return cell;
}
I've tried to put a debugger in the first line of cellRow(...) but the program doesn't enter there. I feel like i'm forgetting about something very simple, but I can't seem to figure out what.
Can someone help me, please?
You're never initing a new cell. You need to add a line like :
if (nil == cell)
cell = [[UITalbeViewCell alloc] init...
after your call to
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TopListCellIdentifier];
I am trying to asynchronously download images for a UITableViewCell, but it is currently setting the same image to each cell.
Please can you tell me the problem with my code:
- (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];
}
SearchObject *so = (SearchObject *)[_tableData objectAtIndex:indexPath.row];
cell.textLabel.text = [[[[so tweet] stringByReplacingOccurrencesOfString:#""" withString:#"\""] stringByReplacingOccurrencesOfString:#"<" withString:#"<"] stringByReplacingOccurrencesOfString:#">" withString:#">"];
cell.detailTextLabel.text = [so fromUser];
if (cell.imageView.image == nil) {
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:[so userProfileImageURL]]];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:req delegate:self];
[conn start];
}
if ([_cellImages count] > indexPath.row) {
cell.imageView.image = [UIImage imageWithData:[_cellImages objectAtIndex:indexPath.row]];
}
return cell;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_cellData appendData:data];
[_cellImages addObject:_cellData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.tableView reloadData];
}
You are appending the data from every image downloaded to the same data object. So in the best case the data object ends up with the data for image #1 immediately followed by the data for image #2 and so on; the image decoder is apparently taking the first image in the chunk of data and ignoring the garbage after. You also seem to be unaware that NSURLConnections' connection:didReceiveData: will not necessarily be called in the order that the connections were started, that connection:didReceiveData: can be called zero or multiple times per connection (and probably will if your images are more than a few kibibytes), and that tableView:cellForRowAtIndexPath: is not guaranteed to be called for every cell in the table in order. All of which are going to totally screw up your _cellImages array.
To do this right, you need to have a separate NSMutableData instance for each connection, and you need to add it to your _cellImages array just once, and at the correct index for the row rather than at the arbitrary next available index. And then in connection:didReceiveData: you need to figure out the correct NSMutableData instance to append to; this could be done by using the connection object (wrapped in an NSValue using valueWithNonretainedObject:) as the key in an NSMutableDictionary, or using objc_setAssociatedObject to attach the data object to the connection object, or by making yourself a class that handles all the management of the NSURLConnection for you and hands you the data object when complete.
I don't know if this is causing the problem or not, but in your connection:didReceiveData: method you're just appending the image data to the array; you should be storing the image data in such a way that you can link it to the cell it's supposed to be shown in. One way to do this would be use an NSMutableArray populated with a bunch of [NSNull]s, then replace the null value at the appropriate index when the connection has finished loading.
Also, you're appending the _cellData to the _cellImages array when the connection hasn't finished loading, you should only be doing this in the connection:didFinishLoading method.
I need to understand something about NSManagedObjectContext update.
I have a UISplitView with a UITableViewController on the RootView and a UIViewController on the Detail View. When I tap in a row with data, I load some data into labels and a UITextView where I can update that field:
- (void)textViewDidEndEditing:(UITextView *)textView {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
[[listOfAdventures objectAtIndex:indexPath.row] setAdventureDescription:textView.text];
}
Ok. This works correctly, the description is updated.
Also, someone might wants to delete a row:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"playerPlaysAdventure.adventureName==%#",[[listOfAdventures objectAtIndex:indexPath.row] adventureName]];
NSArray *results = [[AdventureFetcher sharedInstance] fetchManagedObjectsForEntity:#"Player" withPredicate:predicate withDescriptor:#"playerName"];
[moc deleteObject:[listOfAdventures objectAtIndex:indexPath.row]];
for ( Player *player in results ) {
[moc deleteObject:player];
}
[listOfAdventures removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
[self clearDetailViewContent];
NSError *error = nil;
if ( ![moc save:&error] ) {
NSLog( #"Errore nella cancellazione del contesto!" );
abort();
}
}
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
}
}
So here's my problem: if I comment the rows about the saving of my MOC, the adventure is only momentarily deleted. If you quit the app and the reopen it, the object is still there. This doesn't happen with the update of a field. I'd like to know why and if I should save moc also in textViewDidFinishEditing method.
Thank you in advance.
It's the difference between changing an attribute of an object and adding or removing an entire object in the object graph.
In the first block, you change an attribute of an existing object which saves automatically unless you run an undo. This is because the object already exist in the object graph and no other objects have to be altered to make the change.
In the second block, you are removing an entire object and potentially altering the object graph itself by changing the relationships between objects. That change will not be committed until an implicit save because potentially it can trigger a cascade of changes throughout a large number of objects.