简体   繁体   中英

Xamarin Forms iOS custom UITableViewCell dynamic height

I'm trying to create custom UITableViewCell for Xamarin Forms that will automatically adjust it's height according to the text it contains.

I have found solutions that are made through Designer, but I need to do this in code.

This is what I have now after analyzing the Designer solutions, but it does not work:

public class FormLabelCellView : UITableViewCell, INativeElementView
{
    public UILabel Label { get; private set; }
    public FormLabelCell Cell { get; set; }
    public Element Element => Cell;

    public FormLabelCellView(FormLabelCell cell) : base(UITableViewCellStyle.Default, cell.GetType().FullName)
    {
        Cell = cell;
        // in the custom ContentView solution I can't set the cell to expand and contract according to the text it contains
        Label = new UILabel();

        Label.TranslatesAutoresizingMaskIntoConstraints = false;
        Label.SetContentCompressionResistancePriority(751, UILayoutConstraintAxis.Vertical);
        Label.SetContentHuggingPriority(251, UILayoutConstraintAxis.Vertical);

        this.ContentView.AddSubview(Label);

        this.ContentView.AddConstraint(NSLayoutConstraint.Create(Label, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.ContentView, NSLayoutAttribute.Left, 1, 0));
        this.ContentView.AddConstraint(NSLayoutConstraint.Create(Label, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.ContentView, NSLayoutAttribute.Right, 1, 0));
        this.ContentView.AddConstraint(NSLayoutConstraint.Create(Label, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.ContentView, NSLayoutAttribute.Top, 1, 0));
        this.ContentView.AddConstraint(NSLayoutConstraint.Create(Label, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, this.ContentView, NSLayoutAttribute.Bottom, 1, 0));
    }
}

This is the result (the row height is probably DefaultRowHeight of 44):

自定义ContentView解决方案

Before anyone asks about RowHeight and EstimatedRowHeight . Yes I have set RowHeight = UITableView.AutomaticDimension; and EstimatedRowHeight = 24; in my custom ListViewRenderer.

I can also verify that it works when I modify my custom UITableViewCell to this:

public class FormLabelCellView : UITableViewCell, INativeElementView
{
    public UILabel Label { get; private set; }
    public FormLabelCell Cell { get; set; }
    public Element Element => Cell;

    public FormLabelCellView(FormLabelCell cell) : base(UITableViewCellStyle.Default, cell.GetType().FullName)
    {
        Cell = cell;
        // this solution works, but I can't remove the margins / padding and set background color
        Label = this.TextLabel;
    }
}

Then the result looks like this:

使用TextLabel

I think I'm not creating the constraints correctly. Can someone help me?

Edit: After trying to create custom UITableViewSource for testing purposes I have found that the problem is in the Xamarin implementation of ListViewDataSource and UnevenListViewDataSource . This is unfortunate because these classes are internal so I can't extend them and override the GetHeightForRow function.

In my test, If I set RowHeight = UITableView.AutomaticDimension; and EstimatedRowHeight = 24; in the listViewRenderer, I will get the same effect as you show: the TableView seems having an equal row height.

But when I try to use another way: using override method in the tableView's source like:

public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
    return UITableView.AutomaticDimension;
}

public override nfloat EstimatedHeight(UITableView tableView, NSIndexPath indexPath)
{
    return 24;
}

It works fine(The cell will auto resize its row height in the run time).

Moreover I really recommend you to use this method to construct your Cell:

public MyListViewCell(NSString cellId) : base(UITableViewCellStyle.Default, cellId)
{
     //add your controls in the cell's ContentView
}

Then we can use it in the Source 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"));
    }

    //Update your data

    return Cell;
}
public void SetUpCell(string ParticipantName)
        {
            ParticipantNameLabel.Text = ParticipantName;
        }

public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
            var cell = tableView.DequeueReusableCell("PeopleCell", indexPath) as PeopleCell;

            if (cell == null)
            {
                cell = new PeopleCell(new NSString("PeopleCell"));
            }
            cell.SelectionStyle = UITableViewCellSelectionStyle.None;
            cell.SetUpCell(ListPeople[indexPath.Row]);

            return cell;
        }

When I call cell.SetUpCell() I am getting a null reference on the ParticipantNameLabel which I instantiate inside of the constructor that you demonstrated above.

public MyListViewCell(NSString cellId) : base(UITableViewCellStyle.Default, cellId)
{
     //add your controls in the cell's ContentView
}

If i place the instantiation within constructor below, I don't get this null reference.

public PeopleCell(IntPtr handle) : base(handle)
        {
        }

What am I missing?

So to answer my own question. The problem is in the Xamarin implementation of internal classes ListViewDataSource and UnevenListViewDataSource . Because we can't extend these classes there are two possible solutions for dynamic row height in ListView.

  1. Create own implementation of UITableViewSource .
  2. Use ViewCell to create your custom cell.

I have used the second option, because I'm doing multi-platform application and I don't want to create complicated implementation of UITableViewSource .

So this is example implementation of custom ViewCell:

public class CustomLabelCell : ViewCell
{
    private Label label;

    public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(CustomLabelCell), null);

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    // disable public set on View property
    public new View View
    {
        get { return base.View; }
        private set { base.View = value; }
    }

    public CustomLabelCell()
    {
        this.label = new Label()
        {
            Text = this.Text
        };
        ContentView frame = new ContentView()
        {
            Padding = new Thickness(4.0),
            Content = label
        };
        this.View = frame;
    }

    protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);

        if (propertyName == TextProperty.PropertyName)
        {
            this.label.Text = this.Text;
            ForceUpdateSize();
        }
    }
}

And this is how you would use it in ListView:

ListView listView = new ListView(ListViewCachingStrategy.RecycleElement)
{
    HasUnevenRows = true,
    ItemsSource = new ObservableCollection<TestObj> { new TestObj("one - example text that will reach s e c o n d row"), new TestObj("two"), new TestObj("three - longer example text with more words for third row that will span across three rows"), new TestObj("four"), new TestObj("five") },
    ItemTemplate = new DataTemplate(typeof(CustomLabelCell))
};
listView.ItemTemplate.SetBinding(CustomLabelCell.TextProperty, "Name");

For completeness this is implementation of TestObj:

public class TestObj : System.ComponentModel.INotifyPropertyChanged
{
    private string name;

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs("Name"));
        }
    }

    public TestObj(string name)
    {
        this.name = name;
    }
}

As you see there is no necessity to create custom renderers and custom cells in platform specific code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM