2

thank you for helping me with the following issue.

The Overall Goal

I work on a SwiftUI view that contains a scrollable area to present some data. Assume it should display natural numbers within a certain bound, for example, 0 to 2,000,000. Each number gets displayed within a custom number-view that spans half of the screen. The scrollable area contains the number-views in ascending order.

The constraints are the following:

  1. The number-views are loaded lazily.
  2. We can define what number-view is shown first when loading the scrollable area.
  3. At some point, the scrollable view has to forget views that are out of range to relieve the memory. The first two constraints impose: We can load the view and show the 1,000,000th number-view without loading the previous 999,999 number views.

1st Constraint

Fulfilling the first constraint is straightforward. We create a ScrollView that nests a LazyVStack, a ForEach to make the data available and the number-view. If we display our view, the print statement shows only the first few views get loaded.

var body: some View {
    ScrollView(.vertical, showsIndicators: false) {
        LazyVStack {
            ForEach(0..<2000000) { number in
                NumberView(number)
            }
        }
    }
}
struct NumberView: View {
    
    let number: Int
    
    init(_ number: Int) {
        self.number = number
        
        print("init: \(number)")
    }
    
    var body: some View {
        print("\(number)")
        
        return Text("\(number)")
            .frame(height: 300)
            .border(.red)
    }
}

2nd Constraint

We modify our previous code. Therefore we embed the ScrollView into a ScrollViewReader and tag the number-view with IDs. Now we can scroll to the required number-view once the ScrollView appears.

var body: some View {
    ScrollViewReader { proxy in
        ScrollView(.vertical, showsIndicators: false) {
            LazyVStack {
                ForEach(0..<2000000) { number in
                    NumberView(number)
                        .id(number)
                }
            }
        }
        .onAppear {
            proxy.scrollTo(1000000, anchor: .top)
        }
    }
}

But now we violate our first constraint. When scrolling to the 1000000th number-view, we have to initialize the 999,999 previous ones. This has two disadvantages: It requires lots of time and memory. Ultimately it results in a memory allocation failure. Maybe, deallocation of the number-views is an option. But then we still have to initialize them to discard them later, which takes its time.

3rd Constraint

I'm not 100% sure if or under which conditions SwiftUI deallocates the number-view-structs if they are out of reach. I assume they could be removed from the memory once their onDisappear method gets called.

Alternative Solution

I read an article about an infinite scrolling list. The article does not tackle the exact same problem. But it proposes to modify the list over which ForEach iterates, depending on what number-views are shown. We can keep track of the displayed number-views with onAppear and onDisappear. We can use both functions to update the loaded numbers within the list.

struct InfiniteList: View {
    
    @StateObject var state: Data = Data()
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(state.list, id: \.self) { number in
                    NumberView(number)
                        .onAppear {
                            // load more data
                        }
                        .onDisappear {
                            // unload data
                        }
                }
            }
        }
    }
}

class Data: ObservableObject {
    
    // list that holds the nearby numbers
    @Published var list: [Int] = [1000000]
    
}

But as soon as we update the list, the ScrollView displays number-view of the lowest number, triggers its onAppear function, and therefore loads even smaller numbers.

I assume we could make the last approach work:

  1. disabling updating the number list
  2. scroll to the actual view
  3. enable updating it again

But this would be complicated, and we have to scroll the exact position that the user has seen before the update.

0 Answers0