Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ScrollView: How to handle large amount of content?

Tags:

swiftui

This piece of code is kind of stress test for a ScrollView. While a ForEach loop with 3000 items is rendering acceptably fast, 30000 takes long, and 300000 forever (I stopped after 3 minutes).

However, there are situations where you have a lot of content to scroll, imagine a timeline with zoomable scales (decade / year / month / day). In this case, there may be days across 50 years to display, and the user must have the chance to zoom in and out in order to change the scale.

The question for me is therefore: what strategies are possible with SwiftUI in order to optimize caching, prefetching etc., or to find out which part is to be displayed next after the user scrolled, and to prevent the model from calculating widths?

To be more precise: The model knows all data to display. The ScrollView must be provided with a content view that has the width of all items to display. This width is my problem: how can it be determined in a way that makes sense, and without having all items to be available?

struct TestScrollView: View {

    var body: some View {

        ScrollView(.horizontal, showsIndicators: true, content: {

            HStack(alignment: .bottom, spacing: 1) {

// 3000 is working, 30000 not well, 300000 takes forever:
                ForEach(1 ..< 30000) { index in

                    Rectangle()
                        .fill(Color.yellow)
                        .frame(minWidth: 100, idealWidth: 200, maxWidth: 300, minHeight: 500, idealHeight: 500, maxHeight: 500, alignment: .bottom)
                }
            }
        })
    }
}
``
like image 435
Hardy Avatar asked Aug 04 '19 10:08

Hardy


2 Answers

There was a trick old times for having horizontal UITableView that is using now for reversal filling (bottom to top) that you can use it here:

  • Use a vertical list
  • Rotate it 90˚
  • Rotate all items of it -90˚

now you have horizontal List

struct ContentView: View {

    var body: some View {
        List {
                ForEach(1 ..< 30000) { index in
                    Text("\(index)")
                    .rotationEffect(.init(degrees: -90))
                }
        }
        .rotationEffect(.init(degrees: 90))
        .position(x: 10, y: 200) // Set to correct offset
    }
}
like image 62
Mojtaba Hosseini Avatar answered Nov 10 '22 10:11

Mojtaba Hosseini


Why are you using a Scrollview instead of a List? A List would reuse the cells which is much better for performance and memory management. With the list when a cell scrolls out of sight, it gets removed from the view which helps greatly with performance.

For more information about the difference in performance please see here

    var body: some View
    {
        List
        {
            ForEach(1..< 30000) {
                index in

                Rectangle()
                    .fill(Color.yellow)
                    .frame(minWidth: 100, idealWidth: 200, maxWidth: 300, minHeight: 500, idealHeight: 500, maxHeight: 500, alignment: .bottom)
            }
        }
    }

Edit: Since I miss-read the original question because the scrollview is horizontal.

An option would be to wrap an UICollectionView into UIViewRepresentable see here for the dev talk and here for an example implementation this would let you display it as a horizontal view while getting the performance benefits of a list

like image 26
yawnobleix Avatar answered Nov 10 '22 11:11

yawnobleix