Search code examples
xamarin.formslistviewitem

Dynamically hiding Xamarin Forms ListView item and setting the height of invisible item to 0 not working on iOS


In a Xamarin Forms ListView, a StackLayout is placed in a ViewCell. The following trigger is used to set the StackLayout's height and margin to 0 so that there is no gap in the ListView.

<StackLayout.Triggers>
    <Trigger TargetType="StackLayout" Property="IsVisible" Value="False">
        <Setter Property="HeightRequest" Value="0" />
        <Setter Property="Margin" Value="0" />
    </Trigger>
</StackLayout.Triggers>

The code works perfectly on Android. But there are still gaps on iOS.

Normally calling ViewCell.ForceUpdateSize on a tapping event can help resolve such an issue. But we need to do it grammatically. So I tried to create a CustomViewCell but it doesn't help.

public class CustomViewCell : ViewCell
{
    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();

        var viewModel = (tecommobile.ViewModels.Input)BindingContext;
        viewModel.PropertyChanged += (sender, e) =>
        {
            if (Device.RuntimePlatform == Device.iOS)
            {
                if (e.PropertyName == "IsVisible")
                {
                    //ForceUpdateSize(); crashes, the possible cause could be the height of the StackLayout is already set to 0. The test shows that the tapped event doesn't fire if the height is 0.
                }
            }
        };
    }
}

Please advise a solution or any workaround. Thanks.


Solution

  • We should use an TriggerAction to change this value, and tell the ViewCell to update its size.

    public class ForceUpdateSizeTriggerAction : TriggerAction<VisualElement>
    {
        public double HeighRequest { set; get; }  // you could set it as bindable property if you want to binding its value in runtime
    
        public Thickness CustomMargin { set; get; }
    
        public ForceUpdateSizeTriggerAction() : base()
        {
    
        }
        protected override void Invoke(VisualElement sender)
        {
    
            var parent = sender.Parent;
            while (parent != null && !(parent is ViewCell))
            {
                parent = parent.Parent;
            }
            if (parent is ViewCell cell)
            {
                Device.BeginInvokeOnMainThread(() =>
                {
                    var view = sender as View;
                    view.HeightRequest = HeighRequest;
                    view.Margin = CustomMargin;
                    cell.ForceUpdateSize();
                });
            }
        }
    }
    

    in xaml

    <StackLayout.Style>
       <Style TargetType="StackLayout">
           <Setter Property="HeightRequest" Value="xxx"/> //default height
              <Style.Triggers>
                <DataTrigger TargetType="StackLayout" Binding="{Binding xxx}"  Value="False">  /// xxx here is the property which binding to the IsVisible of stacklayout
                       <DataTrigger.EnterActions>
                            <local:ForceUpdateSizeTriggerAction HeighRequest="0.01" CustomMargin="0"/>
                       </DataTrigger.EnterActions>
    
                       <DataTrigger.ExitActions>
                            <local:ForceUpdateSizeTriggerAction HeighRequest="xxx" CustomMargin="xxx"/> // set the default value of height and margin here
                       </DataTrigger.ExitActions>
              </DataTrigger>
           </Style.Triggers>
       </Style>
    </StackLayout.Style>
    

    And don't forget to set HasUnevenRows="True" of listview.