I have two cells with different heights. When I remove the first one, the second one gets the height of the first one
Asked Answered
G

1

9

Some context

In this screen you can add and remove people. Not all fields are required, so the cell height is dynamic.

The problem

If I add first a person with all the fields completed, then a second person with not all fields completed, and then I remove the first person, the second person takes the first place but with the layout of the first person

IMPORTANT

After I remove the first person, if I scroll up until the remaining cell is off screen, then it fixes itself

Code

This is the table source

namespace xXxx.xXxx.iOS
{
    public class SiniestroParticipantesSource : MvxTableViewSource
    {
        private readonly SiniestroParticipantesViewModel viewModel;

        public SiniestroParticipantesSource(UITableView tableView, SiniestroParticipantesViewModel viewModel)
            : base(tableView)
        {
            this.UseAnimations = true;
            this.AddAnimation = UITableViewRowAnimation.Top;
            this.RemoveAnimation = UITableViewRowAnimation.Middle;
            this.viewModel = viewModel;

            tableView.RegisterNibForCellReuse(UINib.FromName(PersonaDenunciaCellView.Key, NSBundle.MainBundle), PersonaDenunciaCellView.Key);
        }

        public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
        {
            tableView.DeselectRow(indexPath, true);
            var itemPersona = this.viewModel.Personas[indexPath.Section];
            this.viewModel.AgregarCommand.Execute(itemPersona);
        }

        protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
        {
            var cell = (PersonaDenunciaCellView)tableView.DequeueReusableCell(PersonaDenunciaCellView.Key, indexPath);
            return cell;
        }

        public override nint RowsInSection(UITableView tableview, nint section)
        {
            return 1;
        }

        public override nint NumberOfSections(UITableView tableView)
        {
            return this.viewModel.Personas.Count;
        }

        protected override object GetItemAt(NSIndexPath indexPath)
        {
            return this.viewModel.Personas[indexPath.Section];
        }
    }
}

This is the cell view

namespace xXxx.xXxx.iOS
{
    public partial class PersonaDenunciaCellView : MvxTableViewCell
    {
        public static readonly NSString Key = new NSString("PersonaDenunciaCellView");
        public static readonly UINib Nib;

        static PersonaDenunciaCellView()
        {
            Nib = UINib.FromName("PersonaDenunciaCellView", NSBundle.MainBundle);
        }

        protected PersonaDenunciaCellView(IntPtr handle) : base(handle)
        {

            this.DelayBind(() =>
            {
                var set = this.CreateBindingSet<PersonaDenunciaCellView, PersonaDenunciaItemViewModel>();

                set.Bind(btnRemove).To(vm => vm.RemoveCommand);

                set.Bind(lblNombre).To(vm => vm.Persona.Persona.Nombre);

                set.Bind(lblTipoDoc).To(vm => vm.Persona.Persona.TipoDoc.Descripcion);
                set.Bind(tipoDocVisibilityConst).For("Priority").To(vm => vm.Persona.Persona.Nrodoc).WithConversion("iOSVisibility", true);

                set.Bind(lblNrodoc).To(vm => vm.Persona.Persona.Nrodoc);
                set.Bind(nroDocVisibilityConst).For("Priority").To(vm => vm.Persona.Persona.Nrodoc).WithConversion("iOSVisibility", true);

                set.Bind(lblMailContacto).To(vm => vm.Persona.MailContacto);
                set.Bind(mailContactoVisibilityConst).For("Priority").To(vm => vm.Persona.MailContacto).WithConversion("iOSVisibility", true);

                set.Bind(lblTelContacto).To(vm => vm.Persona.TelContacto);
                set.Bind(telContactoVisibilityConst).For("Priority").To(vm => vm.Persona.TelContacto).WithConversion("iOSVisibility", true);

                set.Bind(lblLesionado).To(vm => vm.Persona.Lesionado).WithConversion("Lesionado");

                set.Bind(vehiculoVisibilityConst).For("Priority").To(vm => vm.Persona.Patente).WithConversion("iOSVisibility", true);
                set.Bind(viewVehiculo).For("Visibility").To(vm => vm.Persona.Patente).WithConversion("Visibility");
                set.Bind(lblPatente).To(vm => vm.Persona.Patente);
                set.Bind(lblCiaSeguroDesc).To(vm => vm.Persona.CiaSeguroDesc);

                set.Apply();
            });
        }
    }
}

