I have a MAUI application with a view binded to a viewmodel containing a grouped CollectionView
with these properties set:
<CollectionView
x:Name="MyList"
VerticalOptions="FillAndExpand"
ItemsSource="{Binding CollectionViewItemSourceGrouped}"
SelectionMode="Single"
SelectedItem="{Binding MyItem, Mode=TwoWay}"
SelectionChangedCommand="{Binding MySelectionCommand}"
RemainingItemsThreshold="2"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreItemsCommand}"
IsGrouped="True"
EmptyView="No item found.">
I get and group the source items from an awaitable Web api and I correctly get the collection grouped when I first navigate into the view and if I start toggling back and forth the switch filtering the list. Unfortunately, when I navigate into a detail page and back to the collection through Shell navigation, if I act on the switch again and I reload the list I always get a number of empty groups and it graphically results into a big mess.
The object used as a source for the collection is CollectionViewItemSourceGrouped
, it is an ObservableCollection<ItemsGroup>
which has this implementation:
public class ItemsGroup : ObservableRangeCollection<ItemModel>
{
public DateTime Date { get; private set; }
public ItemsGroup(DateTime date, ObservableRangeCollection<ItemModel> list) : base(list)
{
Date = date;
}
}
Here's also how the data are loaded:
try
{
if (InLoading)
return;
InLoading = true;
//Selectio item gets blanked
if (MyItem is not null)
MyItem = null;
CollectionViewItemSourceGrouped = new();
paginationIndex = 1;
IEnumerable<ItemsGroup> grpItems = await LoadItemsSub(0, MaxItemsPerPage);
if (grpItems != null && grpItems.Any())
CollectionViewItemSourceGrouped = new ObservableRangeCollection<ItemsGroup>(grpItems.OrderByDescending(s => s.Date));
else
CollectionViewItemSourceGrouped = new();
}
catch (LocalException ex)
{
UIMessagesManager.ShowMessage(Stringhe.DANGER, ex.Message);
}
catch (Exception ex)
{
AppCenterEventManager.TrackError(ex, string.Empty);
UIMessagesManager.ShowMessage(Stringhe.DANGER, Stringhe.UNKNOWN_ERROR);
}
finally
{
InLoading = false;
}
Every time I reload data, I make sure the object CollectionViewItemSourceGrouped
is set to null
or is a new empty object. I would expect the collection to be empty, rather this keeps on showing empty groups with only the header set. How can I get rid of them and let the collection properly refresh?
This only happens on iOS and MacCatalyst. It nicely works on Android and WinUI.
In the image above, the header 01/05/2023 is unexpected and appears as an empty group. But the item source has already been reassigned properly in the view model. Here's what I expect:
I tried literally everything but with no luck on iOS and MacCatalyst.
UPDATE 1
Here's a GitHub public repository with a sample project. Try to execute it on iOS Simulator and you will be able to see this strange behavior when VerticalOptions=FillAndExpand
for the CollectionView
. [See picture]
GitHub Repository - GroupedCollectionSample
UPDATE 2
Notice that if I set VerticalOptions
for the container (StackLayout
) I keep on getting the same "empty" group headers odd behavior. I currently "solved" leaving the StackLayout
and setting a fixed HeightRequest
to the CollectionView
, but of course it doesn't fit to all devices.
I also tried to set it into a Grid
as suggested and here's the code. This doesn't fix anything because the collection keeps on expanding out of the screen and so the scroll to the bottom becomes impossible (I think due to the fact that the container doesn't have a fixed end). I dunno what to do since: changing the container VerticalOptions
causes the issue, leaving it at default causes the CollectionView
not to scroll to the bottom.
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Heigth="40" />
<RowDefinition Heigth="*" />
</Grid.RowDefinitions>
<StackLayout
Grid.Row = "0"
HeightRequest="40"
Orientation="Horizontal">
<Switch
VerticalOptions="Center"
Margin="10,0"
IsToggled="{Binding FilterData}">
<Switch.Behaviors>
<toolkit:EventToCommandBehavior
EventName="Toggled"
Command="{Binding FilterDataCommand}" />
</Switch.Behaviors>
</Switch>
<Label
Text="Only enabled users"/>
</StackLayout>
<CollectionView
x:Name="ListUsers"
Grid.Row = "1"
VerticalOptions="FillAndExpand"
ItemsSource="{Binding ListUsersGroup}"
SelectionMode="Single"
SelectedItem="{Binding CurrentUser, Mode=TwoWay}"
SelectionChangedCommand="{Binding OpenDetailCommand}"
IsGrouped="True"
EmptyView="No user found.">
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="extModels:UsersGroup">
<Label
Padding="10,8"
BackgroundColor="Gray"
TextColor="Black"
Text="{Binding Date}"
FontAttributes="Bold"
FontSize="15"/>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="extModels:User">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Label
Grid.Column="0"
Margin="20,0"
Text="{Binding FirstName}">
</Label>
<Label
Grid.Column="1"
Text="{Binding LastName}" >
</Label>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</StackLayout>
I did not have time to review everything, but this here:
<StackLayout>
<StackLayout
HeightRequest="40"
Orientation="Horizontal">
<Switch
VerticalOptions="Center"
Margin="10,0"
IsToggled="{Binding FilterData}">
<Switch.Behaviors>
<toolkit:EventToCommandBehavior
EventName="Toggled"
Command="{Binding FilterDataCommand}" />
</Switch.Behaviors>
</Switch>
<Label
Text="Only enabled users"/>
</StackLayout>
<CollectionView
x:Name="ListUsers"
VerticalOptions="FillAndExpand"
ItemsSource="{Binding ListUsersGroup}"
SelectionMode="Single"
SelectedItem="{Binding CurrentUser, Mode=TwoWay}"
SelectionChangedCommand="{Binding OpenDetailCommand}"
IsGrouped="True"
EmptyView="No user found.">
This is more than enough... What are you "filling"? How are you filling something, that has no bottom?
Also, you see now, how "more code provided" makes much more sense. In your original question, because I did not see the parent, I could not notice this.
Edit: Limiting the height of the item in the container, is one way. Now, usually you will want to limit the container itself.
In your scenario, A Grid
, with RowDefinitions= "40,*"
will give you the desired result. (first row 40, second row, as much as it takes). This way, you can place your StackLayout
in Row = 0
, and CollectionView
in Row = 1
, and let them take all the space.
(So this way your design will be working on different phone screens.)
EDIT2:
<StackLayout> <<< This here has no height restriction. Delete this.
<Grid> <<< This will match your view. Keep it.
<Grid.RowDefinitions>
<RowDefinition Heigth="40" />
<RowDefinition Heigth="*" /> <<< this here reads "fill"
</Grid.RowDefinitions>
<StackLayout
Grid.Row = "0"
HeightRequest="40"
Orientation="Horizontal">
<Switch
VerticalOptions="Center"
Margin="10,0"
IsToggled="{Binding FilterData}">
<Switch.Behaviors>
<toolkit:EventToCommandBehavior
EventName="Toggled"
Command="{Binding FilterDataCommand}" />
</Switch.Behaviors>
</Switch>
<Label
Text="Only enabled users"/>
</StackLayout>
<CollectionView