swiftuisegmentedcontrol

SwiftUI: Segmented Control scrolling


I'm using a segmented control to navigate between tabs. My issue is, when I scroll inside one tab, all the other tabs are also strolling. Which is something I don't want.

I want that the scrolling works only inside the selected tab and when I click on the other tabs, it shows the top of the content of the selected tab.

Check attached video : https://i.sstatic.net/uBZAK.jpg

and here is my code

Thanks!

import SwiftUI

struct ContentView: View {
    
    @State var selectedState = 0
    @State var showTopTabBar: Bool = false
    
    var body: some View {
        
        ZStack (alignment: . top){
            
            ScrollView (showsIndicators: false){
                
                Text("Welcome")
                    .frame(height: 300)
                
                Picker("Options", selection: $selectedState) {
                    Text("First").tag(0)
                    Text("Second").tag(1)
                    Text("Third").tag(2)
                }
                .pickerStyle(SegmentedPickerStyle())
                .background(Color.white)
                .opacity(showTopTabBar ? 0 : 1)
                .readPos { rect in
                    if showTopTabBar != (rect.minY <= 100) {
                        showTopTabBar.toggle()
                    }
                }
                
                if (selectedState==0) {
                    
                    FirstView()
                    
                }
                
                else if (selectedState==1){
                    Secondview()
                }
                
                else {
                    ThirdView()
                }
                
                Spacer()
                
            }
            .padding()
            
            Picker("Options", selection: $selectedState) {
                Text("First").tag(0)
                Text("Second").tag(1)
                Text("Third").tag(2)
            }
            .pickerStyle(SegmentedPickerStyle())
            .padding()
            .background(Color.white)
            .opacity(showTopTabBar ? 1 : 0)
            
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


extension View {
    
    func readPos(onChange: @escaping (CGRect) -> Void) -> some View {
        background(
            GeometryReader { geometryProxy in
                Color.clear
                    .preference(key: CoordinatesPreferenceKey.self, value: geometryProxy.frame(in: .global))
            }
        )
            .onPreferenceChange(CoordinatesPreferenceKey.self, perform: onChange)
    }
    
}


private struct CoordinatesPreferenceKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {}
}


Solution

  • You can use ScrollViewReader to scroll up to start on change of Tabs:

    struct ContentView: View {
        
        @State var selectedState = 0
        @State var showTopTabBar: Bool = false
        
        var body: some View {
            
            ZStack (alignment: . top) {
                
                ScrollView (showsIndicators: false) {
                    
                    // added ScrollViewReader
                    ScrollViewReader { scrollProxy in
                        
                        Text("Welcome")
                            .frame(height: 300)
                            .id("welcome")  // added id to scroll to
                        
                        tabPicker
                            .background(Color.white)
                            .opacity(showTopTabBar ? 0 : 1)
                            .readPos { rect in
                                if showTopTabBar != (rect.minY <= 100) {
                                    showTopTabBar.toggle()
                                }
                            }
                        
                        if (selectedState==0) {
                            ListContentView(tab: "First View", color: .blue)
                        }
                        
                        else if (selectedState==1){
                            ListContentView(tab: "Second View", color: .green)
                        }
                        
                        else {
                            ListContentView(tab: "Third View", color: .orange)
                        }
                        Spacer()
                        
                        // on Change of selected Tab scroll to top
                            .onChange(of: selectedState) { newValue in
                                scrollProxy.scrollTo("welcome", anchor: .center)
                            }
                    }
                }
                .padding()
                
                tabPicker
                    .padding()
                    .background(Color.white)
                    .opacity(showTopTabBar ? 1 : 0)
            }
        }
        
        var tabPicker: some View {
            Picker("Options", selection: $selectedState) {
                Text("First").tag(0)
                Text("Second").tag(1)
                Text("Third").tag(2)
            }
            .pickerStyle(SegmentedPickerStyle())
        }
    }
    
    
    // dummy content view
    struct ListContentView: View {
        
        let tab: String
        let color: Color
        
        var body: some View {
            ForEach(0..<30) { item in
                Text("\(tab) - \(item)")
                    .padding()
                    .frame(maxWidth: .infinity)
                    .background(color)
            }
        }
    }
    

    This now always scrolls up on change of Tab. If you want to preserve the positions in each tab you have to save them in @State vars based on .ids of your displayed content.