Listview not updated when isVisible is set to true
Asked Answered
S

1

8

I'm trying to make a ListView with clickable rows. When you click on a row it sets a child stacklayout to visibility true. This works fine in android, but not in ios. Maybe I'm doing it the wrong way. I am still a beginner, any idea how to fix this or any other better approach? The problem is that it opens on ios so the visibility changes, but it does not update the height of the cell. The cell updates off-screen for example if you scroll up until you can't see the opened cell anymore and then scroll back down. You'll see it has updated the height.

I tried using a custom renderer, but i have no idea where to start.

This is my xaml:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Lisa.Excelsis.Mobile.AssessmentPage" xmlns:local="clr-namespace:Lisa.Excelsis.Mobile;assembly=Lisa.Excelsis.Mobile">
<StackLayout>
    <local:SpecialListView x:Name="CategoryList" 
                ItemsSource = "{Binding Categories}" 
                HasUnevenRows="true" 
                RowHeight="-1" 
                GroupDisplayBinding="{Binding Name}"
                IsGroupingEnabled="true">
        <local:SpecialListView.ItemTemplate>
            <DataTemplate> 
                <ViewCell x:Name="ObservationCell">
                    <ViewCell.View>
                        <StackLayout x:Name="ObservationContainer"
                                     HorizontalOptions="FillAndExpand"
                                     Orientation="Vertical"
                                     VerticalOptions="StartAndExpand"
                                     BackgroundColor="White">   
                            <StackLayout x:Name="Observation"   
                                         HorizontalOptions="FillAndExpand"
                                         VerticalOptions="StartAndExpand"                                        
                                         Padding="15, 10, 10, 10"
                                         BackgroundColor="White">   
                                <StackLayout.GestureRecognizers>
                                    <TapGestureRecognizer Tapped="OpenItem"/>
                                </StackLayout.GestureRecognizers>

                                <Grid HorizontalOptions="FillAndExpand" >
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto" />     
                                        <RowDefinition Height="Auto" />                 
                                    </Grid.RowDefinitions>
                                   
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="35" />
                                        <ColumnDefinition Width="*" />
                                    </Grid.ColumnDefinitions>

                                    <Label  x:Name="ObservationOrder" 
                                            Text="{Binding Criterion.Order, StringFormat='{0}.'}" 
                                            FontSize="18" 
                                            VerticalOptions="StartAndExpand" 
                                            Grid.Column="0" Grid.Row="0"/>   

                                    <Label  x:Name="ObservationTitle" 
                                            Text="{Binding Criterion.Title}" 
                                            FontSize="18" 
                                            VerticalOptions="StartAndExpand" 
                                            Grid.Column="1" Grid.Row="0"/>
                                    
                                </Grid>
                            </StackLayout>
                            <StackLayout x:Name="ObservationButtons"
                                         HorizontalOptions="FillAndExpand"
                                         VerticalOptions="StartAndExpand"
                                         BackgroundColor="White"
                                         IsVisible="false"
                                         Padding="0, 0, 0, 20"
                                         ClassId = "{Binding Id, StringFormat='ObservationButtons_{0}'}">

                                <Grid HorizontalOptions="Center"                                         
                                      Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto" />                 
                                    </Grid.RowDefinitions>

                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="80" />
                                        <ColumnDefinition Width="80" />
                                        <ColumnDefinition Width="10" />
                                        <ColumnDefinition Width="80" />
                                        <ColumnDefinition Width="80" />
                                        <ColumnDefinition Width="80" />
                                        <ColumnDefinition Width="80" />
                                    </Grid.ColumnDefinitions>

                                    <StackLayout Grid.Column="0" Grid.Row="0" >
                                        <Image  Source="yesnobutton0.png" 
                                                HeightRequest="60" WidthRequest="60"
                                                HorizontalOptions="Center"
                                                VerticalOptions="Start"
                                                x:Name="yesImage">   
                                            <Image.GestureRecognizers>
                                                <TapGestureRecognizer Tapped="SetYesImage"/>
                                            </Image.GestureRecognizers>
                                        </Image>

                                        <Label Text="Ja" VerticalOptions="End" HorizontalOptions="Center"/>
                                    </StackLayout>

                                    <StackLayout Grid.Column="1" Grid.Row="0">
                                        <Image  Source="yesnobutton0.png"
                                                HeightRequest="60" WidthRequest="60"
                                                HorizontalOptions="Center"
                                                VerticalOptions="Start"
                                                x:Name="noImage">
                                            <Image.GestureRecognizers>
                                                <TapGestureRecognizer Tapped="SetNoImage"/>
                                            </Image.GestureRecognizers>
                                        </Image>
                                            
                                        <Label Text="Nee" VerticalOptions="End" HorizontalOptions="Center"/>
                                    </StackLayout>

                                    <Image  Source="maybenot.png"
                                            HeightRequest="60" WidthRequest="60"
                                            HorizontalOptions="Center"
                                            VerticalOptions="Start"
                                            Grid.Column="3" Grid.Row="0">
                                        <Image.GestureRecognizers>
                                            <TapGestureRecognizer Tapped="SetMark"/>
                                        </Image.GestureRecognizers>
                                    </Image>

                                    <Image  Source="skip.png"
                                            HeightRequest="60" WidthRequest="60"
                                            HorizontalOptions="Center"
                                            VerticalOptions="Start"
                                            Grid.Column="4" Grid.Row="0">
                                        <Image.GestureRecognizers>
                                            <TapGestureRecognizer Tapped="SetMark"/>
                                        </Image.GestureRecognizers>
                                    </Image>

                                    <Image  Source="unclear.png"
                                            HeightRequest="60" WidthRequest="60"
                                            HorizontalOptions="Center"
                                            VerticalOptions="Start"
                                            Grid.Column="5" Grid.Row="0">
                                        <Image.GestureRecognizers>
                                            <TapGestureRecognizer Tapped="SetMark"/>
                                        </Image.GestureRecognizers>
                                    </Image>

                                    <Image  Source="change.png"
                                            HeightRequest="60" WidthRequest="60"
                                            HorizontalOptions="Center"
                                            VerticalOptions="Start"
                                            Grid.Column="6" Grid.Row="0">
                                        <Image.GestureRecognizers>
                                            <TapGestureRecognizer Tapped="SetMark"/>
                                        </Image.GestureRecognizers>
                                    </Image>
                                </Grid>
                            </StackLayout>
                        </StackLayout>
                    </ViewCell.View>
                </ViewCell>
            </DataTemplate>
        </local:SpecialListView.ItemTemplate>
    </local:SpecialListView>
