Reading time: 1 min

This tutorial shows how to add Android-like top tabs in SwiftUI. The result will look something like this:

Without much ado, here's the code for the component:

struct Tabs<Label: View>: View {
  @Binding var tabs: [String] // The tab titles
  @Binding var selection: Int // Currently selected tab
  let underlineColor: Color // Color of the underline of the selected tab
  // Tab label rendering closure - provides the current title and if it's the currently selected tab
  let label: (String, Bool) -> Label

  var body: some View {
    // Pack the tabs horizontally and allow them to be scrolled
    ScrollView(.horizontal, showsIndicators: false) {
      HStack(alignment: .center, spacing: 30) {
        ForEach(tabs, id: \.self) {
          self.tab(title: $0)
        }
      }.padding(.horizontal, 3) // Tuck the out-most elements in a bit
  }

  private func tab(title: String) -> some View {
    let index = self.tabs.firstIndex(of: title)!
    let isSelected = index == selection
    return Button(action: {
      // Allows for animated transitions of the underline, 
      // as well as other views on the same screen
      withAnimation { 
         self.selection = index
      }
    }) {
      label(title, isSelected)
        .overlay(Rectangle() // The line under the tab
          .frame(height: 2)
           // The underline is visible only for the currently selected tab
          .foregroundColor(isSelected ? underlineColor : .clear)
          .padding(.top, 2)
          // Animates the tab selection
          .transition(.move(edge: .bottom)) ,alignment: .bottomLeading)      
    }
  }
}

And here's a example of how to put it to use:

@State private var selectedTab = 0

...

VStack {
  Tabs(tabs: .constant(["Tabs", "Are", "Fun!"]),
           selection: $selectedTab,
           underlineColor: .black) { title, isSelected in
             Text(title.uppercased())
               .font(.system(size: 14))
               .fontWeight(.semibold)
               .foregroundColor(isSelected ? .black : .gray)
           }

  Text("Selected tab is at index \(tab)")
    .padding(.top, 20)
  Spacer()
}.padding()

Next Post Previous Post