Search code examples
swiftuitabview

TabView button doesn't work when initialized to 2nd tab


I have a scrollable tabview that displays on the click of either button in the home view (ContentView). I want the 1st tab to show when "View 1" is clicked, and the 2nd tab to show when "View 2" is clicked. Once the tabview is opened, it should still function properly, meaning the tab buttons and scrolling still works correctly. Now that I think about it, this is similar to clicking on the "Followers" or "Following" button on Instagram.

When I click on "View 2" it shows the 2nd tab correctly, but when I click on the tab button for View 1, the screen does not show the View 1 text. Video. Does anyone know how to fix this bug? Thanks!

ContentView.swift:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationStack {
            HStack {
                NavigationLink {
                    ScrollTab(currentTab: 0)
                } label: {
                    Text("View 1")
                        .font(.subheadline)
                        .fontWeight(.semibold)
                }
                
                NavigationLink {
                    ScrollTab(currentTab: 1)
                } label: {
                    VStack {
                        Text("View 2")
                            .font(.subheadline)
                            .fontWeight(.semibold)
                    }
                    .frame(width: 76)
                }
            }
        }
    }
}

ScrollTab.swift:

struct ScrollTab: View {
    @State private var currentTab: Int = 0
    
    init(currentTab: Int) {
        _currentTab = State(initialValue: currentTab)
    }
    
    var body: some View {
        Spacer()
        VStack(alignment: .center, spacing: 0) {
            TabBarView(currentTab: self.$currentTab)
            
            TabView(selection: self.$currentTab) {
                Text("This is view 1").tag(0)
                    .onAppear() {
                        self.currentTab = 0
                    }
                Text("This is view 2").tag(1)
                    .onAppear() {
                        self.currentTab = 1
                    }
            }
            .tabViewStyle(.page(indexDisplayMode: .never))
            .background(Color.secondary)
        }
        .edgesIgnoringSafeArea(.all)
    }
}

struct TabBarView: View {
    @Binding var currentTab: Int
    @Namespace var namespace
    
    var tabBarOptions: [String] = ["View 1", "View 2"]
    var body: some View {
        HStack(spacing: 20) {
            ForEach(Array(zip(self.tabBarOptions.indices,
                              self.tabBarOptions)),
                    id: \.0,
                    content: {
                index, name in
                TabBarItem(currentTab: self.$currentTab,
                           namespace: namespace.self,
                           tabBarItemName: name,
                           tab: index)
                
            })
        }
        .padding(.horizontal)
        .background(Color.orange) // <<<< Remove
        .frame(height: 80)
    }
}

struct TabBarItem: View {
    @Binding var currentTab: Int
    let namespace: Namespace.ID
    
    var tabBarItemName: String
    var tab: Int
    
    var body: some View {
        Button {
            self.currentTab = tab
        } label: {
            VStack {
                Spacer()
                Text(tabBarItemName)
                if currentTab == tab {
                    Color.black
                        .frame(height: 2)
                        .matchedGeometryEffect(id: "underline",
                                               in: namespace,
                                               properties: .frame)
                } else {
                    Color.clear.frame(height: 2)
                }
            }
            .animation(.spring(), value: self.currentTab)
        }
        .buttonStyle(.plain)
    }
}

Solution

  • As I stated with the comment above, under the line of TabView(selection: self.$currentTab), you have two Text views with onAppear. That's where a problem occurs.

    So I have separated those two views from the ScrollTab view and created another named TextView as follows.

    import SwiftUI
    
    struct ScrollTab: View {
        @State private var currentTab: Int = 0
        
        init(currentTab: Int) {
            _currentTab = State(initialValue: currentTab)
        }
        
        var body: some View {
            Spacer()
            VStack(alignment: .center, spacing: 0) {
                TabBarView(currentTab: self.$currentTab)
                
                TabView(selection: self.$currentTab) {
                    TextView(currentTab: $currentTab)
                }
                .tabViewStyle(.page(indexDisplayMode: .never))
                .background(Color.secondary)
            }
            .edgesIgnoringSafeArea(.all)
        }
    }
    
    #Preview {
        ScrollTab(currentTab: 1)
    }
    
    struct TextView: View {
        @Binding var currentTab: Int
        
        var body: some View {
            VStack {
                if currentTab == 0 {
                    Text("This is view 1")
                } else {
                    Text("This is view 2")
                }
            }
        }
    }
    

    I haven't touched ContentView and other actors.

    UPDATE

    I have gotten rid of TextView. You can now scroll it horizontally to switch tabs.

    struct ScrollTab: View {
        @State private var currentTab: Int = 0
        
        init(currentTab: Int) {
            _currentTab = State(initialValue: currentTab)
        }
        
        var body: some View {
            Spacer()
            VStack(alignment: .center, spacing: 0) {
                TabBarView(currentTab: self.$currentTab)
                
                TabView(selection: self.$currentTab) {
                    ForEach(0...1, id: \.self) { number in
                        VStack {
                            if number == 0 {
                                Text("This is view 1")
                            } else {
                                Text("This is view 2")
                            }
                        }.tag(number)
                    }
                }
                .tabViewStyle(.page(indexDisplayMode: .never))
                .background(Color.secondary)
            }
            .edgesIgnoringSafeArea(.all)
        }
    }
    

    UPDATE 2

    struct ScrollTab: View {
        @State private var currentTab: Int = 0
        
        init(currentTab: Int) {
            _currentTab = State(initialValue: currentTab)
        }
        
        var body: some View {
            Spacer()
            VStack(alignment: .center, spacing: 0) {
                TabBarView(currentTab: self.$currentTab)
                
                TabView(selection: self.$currentTab) {
                    VStack {
                        if currentTab == 0 {
                            Text("This is view 1").tag(0)
                        } else {
                            Text("This is view 2").tag(1)
                        }
                    }
                }
                .tabViewStyle(.page(indexDisplayMode: .never))
                .background(Color.secondary)
            }
            .edgesIgnoringSafeArea(.all)
        }
    }
    

    enter image description here