I have a UITableViewSource which I have subclassed. I'm overriding GetCell and using my own subclassed cells, like so:
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
MarketItem item=_tableItems[indexPath.Section].Items[indexPath.Row];
MarketCell cell=tableView.DequeueReusableCell(_cellIdentifier) as MarketCell;
if (cell==null)
{
cell=new MarketCell(UITableViewCellStyle.Subtitle,_cellIdentifier,item);
}
// decorate the cell
// ...
return cell;
}
This works but when I get events in my UITableViewDelegate, the index path gets me the wrong cell (events like AccessoryButtonTapped, WillSelectRow etc).
The Section and Row numbers look correct but when I do a
tableView.CellAt(indexPath)
I get the wrong cell. (The row and section numbers again look correct.)
Things to note:
- The table is constantly being updated - items arrive in a different thread which are then InvokeOnMainThread'd
- Although the table is constantly updated, rows and sections are only added - nothing is re-ordered or deleted
- If I pause the updates when I get a 'WillSelectRow', it doesn't help
- Most interestingly (but not a shippable solution) if I make a new cell each time rather than doing DequeueReusableCell, it works correctly.
I can't help thinking it's a stupid bug of my own making but can't find it. Any help would be most gratefully received!
You could try a different approach as demonstrated on: http://simon.nureality.ca/?p=91
Basically, don't subclass UITableViewCell but instead subclass UIViewController as "MarketCellController". This custom controller maintains a standard UITableViewCell as well as your custom stuff and simply adds your custom stuff via AddSubview().
You create one new controller for each required cell and store them into a dictionary.
The trick: By assigning unique tags to the cells, you can retrieve the associated controller from the dictionary.
Quick example:
Dictionary<int,MarketCellController> controllers = new Dictionary<int,MarketCellController>();
// ...
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) {
UITableViewCell cell = tableView.DequeueReusableCell(_cellIdentifier);
MarketCellController cellController;
if (cell == null) {
cellController = new MarketCellController();
cell = cellController.Cell;
controllers.Add(cell.Tag, cellController);
} else {
cellController = controllers[cell.Tag];
}
// Decorate the cell (using Methods of your cellController)
// ...
return cell;
}
This could eventually circumvent problems with subclassed UITableViewCells and dequeuing.
EDIT:
I just had something else in mind: You seem to be only assinging your items on your MarketCell constructor. However, dequeued cells have their old item set and you need to reset it to the new item after dequeuing.
Remove the item parameter of the constructor (it'll be overwritten anyways) and create a public property (or setter) instead. Use that to assign the correct item after fetching the cell (regardless if it was dequeued or newly created). That should do:
MarketCell cell = tableView.DequeueReusableCell(_cellIdentifier) as MarketCell;
if (cell == null) {
cell = new MarketCell(UITableViewCellStyle.Subtitle, _cellIdentifier);
}
// Assign the correct item
cell.Item = item;
// or (whatever you like more):
// cell.SetItem(item);
// Decorate the cell
// ...
return cell;
The same goes for my cellController approach as well. No matter if a cell was dequeued or new, you should always reset everything on the cell that could possibly differ from any other cells.
BTW: If you hard-code your cells to UITableViewCellStyle.Subtitle, you could just omit it from the constructor as well and hard-code it into your MarketCell class.
As for what the cellController approach is worth: You decouple the UITableViewCell from your custom data and behaviour and thus get a nice layer of separation. Your cell controller does not have to act like a table cell ;)
It appears this is still a problem in Xamarin. The link posted by @vlad259 has unfortunately long disappeared - and this 'bug' just bit me today. I don't know why it's happening - in the interests of tracking down the cause, I have resorted to cutting my UITableViewSource implementation all the way down to returning a fixed number of rows, and simply assigning the row-number to the cell label...
The bug manifests specifically as, load the view, scroll to the first off-screen cell (i.e, if your table can render 10 cells, scroll so the 11th just comes on screen), then tap that cell - you will get an almost random Row index in your RowSelected event.
I have found a couple of solutions to this.
Solution A
This bug only appears to manifest if I have overridden GetHeightForRow
to return custom row heights - even if it's returning a constant. If it's not needed... don't override it.
Solution B
It appears that the bug is specifically within the DequeueReusableCell
(s) which takes an NSIndexPath
argument.
Use
UITableView.DequeueReusableCell(NSString)
instead of
UITableView.DequeueReusableCell(NSString, NSIndexPath)