Setting up the TabView

Setting up the TabView ↔️

In our ContentView, we can now replace the existing “Hello, world” Text with a TabView.

But first, we have to declare a State property that tells our TabView which subview to display. By default, we want to display the .meditating Tab.

@State var selectedTab: Tab = .meditating

Now we can insert a TabView into our ContentView and bind it to the selectedTab State.

struct ContentView: View {
    
    @State var selectedTab: Tab = .meditating
    
    var body: some View {
        TabView(selection: $selectedTab) {
            
        }
    }
}

SwiftUI now starts to prepare a tab bar for our ContentView. Don’t worry, we will convert it into a swipeable page view in a moment.

Our TabView should cycle through each SubviewModel instance in our subviewData array and initialize a corresponding Subview. For this purpose, we use a ForEach statement. 

TabView(selection: $selectedTab) {
    ForEach(subviewData) { entry in
        Subview(subviewModel: entry)
    }
}

In order for our TabView to correctly assign and arrange the initialized Subviews we have to append a .tag to each Subview. For this purpose, we use the tag property of our SubviewModel.

TabView(selection: $selectedTab) {
    ForEach(subviewData) { entry in
        Subview(subviewModel: entry)
            .tag(entry.tag)
    }
}

As said before our TabView just created a tab bar for us. To add different tab bar items we could use the .tabItem modifier at this point. For example, as follows:

Subview(subviewModel: entry)
    .tag(entry.tag)
    .tabItem {
        Text("More")
        Image(systemName: "star")
}

But instead of a tab bar, we need a swipeable page view. To do this, we simply apply the .tabViewStyle modifier to our TabView and use the PageViewTabStyle.

TabView(selection: $selectedTab) {
    //...
}
    .tabViewStyle(PageTabViewStyle())

The previously generated tab bar has disappeared. If we now start a Live preview, we see that we can easily swipe through the different subviews.

Let’s embed our TabView into a NavigationView and add a .navigationTitle to it.

NavigationView {
    TabView(selection: $selectedTab) {
        //...
    }
        .tabViewStyle(PageTabViewStyle())
        .navigationTitle("Calmify")
}

Your ContentView preview should now look like this:

But where is the dotted page indicator that tells the user which page he is on? In fact, it is already there, but unfortunately, it is almost invisible when the “Light Mode” is activated. We can easily check this by adding another Preview simulator with “Dark Mode” enabled:

We can fix this “bug” by adjusting the color scheme of the indicator. 

struct ContentView: View {
    
    //...
    
    init() {
       UIPageControl.appearance().currentPageIndicatorTintColor = .orange
       UIPageControl.appearance().pageIndicatorTintColor = UIColor.gray.withAlphaComponent(0.5)
       }
    
    var body: some View {
        //...
    }
}

The Page view indicator is now visible even with “LightMode” enabled. 

Swiping through the page view manually ↔️

We also want to allow the user to manually go to the next Subview by tapping a corresponding Button.

To do this, insert the following struct below your ContentView struct:

struct NavigatorView: View {
    
    var body: some View {
        HStack {
            Spacer()
            Button(action: {
                print("Go to the next subview")
            }) {
                Image(systemName: "arrow.right")
                    .resizable()
                    .foregroundColor(.white)
                    .frame(width: 30, height: 30)
                    .padding()
                    .background(Color.orange)
                    .cornerRadius(30)
            }
        }
            .padding()
    }
}

Next, we add a NavigatorView to our ContentView. This NavigatorView should be stacked on top of the ContentView. To do this, we wrap the TabView in a ZStack and select the .bottomTrailing alignment mode. 

NavigationView {
            ZStack(alignment: .bottomTrailing) {
                TabView(selection: $selectedTab) {
                    //...
                }
                    .tabViewStyle(PageTabViewStyle())
                    .navigationTitle("Calmify")
                NavigatorView()
            }
        }

Your ContentView preview should now look like this:

When we click on the Button in our NavigatorView, we want to navigate to the next Subview. For this, we need a Binding to the selectedTab State of the ContentView

struct NavigatorView: View {
    
    @Binding var selectedTab: Tab
    
    var body: some View {
        //...
    }
}

Let’s initialize this Binding in our ContentView.

NavigatorView(selectedTab: $selectedTab)

By inserting a switch statement into the action closure of our Button we can specify that the next Subview should be shown each time the user taps the Button. If we now embed this switch statement in a withAnimation statement, the transition to the next tab view is animated as if we were swiping.

Button(action: {
    withAnimation {
        switch selectedTab {
        case .meditating:
            selectedTab = .running
        case .running:
            selectedTab = .walking
        case .walking:
            return
        }
    }
}) {
    //...
}

If we now run our app, we can navigate manually using our NavigatorView

Conclusion 🎊

Great, we finished our “Onboarding” app! You’ve learned how to introduce the user to a newly installed app using a modified TabView. We also learned more about working with view hierarchies in SwiftUI. 

In the next chapter, we will learn how to store data persistently in SwiftUI and create our own To-Do app.

2 replies on “Setting up the TabView”

Leave a Reply

Your email address will not be published. Required fields are marked *