Contents

heestand-xyz/tabs

Tabs built in SwiftUI for iOS and macOS.

Swift Package

.package(url: "https://github.com/heestand-xyz/Tabs", from: "1.4.0")

Setup

import SwiftUI
import Tabs

struct MyTab: Identifiable {
    let id: UUID
    let name: String
}

struct ContentView: View {
    
    private static let myTabsStyle = TabsStyle(
        padding: 4.0,
        spacing: .tabSpacing,
        width: nil,
        height: .tabHeight,
        shape: .capsule
    )
    
    private static let myTabsInteraction = TabsInteraction(
        dragActivation: .onEnd
    )
    
    @State private var myTabs: [MyTab] = [
        MyTab(id: UUID(), name: "First"),
        MyTab(id: UUID(), name: "Second"),
        MyTab(id: UUID(), name: "Third"),
    ]
    
    private var myTabIDs: Binding<[UUID]> {
        Binding {
            myTabs.map(\.id)
        } set: { newIDs in
            myTabs = newIDs.compactMap { id in
                myTabs.first { myTab in
                    myTab.id == id
                }
            }
        }
    }
    
    @State private var myActiveTabID: UUID?
    
    var body: some View {
        Tabs(
            style: Self.myTabsStyle,
            interaction: Self.myTabsInteraction,
            openIDs: myTabIDs,
            activeID: $myActiveTabID,
            showClose: true
        ) { tabValue in
            if let myTab = myTabs.first(where: { $0.id == tabValue.id }) {
                MyTabView(myTab: myTab, tabValue: tabValue)
            }
        } xmark: { tabValue in
            Image(systemName: "xmark")
                .imageScale(.small)
                .padding(4.0)
                .foregroundStyle(tabValue.isActive ? .white : .primary)
        }
        .padding(.vertical, .tabSpacing)
        .clipShape(.capsule)
        .background {
            Capsule()
                .opacity(0.1)
        }
        .onAppear {
            myActiveTabID = myTabs.first?.id
        }
    }
}

struct MyTabView: View {
    
    let myTab: MyTab
    let tabValue: TabValue
    
    var body: some View {
        ZStack {
            background
            foreground
                .padding(.horizontal, 24.0)
                .padding(.vertical, 2.0)
        }
    }
    
    private var background: some View {
        if tabValue.isActive {
            Color.accentColor
        } else  {
            Color.primary
                .opacity(0.1)
        }
    }
    
    private var foreground: some View {
        Text(myTab.name)
            .foregroundStyle(tabValue.isActive ? .white : .primary)
    }
}

#Preview {
    ContentView()
        .padding()
}

Tabs

Tabs(
    style: TabsStyle = TabsStyle(shape: .rectangle),
    interaction: TabsInteraction = TabsInteraction(dragActivation: nil),
    enabledIDs: [UUID]? = nil,
    openIDs: Binding<[UUID]>,
    activeID: Binding<UUID?>,
    showClose: Bool = true,
    closeConfirmation: ((UUID) async -> Bool)? = nil,
    @ViewBuilder content: @escaping (TabValue) -> Content,
    @ViewBuilder xmark: @escaping (TabValue) -> Xmark = { _ in EmptyView() }
)

Tabs Style

TabsStyle(
    padding: CGFloat = 0.0,
    spacing: CGFloat = .tabSpacing,
    width: CGFloat? = nil,
    height: CGFloat = CGSize.tabSize.height,
    shape: Shape
)

Tabs Interaction

TabsInteraction(
    dragActivation: DragActivation? = nil
)

Package Metadata

Repository: heestand-xyz/tabs

Default branch: main

README: README.md