dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.02k stars 1.73k forks source link

CollectionView with many items (10,000+) hangs or crashes on iOS #18891

Open activa opened 10 months ago

activa commented 10 months ago

Description

A simple CollectionView with a lot of items (100,000 or so) will hang on Windows and iOS. Memory consumption goes through the roof (gigabytes of allocations) and will eventually even crash on iOS. On Android this seems to work without any issues.

It's extremely easy to reproduce. Simply create a blank application and add this CollectionView to the page:

<CollectionView x:Name="listview"/>

Bind 100,000 strings to the collection view and watch it hang on startup. Running in a debugger shows the memory usage going up to gigabytes.

Steps to Reproduce

  1. Create a new MAUI app
  2. Add a CollectionView to the main page with or without an ItemTemplate
  3. Bind 100,000 strings to the CollectionView
  4. Run

Link to public reproduction project repository

https://github.com/activa/Maui8CollectionViewBug

Version with bug

8.0.3

Is this a regression from previous behavior?

Yes, this used to work in Xamarin.Forms

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

No response

cropyai commented 10 months ago

@activa hi, how is the performance on Android? Could you elaborate on that? I am having an issue with CollectionView here: https://github.com/dotnet/maui/issues/18877 which should be a simple case. If you can try my demo project or give some ideas about it, I would really appreciate.

jsuarezruiz commented 10 months ago

@jonathanpeppers Thoughts?

Vroomer commented 10 months ago

This sounds like a duplicate: https://github.com/dotnet/maui/issues/18639

drasticactions commented 10 months ago

Looking at the sample, I agree. And since that issue already has a PR on it, we can close this.

Duplicate of https://github.com/dotnet/maui/issues/18639

drasticactions commented 10 months ago

Actually, on second thought, since this issue mentions iOS, there could be a separate issue and since the other issue only covers Windows, this can be open for the iOS side.

But I think the root cause for Windows is the same as the other bug.

activa commented 10 months ago

This is indeed very similar to #18639 but it's definitely not platform-specific (although it does seem to work as expected on Android). The root cause may be in shared code.

activa commented 10 months ago

@PureWeen Why did you remove Windows? It is an issue on iOS AND Windows (not Android)

drasticactions commented 10 months ago

@activa Because it was addressed with https://github.com/dotnet/maui/pull/18813, and linked with another issue. So this issue is for looking at the iOS side.

activa commented 10 months ago

@PureWeen @jsuarezruiz @davidortinau

Can this issue please be prioritized, together with #18639? There is no workaround possible and it's a true showstopper. Any app that tries to show more than a few thousand items in a CollectionView is simply unusable or will crash.

I can understand that cosmetic issues are being deferred to the backlog but anything as fundamental as this should get the attention it deserves.

I'm getting very frustrated by the lack of sense of urgency to fix major bugs in MAUI, and I'm definitely not the only one. The first "stable" version was released 18 months ago and it has never been stable enough to be used in a real world application. The number of regressions being introduced with every release is also quite alarming.

ghost commented 9 months ago

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.

mattleibow commented 9 months ago

Both should be in SR 1 preview soon. You can test and confirm if your issues are fixed in the nightlies: https://github.com/dotnet/maui/wiki/Nightly-Builds

ghost commented 9 months ago

Hi @activa. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

mattleibow commented 9 months ago

Added needs-info so you can verify if our fixed have fixed it for you.

activa commented 9 months ago

Tried with 8.0.4-nightly.9679 and there's no change. It still crashes with the test project I linked to this issue.

activa commented 9 months ago

I figured out what's going on, but I don't think it's going to be an easy fix

In ItemsViewController.GetSize(), the content size of the collection view is calculated by calling CollectionView.CollectionViewLayout.CollectionViewContentSize. What that does is call GetSizeForItem() on every single item (this is done by UIKit, not MAUI code). When there are a lot of items, this will take quite a bit of time (on my iPhone test device, this takes 30 seconds for 200,000 items). To make matters worse, the content size is AGAIN calculated in ItemsViewController.InvalidateMeasureIfContentSizeChanged() , which again takes 30 seconds.

