I have 3 or 2 sections (depending on datasource), in my grouped UITableView. I am trying to reload the last section via:
dispatch_async(dispatch_get_main_queue(), ^{
[UIView performWithoutAnimation:^{
[feedDetailTB reloadSections:[NSIndexSet indexSetWithIndex:feedDetailTB.numberOfSections-1] withRowAnimation:UITableViewRowAnimationNone];
}];
});
First of all, the footer never disappears. The data source basically keeps track of whether there are more comments or not (a simple load more functionality). In the viewForFooterInSection I simply return nil, when all the comments have been loaded.
But, as you see in the GIF, at first the loading button stays there. It is even accessible and works. When I scroll up, it vanishes and one can see it in the bottom, which is correct. But after all the comments have been reloaded, it should vanish, but sadly it stays there.
If I use reloadData it works fine. But I can't used it, since I have other sections, which I don't need to reload.
Second, there is a weird animation/flickering of the row items, even when I have used UITableViewRowAnimationNone. Not visible in the GIF
You should implement "isTheLastSection" according to your logic
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
if (isTheLastSection) {
return 40;
}
return 0;
}
In order to add new rows to a section, you must use the insertRowsAtIndexPaths rather than just adding new objects to data source and reloading a section.
Here's the code:
NSMutableArray *newCommentsIndexPath = [[NSMutableArray alloc] init];
for (NSInteger i = currentCount; i < (_postDetailDatasource.commentsFeedInfo.allCommentsArray.count + serverComments.count); i ++)
{
NSIndexPath *idxPath = [NSIndexPath indexPathForRow:i inSection:sectionNumber];
[newCommentsIndexPath addObject:idxPath];
}
[_postDetailDatasource.commentsFeedInfo.allCommentsArray addObjectsFromArray:serverComments];
[feedDetailTB beginUpdates];
[feedDetailTB insertRowsAtIndexPaths:newCommentsIndexPath withRowAnimation:UITableViewRowAnimationFade];
[feedDetailTB endUpdates];
Related
I am trying to delete rows in an array without having to use the edit function that Apple provide (Something along the lines of -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath). So the user selects some of the rows, presses a button not in the table view, then those rows fade away. Apple provide something of sorts here.
The code I am using calls an array of rows to be deleted, which is defined elsewhere, removes the array population objects at those particular rows, and is supposed to remove all of the rows using the fade stuff.
- (void) deleteTableRow {
NSIndexPath *current;
NSLog(#"to be deleted: %#", toBeDeleted); //toBeDeleted is the array with the NSIndexPath items
for(int i=0; i < [toBeDeleted count]; i++) {
current = [toBeDeleted objectAtIndex: i];
[tableData removeObjectAtIndex:current.row]; //Remove the necessary array stuff
}
tv = [UITableView alloc];
[tv beginUpdates];
[tv deleteRowsAtIndexPaths:toBeDeleted withRowAnimation:UITableViewRowAnimationFade];
[tv endUpdates];
// Do whatever data deletion you need to do...
}
tv is defined in my header file and is a referencing outlet of my UITableView.
So here are my main questions:
Because my UITableView is not a UITableViewController (it is part of the view instead), is this even possible?
If it is possible, why is this not working?
I have spent hours searching for the solution with out any luck. I am trying to delete a row (also deselect same row) programmatically. After row deletion call below, UITableViewDelgate methods get called expectedly and data source is updated but UITableView is not refreshed. deselectRowAtIndexPath call also does not work. I tried all kinds of scenarios as shown by commented lines.
Here is my code:
checkoutPerson is called as a result of observer listening for NSNotificationCenter messages.
- (void) checkoutPerson: (NSNumber*) personId {
Person *person = [_people objectForKey:personId];
if( person )
{
// Remove person from data source
int rowIndex = person.rowIndex;
S2Log(#"Deleting row number=%d", rowIndex);
[_allKeys removeObjectAtIndex:rowIndex];
[_people removeObjectForKey: personId];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0];
//[[self tableView] beginUpdates];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
S2Log(#"Deleting indexPath row=%d", [indexPath row]);
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
//[[self tableView] endUpdates];
S2Log(#"Reloading data");
//[[self tableView] reloadData];
//[self performSelector:#selector(refreshView) withObject:nil afterDelay:1.5];
//[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
}
I will appreciate for help.
Thanks
-Virendra
I believe deleted cell is not being recycled. If I delete row in the middle, last row is always erased (since there is one less item) but the deleted row remains.
Use the above code between two function for table view
[tableView beginUpdates];
// the deletion code from data source and UITableView
[tableView endUpdates];
By calling this functions you are telling UITableView that you are about to make updates for deleting your cell.
Edit
The other problem I see with your code is you first delete the data from the data source.
Now you are asking for the UITableViewCell (which actually reloads the UITableView)
and then you are deleting the row from UITableView
I guess you should fetch the UITableViewCell before deleting values from your data source.
I found the problem. It has nothing to do with the code I posted above. It is syncing problem between visual display and the contents of data source. I have an embedded UITableView as part of a composite view. In composite view's controller, I was wiring up UITableView's delegate and data source to an instance of UITableViewController. Instead of this, I should have set UITableViewController's tableView property to the embedded UITableView. It seems that UITableView has to be contained within UITableViewController in order to correctly sync up table view visual display to the contents of data source. This also fixes row deselection and scrolling. I also needed to delay reloadData call in which case deleteRowsAtIndexPaths:withRowAnimation is not required. All you need is to modify the contents of your data source and call reloadData with a delay of 1.5 Seconds.
Thanks to all for great help.
I have been struggling with this for a week and my head is about to explode.
Basically I use Prototype Cell, in CellWillAppear I did a little customizations like background color. Nothing fancy.
Due to this, my table view is always empty at start up (no cell) unless the array (data source) is filled with something. So what I did was in NumberOfRowsInSection:
return dataArray.count < 10? 10 : dataArray.count
I am doing this because I would like to see at least some empty cells when there is no data.
Meaning it will show on start up at least 10 empty cells.
To add data to the cell, I call the delegate method in my tableviewcontroller each and every time to add one single entity in the data array (am doing this, because I think it would be faster than waiting until the whole array is filled then call [self.tableView reloadData];) and then refresh it by using reloadRowsAtIndexPaths. But it crashed every single time when it reached to index 10 (error: ... before update number of data was 10, but after update is 11).
What I really want is:
1.) prepare some data
2.) send it to uitableview controller and add it to an array there, instead of waiting and then sending a whole array to table view and refresh at once.
3.) reload just one row after the update (instead of using reloadData -> since I have different color of cell, the whole reload thing cause my table view flash madly).
The one thing I am doing to cell customization is in willDisplayCell:
What I did there is to change the background color of the cell. Again, nothing fancy.
But since there is no data at start up, no cell is ever visible (ui tablew with no cell at displayed at all), unless I did this
return dataArray.count < 10? 10 : dataArray.count;
just so there are at least 10 empty cells showing (WHY do I have to do the above just to display some customized empty cells beats me...).
Using reloadData is to refresh no problem, but since I am updating the data source array in table view every time data is ready instead of saving all prepared data to this array and send it over to table view to update by using reloadData, I would like to update row by row.
I kind of feel that the error comes from the fact that, if I add one item in the array and then call reloadRowsAtIndexPath, it will say "Ok, you had one item before, but after update there is 2! Inconsistency.."
I have already tried using [tableView beginUpdate]; and [tableView endUpdate];
Nothing has worked so far.....
So to sum up: how can I have different colors of cells showing even when the data array is empty on start up (just like the default ui table view with cells displaying completely even with no data) and update just one of the cells once a piece of data is ready instead of updating the whole ui table view with reloadData?
Many thanks in advance, please advise. Regards.
"how can I have different colors of cells showing even when the data array is empty"
Don't have an empty array, have a mutable array where all the members are initially empty strings, and replace those with your real data when you get it.
"update just one of the cells once a piece of data is ready"
Update your array with the new data, and then use reloadRowsAtIndexPaths:withRowAnimation: to update the table. If you want to see the table update row by row (slow enough to see), then put your data in a temporary array first, then add it one element at a time using performSelector:withObject:afterDelay:, calling reloadRowsAtIndexPaths:withRowAnimation: after each addition.
It's a little hard to tell exactly what you want, but here is an example of what I mean. This table displays 20 empty rows, all with different colors, for 2 seconds, then it replaces the empty strings in displayData with the strings in theData one by one at a rate of 10 per second.
#interface TableController ()
#property (strong,nonatomic) NSArray *theData;
#property (strong,nonatomic) NSMutableArray *displayData;
#end
#implementation TableController
- (void)viewDidLoad {
[super viewDidLoad];
self.displayData = [#[#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#"",#""] mutableCopy];
self.theData = #[#"One",#"Two",#"Three",#"Four",#"Five",#"Six",#"Seven",#"Eight",#"Nine",#"ten",#"Black",#"Brown",#"Red",#"Orange",#"Yellow",#"Green",#"Blue",#"Violet",#"Gray",#"White"];
[self.tableView reloadData];
[self performSelector:#selector(addData) withObject:nil afterDelay:2];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.displayData.count;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
UIColor *cellTint = [UIColor colorWithHue:indexPath.row * .05 saturation:1.0 brightness:1.0 alpha:1.0];
cell.backgroundColor = cellTint;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = self.displayData[indexPath.row];
return cell;
}
-(void)addData {
static int i = 0;
[self.displayData replaceObjectAtIndex:i withObject:self.theData[i]];
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
i++;
if (i < self.displayData.count) [self performSelector:#selector(addData) withObject:nil afterDelay:.1];
}
If you don't want any delay between row updates, and you want to make it work when displayArray has a different number of rows that theData, this version of addData should work:
-(void)addData {
static int i = 0;
if (i < self.displayData.count && i< self.theData.count) {
[self.displayData replaceObjectAtIndex:i withObject:self.theData[i]];
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
i++;
[self addData];
}else if (i >= self.displayData.count && i< self.theData.count) {
[self.displayData addObject:self.theData[i]];
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
i++;
[self addData];
}
}
I want to use iOS 5's nifty row-movement calls to animate a tableview to match some model state changes, instead of the older-style delete-and-insert.
Changes may include both reordering and in-place updates, and I want to animate both, so some rows will need reloadRowsAtIndexPaths.
But! UITableView appears to be just plain wrong in its handling of row reloads in the presence of moves, if the updated cell shifts position because of the moves. Using the older delete+insert calls, in a way that should be equivalent, works fine.
Here's some code; I apologize for the verbosity but it does compile and run. The meat is in the doMoves: method. Exposition below.
#define THISWORKS
#implementation ScrambledList // extends UITableViewController
{
NSMutableArray *model;
}
- (void)viewDidLoad
{
[super viewDidLoad];
model = [NSMutableArray arrayWithObjects:
#"zero",
#"one",
#"two",
#"three",
#"four",
nil];
[self.navigationItem setRightBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:
#ifdef THISWORKS
#"\U0001F603"
#else
#"\U0001F4A9"
#endif
style:UIBarButtonItemStylePlain
target:self
action:#selector(doMoves:)]];
}
-(IBAction)doMoves:(id)sender
{
int fromrow = 4, torow = 0, changedrow = 2; // 2 = its "before" position, just like the docs say.
// some model changes happen...
[model replaceObjectAtIndex:changedrow
withObject:[[model objectAtIndex:changedrow] stringByAppendingString:#"\u2032"]];
id tmp = [model objectAtIndex:fromrow];
[model removeObjectAtIndex:fromrow];
[model insertObject:tmp atIndex:torow];
// then we tell the table view what they were
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:changedrow inSection:0]]
withRowAnimation:UITableViewRowAnimationRight]; // again, index for the "before" state; the tableview should figure out it really wants row 3 when the time comes
#ifdef THISWORKS
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:fromrow inSection:0]]
withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:torow inSection:0]]
withRowAnimation:UITableViewRowAnimationAutomatic];
#else // but this doesn't
[self.tableView moveRowAtIndexPath:[NSIndexPath indexPathForRow:fromrow inSection:0]
toIndexPath:[NSIndexPath indexPathForRow:torow inSection:0]];
#endif
[self.tableView endUpdates];
}
#pragma mark - Table view data source boilerplate, not very interesting
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return model.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#""];
if (cell == nil)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#""];
[cell.textLabel setText:[[model objectAtIndex:indexPath.row] description]];
[cell.detailTextLabel setText:[NSString stringWithFormat:#"this cell was provided for row %d", indexPath.row]];
return cell;
}
What the code does: sets up a tiny model (small mutable array); when a button is pushed, it makes a small change to the middle element of the list, and moves the last element to be the first. Then it updates the table view to reflect these changes: reloads the middle row, removes the last row and inserts a new row zero.
This works. In fact, adding logging to cellForRowAtIndexPath shows that although I ask for row 2 to be reloaded, the tableview correctly asks for row 3 because of the insert once it's time to actually do the update. Huzzah!
Now comment out the top #ifdef to use the moveRowAtIndexPath call instead.
Now the tableview removes row 2, asks for a fresh row 2 (wrong!), and inserts it in the final row-2 position (also wrong!). Net result is that row 1 moved down two slots instead of one, and scrolling it offscreen to force a reload shows how it's gone out of sync with the model. I could understand if moveRowAtIndexPath changed the tableview's private model in a different order, requiring the use of the "new" instead of "old" index paths in reloads or model fetches, but that's not what's going on. Note that in the second "after" pic, the third and fourth rows are in the opposite order, which should't happen no matter which cell I'm reloading.
My vocabulary has grown colorful cursing Apple. Should I be cursing myself instead? Are row moves just plain incompatible with row reloads in the same updates block (as well as, I suspect, inserts and deletes)? Can anyone enlighten me before I go file the bug report?
I just spent some time playing with your code, and I agree; looks like it just doesn't work.
This whole area is a bit under-documented, but they don't actually say that you can mix moveRowAtIndexPath:toIndexPath: with reload methods. It does say in the that it can be mixed with row-insertion and row-deletion methods. Those seems to work if I modify your code to exercise those instead. So, you might be asking for an enhancement, not filing a bug. Either way, I'd definitely send it to radar.
I have a UITableViewController and its UITableView is static with three sections. I am trying to populate my static sections in viewWillAppear like so:
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] detailTextLabel] setText:#"Stuff"];
[[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]] detailTextLabel] setText:#"Other stuff"];
...
}
The first line populates row 0 in section 0 fine but the second line (and in fact any cellForRowAtIndexPath call to any section other than the first) returns a nil cell.
I did a few tests to try and narrow down the problem:
[self.tableView numberOfSections] // returns 3 - which is correct
[self.tableView numberOfRowsInSection:1] // return 4 - which is unique and correct for my second section
so, the table view has the correct sections it seems but:
[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1] // nil
I am very confused - does anyone have any idea what might be causing it?
I have nothing special in the code: it's a straight-forward static table created in the storyboard with all three sections set up from the start; the view controller is correctly a subclassing UITableViewController; I can populate section 0 perfectly and all works as I would expect.
Thanks for any help.
If the cells aren't on screen when the view controller initially appears, they have probably not been set up. You should create outlets to the cells you are interested in instead and modify the values that way.