3

I use TabView to browse albums because I need a paging function, but my album has thousands of pictures, and TabView is very slow to process. Is there any way to lazy loading so that TabView can handle thousands of data.

TabView(selection: $assetModel.identifier) {
    ForEach(0..<7000) { index in
        let asset = assetModel.assetArray[index]
        AssetPreviewItemView(asset: asset)
            .tag(asset.localIdentifier) 
    }
}
.tabViewStyle(.page(indexDisplayMode: .always))
.indexViewStyle(.page(backgroundDisplayMode: .always))
gaohomway
  • 2,132
  • 1
  • 20
  • 37
  • It's likely not the TabView that is slow. The TabView just renders a set of Views and does not handle any of your data aspects, like fetching images, caching, fetch-ahead, etc. Put this logic into a Model or View Model - then show what you have. – CouchDeveloper Jul 31 '21 at 10:49
  • @CouchDeveloper Thank you for your reply, I can be sure that TabView is slow. If you change the picture to Color, it will freeze and slow down. Because there are too many, I have 7,000 photos. I think the best way is to load lazily, and always maintain three photos, which is the best way. – gaohomway Jul 31 '21 at 11:07
  • Have you discovered the "LazyView" trick already: https://forums.swift.org/t/lazy-loading-of-large-number-of-images-in-a-zstack/43886 ? You still have to count for that the init will be called for all views, though. Otherwise, you may consider to use good old UIKit PageViewController. – CouchDeveloper Jul 31 '21 at 12:01
  • When you think that something making your app slow, try removing it to see if you're correct. If you try displaying your collection without TabView you'll see it's not the problem. Also without your code we can't help you. – Phil Dukhov Jul 31 '21 at 12:07
  • @Philip Thank you for your reply. I updated the code. It is a view to browse photos by sliding left and right pages. Slow due to too much data. – gaohomway Jul 31 '21 at 13:40
  • @CouchDeveloper This method is very new, but I tried it and it still doesn’t work. – gaohomway Jul 31 '21 at 13:40
  • OK, I can confirm that TabView has a performance issue when we include _many_ views. I can imagine a solution which is some "LazyTabView", based on LazyHStack and a GeometryReader to layout the pages, and a mechanism that "snaps" pages using a ScrollViewReader's scrollTo and swipe gesture recognisers and a bunch of more code. – CouchDeveloper Jul 31 '21 at 14:30
  • 1
    Hi @gaohomway, did you find a good solution for this? I have the same problem now :) – Jaafar Mahdi Jan 21 '23 at 21:30

1 Answers1

0

It doesn't handle big data, that's why it's TabView, not PageView. Its purpose is display tabs as page style, not display pages.

If you insist to use TabView for big data, stop using @Published for page index or it will reload all child views (7000 in your example) and cause the lag

You can test it out on your preview with my example code

struct TestView: View {
    @StateObject var viewModel = ViewModel()
    @State var index = 0
    @State var usePublished = false

    var data = 0..<1000
    var body: some View {
        VStack(spacing: 40) {
            if usePublished {
                TabView(selection: $viewModel.index) {
                    ForEach(data, id:\.self) { i in
                        TestContentView(i).tag(i)
                    }
                }
            } else {
                TabView(selection: $index) {
                    ForEach(data, id:\.self) { i in
                        TestContentView(i).tag(i)
                    }
                }
            }
            Toggle("using @Published", isOn: $usePublished)
                .fixedSize()
        }.tabViewStyle(.page(indexDisplayMode: .always))
    }
}

struct TestContentView: View {
    static var initCount = 0
    @State var initCount = 0
    var value: Int
    init(_ value: Int) { self.value = value ; Self.initCount += 1 }
    var body: some View {
        VStack(spacing: 40) {
            Spacer()
            Text("page \(value)")
            Divider()
            Text("init count : \(initCount)")
        }.onReceive(Timer.publish(every: 0.1, on: .main, in: .default).autoconnect()) { _ in
            initCount = Self.initCount
        }
    }
}

extension TestView {
    class ViewModel: ObservableObject {
        @Published var index = 0
    }
}

struct TestOtherTestViews_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

No new init when changing page with @State enter image description here

1000 new inits each time changing page with @Published enter image description here

meomeomeo
  • 764
  • 4
  • 7