The thing is, there's really no good reason to want to calculate the content size of a collection view. I'm trying to understand why this is needed.

activa commented 9 months ago

After some additional investigation, this is what I found:

The bad guy here is CollectionViewContentSize (property of UICollectionViewLayout). Whenever that property is accessed, UIKit will iterate over all items in the collection and call GetSizeForItem() for every single item. If you have more than a few hundred items, this will kill the app. The problem is that this property is accessed in many places, and the only reason why it's used is in case you want the CollectionView to size with the contents, which is very uncommon. In the vast majority of cases, the CollectionView control will have layout constraints and doesn't care one bit about the size of its contents (For example, when both HorizontalOptions and VerticalOptions are set to Fill)

I made some changes in the iOS code to skip reading CollectionViewContentSize when the CollectionView has a size constraint. This fixes the problem completely but there's a high risk of introducing regressions.

If I can find a solution that doesn't break existing code, I may create a PR.

To be continued...

mattleibow commented 9 months ago

CollectionView should be virtualized so unless your collection view is thousands of pixels high, there should only be like 20 items.

Can you try setting an explicit height of say 200 and see what happens? It should not iterate over more than the items visible.

activa commented 9 months ago

@mattleibow I think you misunderstood what I'm trying to say.

CollectionView is indeed virtualized but when you request the content size from UIKit, like MAUI does, it will iterate over all items, not just the visible ones, because that's the only way to calculate the content size :

https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617796-collectionviewcontentsize

Subclasses must override this property and use it to return the width and height of the collection view’s content. These values represent the width and height of all the content, not just the content that is currently visible. The collection view uses this information to configure its own content size for scrolling purposes.

MAUI should not be asking for the content size because it's not necessary. The reason it's doing it is to resize the collectionview based on its contents when HorizontalOptions and/or VerticalOptions are set to Start/Center/End. But MAUI requests this content size multiple times, no matter what the layout options of the CollectionView.

activa commented 9 months ago

So is this going to be prioritized? This is a blocker with no possible workaround. It's currently impossible to create an iOS app with a CollectionView that has more than a few hundred items to show.

Even the following extremely simple example will either hang or crash an app on iOS:

public class MainPage : ContentPage
{
    public MainPage()
    {
        Content = new CollectionView
        {
            WidthRequest = 200, 
            HeightRequest = 200,
            ItemsSource = Enumerable.Range(1,100_000).Select(i => i.ToString()).ToList()
        };
    }
}

The reason it crashes is because the CollectionViewContentSize property is used in the iOS code, which will iterate over ALL items to calculate the size of the content of the CollectionView (as explained in my earlier comments)

activa commented 9 months ago

So is this going to be stuck in the backlog for years now? This issue is preventing us from migrating to MAUI because there is no workaround for it. None. There's currently no way to show a few thousand items in a list in a MAUI app on iOS without it freezing the app. ListView would be an alternative if it weren't for the infamous data template BindingContext bug that has seemingly been pushed to 8.0 SR3.

@davidortinau Referring to our email exchange last week. See what I mean? MAUI is now officially a dead end for us, because of issues like these that are being ignored. This should not be happening in a product that has been "stable" for 18 months.

Redth commented 9 months ago

@activa thanks for doing some investigation here. I do understand what you're pointing out here and the problem with it. We had some other fixes go in that should help situations where list sources are not as huge that were unnecessarily slow given the context, however I believe I've experienced the behaviour you're seeing while doing some investigation in this area awhile back.

We'll take another look at this one with the new context here.

Redth commented 9 months ago

Digging a bit more, I suspect something changed with iOS in recent versions. I applied the sample posted here to a new Xamarin.Forms app and it's exhibiting the same behaviour (MAUI version is a bit slower in debug since the default there is running with the interpreter on).

