9

I have a custom ListView using custom ViewCells with radio buttons. On clicking of each of the radio buttons the ListView dynamically resizes its height to hide/show a comment box.

On using ForceUpdateSize in iOS platform, the ListView performance slows down rapidly on clicking the radio buttons. The app eventually hangs and stops responding.

Is there an alternative solution to use instead of ForceUpdateSize to dynamically expand the ListView row at runtime?

iBug
  • 35,554
  • 7
  • 89
  • 134
Apurva19
  • 105
  • 1
  • 6
  • Removed tags from title; trademark capitalization; noise reduction. – iBug Jan 10 '18 at 12:05
  • The documentation even says that this can be an expensive operation: https://developer.xamarin.com/api/member/Xamarin.Forms.Cell.ForceUpdateSize()/ – Cheesebaron Jan 10 '18 at 12:39

2 Answers2

12

Define a ViewCell Size Change event wherever you need to change your ViewCell size

public static event Action ViewCellSizeChangedEvent; 

In your case, it should be triggered by your radio button. Call it like this:

ViewCellSizeChangedEvent?.Invoke();

It will then use the ListView renderer to update the iOS TableView.

public class CustomListViewRenderer : ListViewRenderer
{
    public CustomListViewRenderer()
    {
        WhatEverContentView.ViewCellSizeChangedEvent += UpdateTableView;
    }

    private void UpdateTableView()
    {
        var tv = Control as UITableView;
        if (tv == null) return;
        tv.BeginUpdates();
        tv.EndUpdates();
    }
}

It should solve your performance problem while keep using your Xaml instead of creating a custom ViewCell which is not needed.

Duke Y
  • 136
  • 4
  • 1
    Can you explain why `BeginUpdates` and `EndUpdates` fix the performance problem? I thought they were being used for cell animations. – user246392 Mar 28 '19 at 15:43
  • I can't tell for sure, but the standard xamarin.ios cell-resizing appears to be animated. When I make changes to the size the viewcell animates a linear collapsing/expanding by default – Csharpest Apr 16 '19 at 13:40
  • OMG you cannot imagine how much time I spent checking around for this. Nothing worked, until I saw this. Phew, thanks Duke. Btw, if you don't want to use a static event, you can set a new property like `NeedsLayout` or anything which gets called in the renderer's `OnElementPropertyChanged()`-method. – Waescher Jul 07 '20 at 22:00
  • Thank you a thousand times for this! We were seeing random run-time crashes related to our use of ForceUpdateSize. This solution saved me from untold pain and anguish. – bonetoad Nov 11 '22 at 17:04
0

My solution is: Try to use custom renderer. When the button clicked, I use tableView.ReloadRows() to dynamically change the size of a cell.

Firstly, define a bool list whose items equal to Rows you want to show in the Source. I initialize its items with false at first time.

List<bool> isExpanded = new List<bool>();

public MyListViewSource(MyListView view)
{
    //It depends on how many rows you want to show.
    for (int i=0; i<10; i++) 
    {
        isExpanded.Add(false);
    }
}

Secondly, Construct the GetCell event(I just put a UISwitch in my Cell for testing) like:

public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{

    MyListViewCell cell = tableView.DequeueReusableCell("Cell") as MyListViewCell;

    if (cell == null)
    {
        cell = new MyListViewCell(new NSString("Cell"));

        //This event is constructed in my Cell, when the switch's value changed it will be fired.
        cell.RefreshEvent += (refreshCell, isOn) =>
        {
            NSIndexPath index = tableView.IndexPathForCell(refreshCell);
            isExpanded[index.Row] = isOn;
            tableView.ReloadRows(new NSIndexPath[] { index }, UITableViewRowAnimation.Automatic);
        };
    }

    cell.switchBtn.On = isExpanded[indexPath.Row];

    return cell;
}

At last, we can override the GetHeightForRow event. Set a large or small value depending on the item in the isExpanded:

public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
    if (isExpanded[indexPath.Row])
    {
        return 80;
    }
    return 40;
}

Here is some part of my cell for you referring to:

//When switch's value changed, this event will be called
public delegate void RefreshHanle(MyListViewCell cell, bool isOn);
public event RefreshHanle RefreshEvent;
switchBtn.AddTarget((sender, args) =>
{
    UISwitch mySwitch = sender as UISwitch;
    RefreshEvent(this, mySwitch.On);
}, UIControlEvent.ValueChanged);
Ax1le
  • 6,563
  • 2
  • 14
  • 61
  • my listview is a cross platform control. It works perfectly fine in android. Can this above solution be applied to ios custom listview renderer? – Apurva19 Jan 13 '18 at 07:25
  • @Apurva19 Yep,I made a renderer for listview to do this. – Ax1le Jan 13 '18 at 07:37
  • @ Land Thanks, il try out your solution and post back. – Apurva19 Jan 17 '18 at 07:01
  • @ Land Can you tell me where does that RefreshEvent event handler code which u have given should be added. I m slight confused with it. In my case i have used radio button images binded to commands in view model. – Apurva19 Jan 22 '18 at 13:24
  • @Apurva19 I create this refresh event in my custom cell, when the toggle button’s value changes it will be called. If you are still confused about it, I can make a sample for you. – Ax1le Jan 22 '18 at 13:40
  • @Apurva19 I made a simple sample [here](https://github.com/lt0526/ListViewDemo) in iOS renderer. Each cell has a switch controlling the cell's height. – Ax1le Jan 23 '18 at 03:47
  • Lu - Hi i finally used the default listview and fixed the issue. Thanks. – Apurva19 Mar 07 '18 at 07:29
  • Could you please provide the definition of MyListViewCell as well? – Amir Hajiha Apr 16 '21 at 22:59