Cell ViewModel

namespace xXxx.xXxx.Core.ViewModels.Items
{

    public class PersonaDenunciaItemViewModel:MvxViewModel
    {
        private readonly IMvxMessenger messenger;
        private readonly IUserInteraction userInteraction;

        public string Index { get; set; }

        public PersonaDenunciaItemViewModel (SiniestroCarga.PersonaDenuncia persona, IMvxMessenger messenger, IUserInteraction userInteraction, string index)
        {
            this.messenger = messenger;
            this.userInteraction = userInteraction;
            this.Persona = persona;
            this.Index = index;
        }

        private SiniestroCarga.PersonaDenuncia persona;
        public SiniestroCarga.PersonaDenuncia Persona
        {
            get
            {
                return this.persona;
            }
            set
            {
                this.persona = value;
                this.RaisePropertyChanged(() => this.Persona);
            }
        }

        private ICommand removeCommand;
        public ICommand RemoveCommand
        {
            get
            {
                return this.removeCommand = this.removeCommand ?? new MvxCommand(this.RemovePersona);
            }
        }

        private void RemovePersona()
        {
            this.userInteraction.Confirm("Are you sure?", () =>
            {
                this.messenger.Publish(new RemovePersonaDenunciaMessage(this, this.Persona, this.Index));
            }, null, "OK", "Cancel");
        }
    }

}

And that last messenger.Publish is subscribed to this on another ViewModel

OnRemovePersonaDenuncia = message =>
                {
                    var listPersonas = new ObservableCollection<PersonaDenunciaItemViewModel>(this.Personas);
                    listPersonas.Remove(this.Personas.First(p => p.Index == message.Index));
                    this.Personas = listPersonas;
                }

Updates

Changing the implementation of RemoveCommand to this

this.Personas.Remove(this.Personas.First(p => p.Index == message.Index));

Makes that, when I press the remove button, the simulator closes without any error. The application trace on Xamarin Studio shows this

*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/UITableView.m:1700

Gaffer answered 6/9, 2016 at 18:38 Comment(7)
Are you using Autolayout in your TableViewCells ? Also can you post your viewmodel ? Especially the RemoveCommand implementationDisenchant
@Disenchant yes, I'm using AutoLayout. I will update the post nowGaffer
@Disenchant updatedGaffer
Can you try this: In OnRemovePersonaDenuncia change the implementation to this.Personas?.Remove(message.Persona) ?Disenchant
@Disenchant this.Personas and message.Persona are different types, so "cannot convert from xXxx.xXxx.Core.Models.Persona to xXxx.xXxx.Core.ViewModels.Items.PersonaItemViewModel"Gaffer
I see just update to this instead: this.Personas?.Remove(this.Personas.First(p => p.Index == message.Index)Disenchant
I have updated the post to show the resultsGaffer
C
1

iOS does not calculate the height of the cells correctly when the content height is dynamic. That´s very annoying but you can override GetHeightForRow and EstimatedHeight in SiniestroParticipantesSource to calculate the exact height of each cell depending on the data:

public override nfloat EstimatedHeight(UITableView tableView, NSIndexPath indexPath) => 
    GetHeightForRow(tableView, indexPath);

public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
    var data = (PersonaDenunciaItemViewModel)ItemsSource.ElementAt(indexPath.Row);
    var cell = (PersonaDenunciaCellView)tableView.DequeueReusableCell(PersonaDenunciaCellView.Key, indexPath);

    // TODO set the cell data manually (ignoring bindings)
    // i.e: **cell.lblNombre = data.Persona.Persona.Nombre**; // and every other field required

    cell.SetNeedsLayout();
    cell.LayoutIfNeeded();

    var size = cell.ContentView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
    return NMath.Ceiling(size.Height) + 1;
}

Here´s an example using the very same code.

Corena answered 6/9, 2016 at 20:27 Comment(4)
Why should I ignore bindings?Gaffer
Because you don´t want to wait for any binding to do its work. You want to asign values right away. This operation is just to calculate heights. Not related to mvvmcross bindingsCorena
I can't access the cell elements such as cell.lblNombre. "due to its protection level"Gaffer
lblNombre is probably auto generated in the designer file. Expose it in by public UILabel LblNombre => lblNombreDisenchant

© 2022 - 2024 — McMap. All rights reserved.