Redth commented 9 months ago

Also dropping a note for myself or others looking into this... One thing I've noticed is that in my experiments in another area using UICollectionView + MAUI, I was setting: UICollectionView.Layout.EstimatedItemSize = UICollectionViewFlowLayout.AutomaticSize; and UICollectionView.Layout.ItemSize = UICollectionViewFlowLayout.AutomaticSize;. This might be something to test against the MAUI code to see if it helps at all. We seem to set the EstimatedItemSize for the Shell usage of CollectionView, but I don't see it being set anywhere in the handler for CollectionView or in Xamarin.Forms' renderer.

activa commented 9 months ago

I tried a lot of things but no matter what I tried, GetSizeForItem() is called for every item in the items source. The marshalling between iOS (UIKit) code and .NET code has quite a bit of overhead, so even if GetSizeForItem() is a no-op, it will still take almost a minute for 200,000 items.

mikebikerider commented 8 months ago

Xamarin.Forms CollectionView and ListView do not have that issue. In Xamarin.Forms I am loading up to 25000 items that are multi-column grids. Scroll is fast and smooth. The CollectionView/ListView size in a star row is calculated correctly. In Xamarin I place vertical scroll CollectionView or ListView insde a Horizontal Scroll ScrollView and it works like a charm. In MAUI same app can't handle 25000 items. Scroll is jerky with 500 items. To make it work in Horizontal ScrollView I have to correct CollectionView size in OnSizeAllocated() when screen is rotated. There is no need for such in Xamarin. Maybe somebody takes a look why Xamarin.Forms CollectionView performs so amazingly well on iOS and Android. The underlying platform controls are the same. There is no good expalanation why MAUI CollectionView performs so poorly.

activa commented 8 months ago

So what's the status on this? This crippling bug was reported 2 months ago and it's still not even scheduled to be fixed in an upcoming service release. Our MAUI conversion has been put on hold indefinitely because there is NO WORKAROUND for this bug. The ability for an app to show a list of several thousand items is core functionality and a very common use case.

I can only conclude that MAUI is no longer a product that Microsoft is invested in. There's simply no other explanation for this.

pulmuone commented 8 months ago

same issue very very slow

jacobvalter commented 7 months ago

Any update here? Having same issue with MacCatalyst and 1500 items in the CollectionView...

@activa would it be possible for you to share the iOS code to skip reading CollectionViewContentSize when the CollectionView has a size constraint? This would be great!

activa commented 7 months ago

10 weeks and counting...

mikebikerider commented 7 months ago

In my experience poor performance of the .NET MAUI CollectionView is not with the control itself. CollectionView and ListView are very sensitive to sizing being correct. If it is not, then scrolling becomes jerky and sluggish. I was able to drastically improve CollectionView performance by calculating height and width the CollectionView should occupy and adding code that forces calculated size. That goes against everything I ever learned about flexible layouts, but it works for now. The number of rows that can be comfortably scrolled went up from 200 to 150000. I discovered that in my content layouts iOS safe area insets were applied incorrectly. Android does not have safe area, but a vertical scroll CollectionView paced inside a horizontal SrcollView tries to occupy more vertical space than available. I added code in SizeAllocated issuing HeightRequest for the CollectionView. The 'CollectionView' fixes I came up with are indicative of issues elsewhere. MAUI seems to be loose with handling star rows and columns. They are often treated as auto. That screws up sizing and it is worse under.NET8 compared to .NET7. Xamarin.Forms renderers don't have those issues, but Xamarin.Forms will be cut off by May 1, 2024.

activa commented 7 months ago

@mikebikerider See the comments on this issue. It's a design flaw in the MAUI CollectionView implementation on iOS. A virtualized list view is (by definition) designed to handle thousands of items, just like it does without any issues at all on Xamarin (both CollectionView and ListView). The MAUI implementation is simply broken.

mikebikerider commented 7 months ago

