Skip to content

Instantly share code, notes, and snippets.

@slowbrewedmacchiato
Last active June 14, 2025 06:41
Show Gist options
  • Save slowbrewedmacchiato/25cb04c7cadf627f39baebad72f084fc to your computer and use it in GitHub Desktop.
Save slowbrewedmacchiato/25cb04c7cadf627f39baebad72f084fc to your computer and use it in GitHub Desktop.
Creating iOS 26 Tab bars with separated option
import SwiftUI
/// The main application entry point for the Ancient Prophecy Tab Bars app.
///
/// This app demonstrates a custom tab bar implementation with an overlay menu system.
/// Specifically the implementation of a separate tab bar option now available in iOS 26.
/// The app automatically exits when the main content view disappears and uses a fixed content size window.
@main
struct AncientProphecyTabBarsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onDisappear {
// Exit the app when the content view disappears
exit(0)
}
}
// Set window to resize based on content size only
.windowResizability(.contentSize)
}
}
// MARK: - Custom Tab Enum
/// Represents the available tabs in the custom tab bar interface.
///
/// Each tab case provides its own title, SF Symbol, and unique identifier.
/// The enum conforms to multiple protocols to support SwiftUI's TabView and ForEach requirements.
enum CustomTab: Int, Equatable, Hashable, Identifiable, CaseIterable {
case watchNow
case library
case new
case favorites
case share
/// Unique identifier for each tab case.
///
/// Uses the raw integer value as the identifier for Identifiable conformance.
var id: Int {
rawValue
}
/// Localized display title for each tab.
///
/// Returns the appropriate localized string for display in the tab bar.
var title: String {
switch self {
case .watchNow:
return String(localized: "Watch Now", comment: "Tab title for immediate content viewing")
case .library:
return String(localized: "Library", comment: "Tab title for content library")
case .new:
return String(localized: "New", comment: "Tab title for new content discovery")
case .favorites:
return String(localized: "Favorites", comment: "Tab title for favorited content")
case .share:
return String(localized: "share", comment: "Tab title for sharing functionality")
}
}
/// SF Symbol name for each tab's icon.
///
/// Returns the appropriate SF Symbol string for display in the tab bar.
var symbol: String {
switch self {
case .watchNow:
return "tv.fill"
case .library:
return "play.square.stack.fill"
case .new:
return "video.fill.badge.plus"
case .favorites:
return "heart.fill"
case .share:
return "square.and.arrow.up.fill"
}
}
/// Creates a view representation for the specified tab's content area.
///
/// This static method generates a consistent layout for all tab content views,
/// displaying the tab's icon and title in a vertical stack.
///
/// - Parameter tab: The tab case to create a view for
/// - Returns: A SwiftUI view containing the tab's icon and title
@ViewBuilder
static func makeTabView(for tab: CustomTab) -> some View {
if tab == .library {
ScrollView {
LazyVStack(alignment: .leading, spacing: 12) {
ForEach(0 ..< 100, id: \.self) { _ in
HStack {
Text(UUID().uuidString.prefix(18))
.padding()
Spacer()
Image(systemName: "play.circle")
.padding()
}
}
}
.padding(.vertical)
}
} else {
VStack(spacing: 25) {
// Display the tab's SF Symbol icon
Image(systemName: tab.symbol)
.font(.largeTitle)
// Display the tab's localized title
Text(tab.title)
.font(.title)
}
}
}
}
// MARK: - ContentView
/// The main content view that implements a custom tab bar with overlay menu functionality.
///
/// This view creates a TabView with custom tabs and implements a special overlay menu
/// that appears when the share tab is selected. The overlay menu provides additional
/// options without actually navigating to a share tab.
@available(iOS 26, *)
struct ContentView: View {
// MARK: - Properties
/// Array of all available tabs for the tab bar
let tabs: [CustomTab] = CustomTab.allCases
/// The currently selected tab in the tab bar
///
/// This state property tracks which tab is currently active and updates the UI accordingly.
@State private var selectedTab: CustomTab = .watchNow
/// Controls the visibility of the overlay menu
///
/// When true, displays the overlay menu with additional options.
/// This is triggered when the share tab is selected.
@State private var showOverlay: Bool = false
// MARK: - Body
var body: some View {
ZStack {
// MARK: - Main Tab View
TabView(selection: $selectedTab) {
ForEach(tabs) { tab in
Tab(value: tab, role: tab == CustomTab.share ? .search : .none) {
// Display the content view for each tab
ZStack {
CustomTab.makeTabView(for: tab)
}
} label: {
// Tab bar item with icon and title
Image(systemName: tab.symbol)
Text(tab.title)
}
}
}
.tint(Color(uiColor: .systemPink)) // Custom tint color for the tab bar
.tabBarMinimizeBehavior(.onScrollDown) // Hide tab bar when scrolling down
.onChange(of: selectedTab) { oldValue, newValue in
// Handle share tab selection by showing overlay instead of navigating
if newValue == .share {
showOverlay = true
// Revert to previous tab selection to prevent actual navigation
selectedTab = oldValue
}
}
// MARK: - Overlay Menu
if showOverlay {
// Semi-transparent background overlay
Color.black.opacity(0.0001)
.ignoresSafeArea()
.transition(.opacity)
.onTapGesture {
// Dismiss overlay when tapping outside the menu
withAnimation(.bouncy) {
showOverlay = false
}
}
// MARK: - Menu Content
GeometryReader { proxy in
VStack {
Spacer()
HStack {
Spacer()
// Menu container with options
VStack(alignment: .leading, spacing: 0) {
// Menu header
Text("Some Options")
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(.primary)
.padding()
Divider()
// First menu option
Button {
// Handle first option selection
// TODO: Implement actual functionality
withAnimation {
showOverlay = false
}
} label: {
VStack(alignment: .leading) {
Text("Button Option 1")
.font(.headline)
.fontWeight(.medium)
.foregroundStyle(.primary)
Text("Subtitle option 1")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
}
.buttonStyle(.plain)
Divider()
// Second menu option
Button {
// Handle second option selection
// TODO: Implement actual functionality
withAnimation {
showOverlay = false
}
} label: {
VStack(alignment: .leading) {
Text("Button Option 2")
.font(.headline)
.fontWeight(.medium)
.foregroundStyle(.primary)
Text("Subtitle option 2")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
}
.buttonStyle(.plain)
}
.background(.ultraThinMaterial) // Translucent background material
.frame(width: proxy.size.width * 0.5) // Responsive width
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
.padding(.horizontal, 24)
.padding(.bottom, 60) // Account for tab bar height
.shadow(radius: 2) // Subtle shadow for depth
.transition(.move(edge: .bottom).combined(with: .opacity))
}
}
}
.animation(.bouncy, value: showOverlay) // Bouncy animation for menu appearance
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment