2
votes

I am trying to write a CustomRenderer that can allow a ListView to have alternating row colors. I have run into an issues and am not sure if it might be a bug or am I just not implementing the renderer correctly:

My expectations for the following code is that each custom view cell should get a color based on it position. However, this is not what is happening, I believe that this this code tv.IndexPathForCell(cell).Row should return the cell's index but it is not working as expected. It is currently returning a null value ( which it should if the cell in not part view), but I believe that the cell is part of the table view and thus my issue. If I comment out the var index = tv.IndexPathForCell(cell).Row; and then set the background color to a static value the code and view works correctly. In addition an exception is thrown because of the null value and the cell is set to the base and the view renders with a default background.

Am I doing something wrong, I have tried several other things but I believe this should be the correct way to do this.

c# code:

[assembly: ExportRenderer(typeof(CustomViewCell), typeof(CustomViewCellRenderer))]


namespace AIM.iOS {

  class CustomViewCellRenderer : ViewCellRenderer {
    public override UITableViewCell GetCell(Cell item, UITableView tv) {
       UITableViewCell cell;

       try {
          cell = base.GetCell(item, tv);

          var customCell = (CustomViewCell)item;
          var cell = base.GetCell(item, tv);

          var index = tv.IndexPathForCell(cell).Row;

          cell.BackgroundColor = (index % 2 == 0) ? Color.Gray.ToUIColor() :
                                                  Color.White.ToUIColor();
       }catch (Exception ex){
          string message = ex.Message;
          cell = base.GetCell(item, tv);
       }

       return cell  
    }
  }
}

I am extending the ExtendedViewCell class from Xamarin-Forms-Labs which adds background properties to the ViewCell object.

namespace AIMUI.Controls {
  public class CustomViewCell : ExtendedViewCell {
    public CustomViewCell() { }
  }
}

Here is the Xaml:

<localcontrols:ListView>
  <ListView.ItemTemplate>
    <DataTemplate>
      <localcontrols:CustomViewCell>
        <localcontrols:CustomViewCell.View>
        </localcontrols:CustomViewCell.View>
      </localcontrols:CustomViewCell>
    </DataTemplate>
  </ListView.ItemTemplate>
</localcontrols:ListView>

Edit:

Ok, so I solved the issue with getting the index by using var index = tv.IndexPathForRowAtPoint(cell.Center).Row;, however this has caused another issue. It works perfectly the first time, as well as coming back to the page from a sub view. However, when I leave the page and go to another view from the masterpage and come back it sets the background of all items gray. Currently, I am creating a new page and then setting it to the masterpage.details, when coming from the menu.

3

3 Answers

2
votes

This can be done using a converter instead of a renderer.

public class StripedBackgroundIndexConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null || parameter == null) return Color.White;
        var index = ((ListView) parameter).ItemsSource.Cast<object>().ToList().IndexOf(value);
        return index % 2 == 0 ? Color.White : Color.FromRgb(240, 240, 240);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Then name your ListView and pass it as the parameter using x:Reference

<ListView x:Name="MyItemsListView" ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedMyItem, Mode=TwoWay}">
<ListView.ItemTemplate>
  <DataTemplate>
    <ViewCell>
      <Grid BackgroundColor="{Binding .,Converter={StaticResource StripedBackgroundIndexConverter}, ConverterParameter={x:Reference MyItemsListView}}">

      </Grid>
    </ViewCell>
  </DataTemplate>
</ListView.ItemTemplate>

Of course you can also use the converter in your renderer if you are needing to customize other elements.

1
votes

As you've already found out IndexPathForRowAtPoint will return the index of the Row in the datasource even if the row isn't visible, unlike the IndexPathForCell that will not work correctly until the cell is visible.

With regard to your navigation issue, when you Push pages onto the Xamarin.Forms Navigation Stack they are not unloaded and still are functional behind the scenes. This is why when you navigate back through the Navigation Stack and Pop one off, you are seeing the same page exactly the same as you had navigated away from it (unless of course you have some logic in one of the Page event-handlers that will get re-invoked doing something other processing).

In iOS the cells are Enqueued and Dequeued when appropriate, and may still reside in memory although they are not visible, awaiting Garbage Collection.

It sounds very weird behavior that when you navigate away from the page (not to a subview) and then come back to this page, that it doesn't work, due to the modulus that you are using.

As an alternative, just for exploration and testing purposes, introduce another field into your Model that has an increasing ID that you do the modulus on to set the background-color of the row, to see if you can isolate the IndexPathForRowAtPoint function being possibly where the main issue is.

If you able to create a small project with only this in, it would be very helpful for those in assisting further?

0
votes

After contacting Xamarin Support, and with their help this was the result.

indexPathForPoint is not reliable based on how and when the actual cell is on the screen. Typically, the GetCell method has the NSIndexPath parameter which is reliable, but the Forms abstraction does not pass this along.

Suggested as an enhancement HERE

As a workaround, you could use another method for index. However, this did not worked for me, as it was not 100% reliable. I did not find a great way to implement this functionality.

As a side note - I feel that this was over looked in the Xamarin code design process, It should not be this hard to simply get the index of each cell.