@acivia, there is a similar discussion at https://github.com/dotnet/maui/issues/18505#issuecomment-1829239611 . There I posted a GitHub link https://github.com/mikebikerider/CollectionViewSample-net8/tree/master to my CollectionView sample app. I had no firm opinion whether MAUI CollectionView issues are rooted entirely, partially, or at all, in the internal CollectionView code. So, I wrote a sample app to see what I can do to make the poor working, not expecting much. To my amazement I was able to make it work with some unconventional gimmicks. Different for iOS and Android, but of the same nature. It is not a workaround. More of an attempt to point somebody more qualified where to look. The sample has two flyout pages. One page has a CollectionView in a star row of the main grid. The other one has same CollectionView in a grid that is inside a horizontal scroll ScrollView to enable scrolling in both directions. The CollectionView() content (DataTemplate) is as a 3-column grid for the first page, and 4-column grid for the second page. That second page performance was horrible, especially on Android, especially under .NET8. I also discovered issues other than poor scrolling. Now it is satisfactory. I don't like how I did it, but it works. The initial number of rows loaded is low - 100. It can be increased up to 999999. I did not try going over 150000. Loading large number or rows takes more time, but scrolling is fast. Prior to adding odd code in OnSizeAllocated() it was a pain loading more than 200 rows on Android. I could load 2000 on iOS but scrolling was awful.

There are some goodies in the sample. The CollectionView header stays on top when the content is scrolled up or down.

kyurkchyan commented 7 months ago

I have the same issue. But it's about more than just the large number of rows. It's more about terrible performance altogether. However, the large number of rows terribly exacerbates the problem. After futile attempts to fix the problem, we've migrated most of our collection views to DevExpress CollectionView. This solved our applications' performance problem, even in the most complex layouts. Take a look at the comparison below:

MAUI Collection View DevExpress Collection View
maui_collection_view devexpress_collection_view

In the MAUI collection view GIF it's the scroll that hangs terribly, and the whole view becomes unresponsive to the scroll gestures. You can see how dramatic the performance difference is. We observe the same picture both on iOS and on Android. I wish that the native collection view could have as good of a performance as the DevExpress guys could achieve.

Unfortunately, DevExpress collection view has some limitations in terms of features. For instance, it has yet to support headers/footers. But I have heard their product team promise that they will add that feature pretty soon. I hope the situation will change and MAUI's collection view will become usable in terms of performance.

Manish-Pradhan-FP commented 4 months ago

Any updates on this? This is creating big issues for us.

ThinhKVT commented 4 months ago

We encountered the same issue in MAUI Collection View with terrible performance and crash app. Anyone has new idea for the problem ?

mikebikerider commented 4 months ago

I had same issues. Code that produced smooth fast CollectionView scrolling under Xamarin iOS and Android become sluggish and jerky when ported to MAUI. I found that MAUI does not correctly estimate the space required for CollectionView, especially when placed in a star row. MAI handling of star rows is separate issue, but it affects CollectionVIew more than other controls. The workaround is to calculate the required space and adjust the MaximumHeightRequest (mvvm binding) in OnSizeAllocated. On iOS it requires taking insets into account. In MAUI iOS UseSafeArea is default, but for CollectionView it does not work properly. Another difference compared to Xamarin is that CollectionView must be populated with data after the page is loaded. In Xamarin I would populate the CollectionView before the page is shown so the user doesn't have to stare at empty space when the page is shown. Can't do that in MAUI. Some techniques for improving CollectionView performance that worked well in Xamarin may have opposite effect in MAUI. I use grid in CollectionView template. In Xamarin I would calculate the height of the row and bind Grid.Row.Height to the calculated height. I would make all rows equal for better performance, if necessary. I found that in MAUI setting template Grid.Row.Height to 'auto' works better (faster) than binding. Good luck.

activa commented 2 months ago

This blocker bug with no available workaround has been open for 8 months with seemingly zero progress towards a solution.

Also, what is the "delighter-sc" label?