maui
maui copied to clipboard
[iOS] ListView with HasUnevenRows="true" doesn't resize dynamically
Description
On iOS, when using a ListView with HasUnevenRows="true", the ViewCell's size doesn't change dynamically if the enclosed content resizes.
I'm using a simple ContentView inside the ViewCell to resize the items.
Android - accordion in ViewCell open:
Android - accordion in ViewCell closed:
iOS - accordion in ViewCell open:
iOS - accordion in ViewCell closed - ViewCell takes up the same space as before:
Apologies for the rather complicated example code.
(I've liberated some code for the sample from https://github.com/dotnet/maui/issues/7217 to reproduce the issue)
Steps to Reproduce
- Create a new .Net MAUI Solution called "iOSContentViewBug"
- Replace content of MainPage.xaml with
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:c="clr-namespace:iOSContentViewBug"
x:Class="iOSContentViewBug.MainPage">
<ListView ItemsSource="{Binding AccordionValues}" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<c:AccordionView AccordionContent="blablabla">
</c:AccordionView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
- Replace content of MainPage.xaml.cs with this:
namespace iOSContentViewBug;
public partial class MainPage : ContentPage
{
private List<string> _accordionValues = new List<string>
{
"Content",
"Content",
"Content",
"Content",
"Content",
"Content",
"Content",
"Content",
"Content",
"Content",
"Content"
};
public MainPage()
{
InitializeComponent();
BindingContext = this;
}
public List<string> AccordionValues
{
get { return _accordionValues; }
set { _accordionValues = value; OnPropertyChanged(nameof(AccordionValues)); }
}
}
- Create a new ContentView "AccordionView.xaml"
- Replace AccordionView.xaml with this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="iOSContentViewBug.AccordionView"
x:Name="_accordionView">
<StackLayout>
<Frame Margin="10" BackgroundColor="Red">
<Grid RowDefinitions="Auto,Auto" VerticalOptions="Center">
<Frame Grid.Row="0">
<Grid ColumnDefinitions="*,Auto">
<Label Grid.Column="0" Text="Header" />
<Label
x:Name="label"
Grid.Column="1"
HorizontalOptions="End"
Text="Hide">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</Label.GestureRecognizers>
</Label>
</Grid>
</Frame>
<Frame Grid.Row="1" x:Name="frame">
<Label Text="{Binding Source={x:Reference _accordionView}, Path=AccordionContent}"/>
</Frame>
<!--<ContentView x:Name="_accContent" Grid.Row="1"
Content="{Binding Source={x:Reference _accordionView}, Path=AccordionContentView}">
</ContentView>-->
</Grid>
</Frame>
</StackLayout>
</ContentView>
- Replace AccordionView.xaml.cs with this:
namespace iOSContentViewBug;
public partial class AccordionView : ContentView
{
public AccordionView()
{
InitializeComponent();
}
public static readonly BindableProperty AccordionContentProperty = BindableProperty.Create(nameof(AccordionContent), typeof(string), typeof(AccordionView), default(string));
public string AccordionContent
{
get => (string)GetValue(AccordionContentProperty);
set => SetValue(AccordionContentProperty, value);
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
frame.IsVisible = !frame.IsVisible;
label.Text = frame.IsVisible ? "Show" : "Hide";
}
}
- Run on iOS - the ViewCell should keep the same size, regardless of state of the Content View (Show/Hide)
Link to public reproduction project repository
https://github.com/stefanpirkl/bugreport-maui-ios-listview-uneven
Version with bug
7.0 (current)
Last version that worked well
Unknown/Other
Affected platforms
iOS
Affected platform versions
iOS 16.2
Did you find any workaround?
no
Relevant log output
No response
We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.
Does the same thing happen if you use a CollectionVIew?
Hi, thanks for the quick response!
CollectionView behaves exactly the same, I'm afraid.
Does the same thing happen if you use a CollectionVIew?
CollectionView has the same problem on iOS, it works well on Android.
Also happens to me. Have you found any workaround?
No. AFAIK, there is no workaround.
Verified this on Visual Studio Enterprise 17.7.0 Preview 2.0. Repro on iOS 16.4, not repro on Android 13.0-API33 with below Project: iOSContentViewBug.zip
Any news on this issue?
After digging a bit, these seem to be the same issues: #8239 #7967 Still no workaround. This is very frustrating.
We've finally found a workaround!
Instead of ListView/CollectionView, use a (Vertical/Horizontal)StackLayout with BindableLayout: ` <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:c="clr-namespace:iOSContentViewBug" x:Class="iOSContentViewBug.MainPage">
<VerticalStackLayout BindableLayout.ItemsSource="{Binding AccordionValues}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<c:AccordionView AccordionContent="blablabla">
</c:AccordionView>
</DataTemplate>
</BindableLayout.ItemTemplate>
</VerticalStackLayout>
`
If you need scrolling, wrap the StackLayout in an additional ScrollView.
We've finally found a workaround!
Instead of ListView/CollectionView, use a (Vertical/Horizontal)StackLayout with BindableLayout: `
<VerticalStackLayout BindableLayout.ItemsSource="{Binding AccordionValues}"> <BindableLayout.ItemTemplate> <DataTemplate> <c:AccordionView AccordionContent="blablabla"> </c:AccordionView> </DataTemplate> </BindableLayout.ItemTemplate> </VerticalStackLayout>
` If you need scrolling, wrap the StackLayout in an additional ScrollView.
Although this does work, bindable layouts do not have virtualization like listview and collectionview do, so performance can suffer as itemssource count increases. Could really do with a proper fix for this!
Although this does work, bindable layouts do not have virtualization like listview and collectionview do, so performance can suffer as itemssource count increases. Could really do with a proper for this!
Sorry :( I know it's not a proper fix, just thought I'd let people know there's a workaround, since we've really struggled with this issue. Maybe the workaround was too obvious and our dev team is just stupid? Should I delete it?
Although this does work, bindable layouts do not have virtualization like listview and collectionview do, so performance can suffer as itemssource count increases. Could really do with a proper for this!
Sorry :( I know it's not a proper fix, just thought I'd let people know there's a workaround, since we've really struggled with this issue. Maybe the workaround was too obvious and our dev team is just stupid? Should I delete it?
No, it's not stupid! This will be a perfectly fine solution for some use cases, so it's useful to let people know about it! Was just dropping my two cents/leaving a reply on the thread so I can track it.
Is there any solution until now? This workaround is useful in simple scenarios, but using BindableLayout can be very costly, Especially when using TemplateSelectors, loading page can get very slow.
is there any other solution for binding items more fastly using BindableLayout?
I guess it's still not fixed, and there is still no workaround?!
The workaround in this case is to call ForceUpdateSize()
on the ViewCell
after you make a change you expect to cause the ListView item size to require an update.
So, in the repro, I added an event to AccordionView
: public event EventHandler AccordionVisibilityChanged;
and in the Tap gesture callback where the frame visibility is changed, I invoke that event.
On the MainPage in the repro, I have registered to the event and have this in the MainPage.xaml.cs code behind:
private void AccordionView_OnAccordionVisibilityChanged(object sender, EventArgs e)
{
if (sender is AccordionView accordionView && accordionView.Parent is ViewCell viewCell)
{
viewCell.ForceUpdateSize();
}
}
https://github.com/dotnet/maui/assets/271950/c0df0f66-ea60-4924-b3ac-aaa2cf76aed2
This problem seems to be fixed if you set VerticalOptions to Start on the ListView
I had a similar issue in a ListView and came up with the following workaround which is placed in the ViewModel and called when an item in the ListView gets updated (in my instance this was called via a messenger passing updatedObject
).
// get index of object in list
int index = YourItems.IndexOf(updatedObject);
// assign updated object to item in list
YourItems[index] = updatedObject;
// hack to re-highlight the row
YourSelectedItem = null;
YourSelectedItem = YourItems[index];
As long as YourSelectedItem
is set to the SelectedItem in the ListView and YourItems
is set to the ItemsSource, this should work fine.
Hope it helps.