Apollonas
Apollonas

Reputation: 667

DequeueReusableCell in Monotouch causes cells to rearrange when scrolling

I am using the Monotouch.Dialog class to populate a UITableView. I am using a custom cell and as you can see the code in the Element that gets the cell is as follows:

public override UITableViewCell GetCell (UITableView tv)
{
var cell = tv.DequeueReusableCell(_key) as FoodItemCell;

if (cell == null)
   cell = new FoodItemCell(_foodItem, _key);
else
   cell.UpdateCell(_foodItem);

return cell;
}

I got this code from a Miguel De Icaza post online that suggest the proper way to create custom cells for the MonoTouch.Dialog.

The Data is displayed appropriately but when I have a long list and I scroll up and down quickly, the cells swap places in a random unpredictable fashion. So the cell in row in may end up in row 4 when I am finished scrolling.

When I use the following code this problem is solved but as you can see I am not dequeueing cells and this may create memory issues.

private FoodItemCell _currentCell;

public override UITableViewCell GetCell (UITableView tv)
{
    if(_currentCell == null)
    {
        _currentCell = new FoodItemCell(_foodItem, _key);
        return _currentCell;
    }
    else
    {
        return _currentCell;
    }
}

Here is the full code:

public class FoodItemElement: Element
{
    private static NSString _key = new NSString ("foodItemCell");
    private FoodItem _foodItem;
    public event EventHandler<ElementClickedEventArgs> ElementClicked;


    public FoodItemElement(FoodItem foodItem): base(null)
    {
        _foodItem = foodItem;
    }

    public override UITableViewCell GetCell (UITableView tv)
    {
        var cell = tv.DequeueReusableCell(_key) as FoodItemCell;

        if (cell == null)
            cell = new FoodItemCell(_foodItem, _key);
        else
            cell.UpdateCell(_foodItem);

        return cell;
    }
}


public class FoodItemCell: UITableViewCell
{    
    private FoodItemCellDataView _dataView;
    private FoodItem _foodItem;

    public FoodItemCell(FoodItem data, NSString key) : base (UITableViewCellStyle.Default, key)
    {
        _foodItem = (FoodItem)data;
        _dataView = new FoodItemCellDataView(_foodItem);

        ContentView.Add(_dataView);
        ConstructCell();
    }

    public override void LayoutSubviews ()
    {
        base.LayoutSubviews ();
        _dataView.Frame = ContentView.Bounds;
        _dataView.SetNeedsDisplay ();
    }

     /// <summary>
     /// Updates the cell.
     /// </summary>
    public void UpdateCell(FoodItem newData)
    {
        _dataView.Update(newData);
        SetNeedsLayout();
    }

    /// <summary>
    /// Constructs the cell.
    /// </summary>
    private void ConstructCell()
    {
        //This prevents the default blue color when selecting the cell.
        //this.SelectionStyle = UITableViewCellSelectionStyle.None;
        //this.ContentView.BackgroundColor = UIColor.White;

        var lblFoodItemName = new UILabel();
        lblFoodItemName.Frame = new System.Drawing.RectangleF(4,2,270,20);
        lblFoodItemName.TextColor = UIColor.Black;
        lblFoodItemName.Font = Fonts.H3;
        lblFoodItemName.BackgroundColor = UIColor.Clear;
        lblFoodItemName.Text = _foodItem.Name;

        var lblCalories = new UILabel();
        lblCalories.Frame = new System.Drawing.RectangleF(15,22,75,20);
        lblCalories.TextColor = UIColor.Black;
        lblCalories.Font = Fonts.Small;
        lblCalories.BackgroundColor = UIColor.Clear;
        lblCalories.Text = "CAL - " + _foodItem.CaloriesRounded.ToString();

        var lblPortion = new UILabel();
        lblPortion.Frame = new System.Drawing.RectangleF(95,22,190,20);
        lblPortion.TextColor = UIColor.Black;
        lblPortion.Font = Fonts.Small;
        lblPortion.BackgroundColor = UIColor.Clear;
        lblPortion.Text = "Portion - " + _foodItem.Portion.ToString();

        this.ContentView.AddSubview(lblFoodItemName);
        this.ContentView.AddSubview(lblCalories);
        this.ContentView.AddSubview(lblPortion);

        Accessory = UITableViewCellAccessory.DisclosureIndicator;
    }   
}


// I create a view that renders my data, as this allows me to reuse
// the data rendering outside of a UITableViewCell context as well.
public class FoodItemCellDataView : UIView {

     public FoodItemCellDataView(FoodItem foodItem)
     {
          Update (foodItem);
     }

     // Public method, that allows the code to externally update
     // what we are rendering.   
     public void Update(FoodItem foodItem)
     {
          SetNeedsDisplay();
     }
}

Does anyone have an idea how I can properly dequeue the cells and not have the scrolling issue?

Upvotes: 1

Views: 1376

Answers (2)

Jannie Theunissen
Jannie Theunissen

Reputation: 30164

Your code does not show what _dataView.Update(newData) does, but I am usuming it doesn't change anything on the cell layout, but just refreshes the UI elements with new data. In that case (and I must be mad to contradict Miguel), you should be calling cell.UpdateCell(_foodItem) with every visit to GetCell whether the cell is dequeued or new, so:

public override UITableViewCell GetCell (UITableView tv)
{
var cell = tv.DequeueReusableCell(_key) as FoodItemCell;

if (cell == null)
   cell = new FoodItemCell(_foodItem, _key);
// else  <- I would remove this
   cell.UpdateCell(_foodItem);

return cell;
}

Upvotes: 1

poupou
poupou

Reputation: 43553

It's difficult to be sure why without seeing more source code (see my comment).

Here's a working example (BubbleCell) that you can use to compare your code with.

One important part is to call SetNeedsLayout in your UpdateCell method. Otherwise the old content might appear (in the new reused cell) before a refresh is done. That could explain your issue.

Upvotes: 0

Related Questions