Search code examples
iosxcodeswiftuitabsscrollview

Scroll view confuses size of content between tabs


I have a scroll view with two buttons that switch the content of the scroll view. Content for the first tab is very big and for the second tab its very small compared to the first. The bug is not very consistent and it is hard to reproduce, but the best way to reproduce it is to scroll while the tab with the large content is selected, then stop the scroll midway with a tap and switch to the other tab. And then only the background is shown no items are loaded until you scroll a bit and the scroll view fixes its content size. When the bug happens the scroll indicator stays very small even tho we have moved to the tab that does not have so much content and its usual scroll indicator is much bigger

Edit: I have now tried using .id(UUID()) based on https://www.youtube.com/watch?v=h0SgafWwoh8, and also I tried using ScrollViewReader to scroll to the top of each section when content changes (with .onChange(of content)). Both those did not work.

struct ContentView: View {
    
    enum Content: String {
        case one
        case two
    }
    
    @State private var contentSelection: Content = .one
    
    var body: some View {
        ZStack {
            Color.red
            GeometryReader { proxy in
                ScrollView {
                    ZStack {
                        LazyVStack(pinnedViews: .sectionHeaders) {
                            Text("There is some other content here")
                                .frame(height: proxy.size.height/2)
                                .background(Color.orange)
                            Section(header:
                                HStack {
                                    Spacer()
                                    Button(action: { contentSelection = .one }) {
                                        Text("One")
                                            .foregroundColor(contentSelection == .one ? .black : .gray)
                                    }
                                    Button(action: { contentSelection = .two }) {
                                        Text("two")
                                            .foregroundColor(contentSelection == .two ? .black : .gray)
                                    }
                                    Spacer()
                                }
                                .padding()
                                .background(Color.yellow)
                            ) {
                                switch contentSelection {
                                case .one:
                                    LazyVStack {
                                        ForEach((1...500), id: \.self) { item in
                                            ZStack {
                                                Text("This is item number \(item)")
                                            }
                                            .frame(height: 80)
                                        }
                                    }
                                case .two:
                                    LazyVStack {
                                        ForEach((1...50), id: \.self) { item in
                                            ZStack {
                                                Text("This is item number \(item)")
                                            }
                                            .frame(height: 80)
                                        }
                                    }
                                }
                            }
                        }
                    }
                    .background(Color.green)
                }
            }
        }
    }
}

Solution

  • struct ContentView: View {
        
        enum Content: String {
            case one
            case two
        }
        
        @State private var contentSelection: Content = .one
        
        var body: some View {
            ZStack {
                Color.red
                GeometryReader { proxy in
                    ScrollView {
                        ZStack {
                            LazyVStack(pinnedViews: .sectionHeaders) {
                                Text("There is some other content here")
                                    .frame(height: proxy.size.height/2)
                                    .background(Color.orange)
                                Section(header: headerView
                                ) {
                                    switch contentSelection {
                                    case .one:
                                        content1
                                    case .two:
                                        content2
                                    }
                                }
                            }
                        }
                        .background(Color.green)
                    }
                }
            }
        }
        
        var content1: some View {
            LazyVStack {
                ForEach((1...500), id: \.self) { item in
                    ZStack {
                        Text("This is item number \(item)")
                    }
                    .frame(height: 80)
                }
            }
        }
        
        var content2: some View {
            LazyVStack {
                ForEach((1...500), id: \.self) { item in
                    ZStack {
                        Text("This is item number \(item)")
                    }
                    .frame(height: 80)
                }
            }
        }
        
        var headerView: some View {
            HStack {
                Spacer()
                Button(action: { contentSelection = .one }) {
                    Text("One")
                        .foregroundColor(contentSelection == .one ? .black : .gray)
                }
                Button(action: { contentSelection = .two }) {
                    Text("two")
                        .foregroundColor(contentSelection == .two ? .black : .gray)
                }
                Spacer()
            }
            .padding()
            .background(Color.yellow)
        }
    }