</StackLayout>

This is an example of how it works on android and how i want it to work on ios. Listview example

Sherborn answered 1/3, 2016 at 8:53 Comment(1)
Have you tried calling ForceLayout() on your LayoutBradski
F
5

I reproduced your Problem in a small Testproject. I prefer doing layout changes via data binding, instead of code behind.

Let's start with the Template:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              x:Class="App6.Page1">
  <ListView x:Name="CategoryList"
            BackgroundColor="Gray"
            ItemsSource="{Binding Categories}"
                SelectedItem="{Binding SelectedItem}"
                HasUnevenRows="true"
                RowHeight="-1">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell x:Name="ObservationCell">
          <ViewCell.View>
            <StackLayout x:Name="Observation"
                        HorizontalOptions="FillAndExpand"
                        VerticalOptions="StartAndExpand"
                        Padding="15, 10, 10, 10"
                        BackgroundColor="White">

              <Label  x:Name="ObservationTitle"
                      Text="{Binding Title}"
                      FontSize="18"
                      TextColor="Black"
                      VerticalOptions="StartAndExpand"/>

              <StackLayout Orientation="Horizontal" IsVisible="{Binding IsSelected}">
                <Image BackgroundColor="Fuchsia" WidthRequest="40" HeightRequest="40"></Image>
                <Image BackgroundColor="Green" WidthRequest="40" HeightRequest="40"></Image>
                <Image BackgroundColor="Yellow" WidthRequest="40" HeightRequest="40"></Image>
                <Image BackgroundColor="Blue" WidthRequest="40" HeightRequest="40"></Image>
                <Image BackgroundColor="Black" WidthRequest="40" HeightRequest="40"></Image>
              </StackLayout>
            </StackLayout>
          </ViewCell.View>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

The Datatemplate is basicaly the same, but:

  • the StackLayout has no Click-Listener
  • the Visbility of the StackLayout is bound to IsSelected (IsVisible="{Binding IsSelected}")
  • the SelectedItem of the ListView is bound to SelectedItem of our ViewModel

Our Page just sets the ViewModel as DataContext

public partial class Page1 : ContentPage
{
    public Page1()
    {
        InitializeComponent();
        BindingContext = new Page1ViewModel();
    }
}

The ViewModel

  • implements INotifyPropertyChanged to notify the view about data changes
  • adds some dummy items to our Categories Collection
  • has a SelectedItem property that updates the IsSelected property of the Categories
class Page1ViewModel : INotifyPropertyChanged
{
    private Category _selectedItem;
    private ObservableCollection<Category> _categories = new ObservableCollection<Category>();
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<Category> Categories
    {
        get { return _categories; }
        set
        {
            _categories = value;
            OnPropertyChanged();
        }
    }

    public Category SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            if (_selectedItem == value)
                return;

            if (_selectedItem != null)
            {
                _selectedItem.IsSelected = false;
            }

            _selectedItem = value;
            if (_selectedItem != null)
            {
                _selectedItem.IsSelected = true;
            }
        }
    }

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public Page1ViewModel()
    {
        Categories.Add(new Category());
        Categories.Add(new Category());
        Categories.Add(new Category());
        Categories.Add(new Category());
        Categories.Add(new Category());
    }
}

Last, but not least, but most important, you need a tiny custom renderer that overwrites the default one. We call ReloadData() if the SelectedItem has changed.

[assembly: ExportRenderer(typeof(ListView), typeof(MyListViewRenderer))]
namespace App6.iOS.CustomRenderer
{
    public class MyListViewRenderer : ListViewRenderer
    {
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == ListView.SelectedItemProperty.PropertyName)
            {
                Device.BeginInvokeOnMainThread(() => Control.ReloadData());
            }
        }
    }
}

Result

List View with dynamic cells

Felicidadfelicie answered 9/3, 2016 at 19:19 Comment(3)
Can this be done with just reloading the row you clicked?Sherborn
Maybe. But ReloadData is a the usual method in iOS to signal data changes to the UITableView. That's why I have chosen this approach. So I assume that it is optimized somehow, if performance is your concern.Ascariasis
Hello, I had the same problem. I have some extra behavior : if I click again on a row, I want to deactivate it (in viewModel if (SelectedItem == value) { SelectedItem.IsSelected = false; // will collapse the submenu SelectedItem = null; // doesn't trigger the SelectedItem property and thus, the custom renderer doesn't call ReloadData }Damsel

© 2022 - 2024 — McMap. All rights reserved.