Skip to content

Navigation Menu

Sign in
Sign up

Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ZSideMenu

ZSideMenu

A gesture-driven side menu for SwiftUI with native toolbar and navigation support. Inspired by Claude iOS app.


Features

  • Smooth, interruptible swipe gestures to open and close
  • Full support for SwiftUI NavigationStack and .toolbar modifiers
  • Smart gesture conflict resolution (scroll views, segmented controls, nested navigation)
  • Automatic landscape pinning
  • Optional NavigationSplitView fallback on regular size classes (iPad)
  • Haptic feedback, rounded corners, shadows — matches iOS system aesthetics
  • Zero dependencies · Swift 6.0 · iOS 17+

Installation

Add ZSideMenu via Swift Package Manager:

https://github.com/xzebra/ZSideMenu.git

Or in Package.swift:

dependencies: [
 .package(url: "https://github.com/xzebra/ZSideMenu.git", from: "1.0.0")
]

Quick Start

import ZSideMenu
struct ContentView: View {
 @State private var isMenuOpen = false
 var body: some View {
 ZSideMenuView(isOpen: $isMenuOpen) {
 // Sidebar
 List {
 Button("Home") { isMenuOpen = false }
 Button("Search") { isMenuOpen = false }
 }
 .navigationTitle("Menu")
 } mainContent: {
 // Main content
 NavigationStack {
 Text("Hello, world!")
 .navigationTitle("Home")
 .toolbar {
 ToolbarItem(placement: .navigation) {
 Button("Menu", systemImage: "line.horizontal.3") {
 isMenuOpen.toggle()
 }
 }
 }
 }
 }
 }
}

Native Toolbar Support

ZSideMenu works seamlessly with SwiftUI's toolbar system. Place toolbars in both the sidebar and the main content — they render natively, exactly as you'd expect.

Sidebar with toolbars

ZSideMenuView(isOpen: $isMenuOpen) {
 NavigationStack {
 List {
 ForEach(MenuItem.allCases) { item in
 Label(item.title, systemImage: item.icon)
 }
 }
 .navigationTitle("Menu")
 .toolbar {
 ToolbarItemGroup(placement: .bottomBar) {
 Button("Create", systemImage: "plus") { /* ... */ }
 Spacer()
 Button("Settings", systemImage: "gear") { /* ... */ }
 }
 }
 }
} mainContent: {
 // ...
}

Main content with navigation bar

ZSideMenuView(isOpen: $isMenuOpen) {
 // sidebar ...
} mainContent: {
 NavigationStack {
 List { /* ... */ }
 .navigationTitle("Home")
 .toolbar {
 ToolbarItem(placement: .navigation) {
 Button("Menu", systemImage: "line.horizontal.3") {
 isMenuOpen.toggle()
 }
 }
 ToolbarItem(placement: .primaryAction) {
 Button("Add", systemImage: "plus") { /* ... */ }
 }
 }
 }
}

Nested Navigation

Both the sidebar and main content support full NavigationStack navigation with push/pop. The gesture system automatically detects when a view is pushed onto the navigation stack and disables the sidebar swipe, so the system back-swipe works as expected.

ZSideMenuView(isOpen: $isMenuOpen) {
 // sidebar ...
} mainContent: {
 NavigationStack {
 List(items) { item in
 NavigationLink(item.title) {
 DetailView(item: item)
 // Back swipe works here — sidebar gesture stays out of the way
 }
 }
 .navigationTitle("Items")
 .toolbar {
 ToolbarItem(placement: .navigation) {
 Button("Menu", systemImage: "line.horizontal.3") {
 isMenuOpen.toggle()
 }
 }
 }
 }
}

Configuration

Native sidebar on iPad

When useNativeSidebar is enabled, ZSideMenu automatically switches to NavigationSplitView on regular-width size classes (iPad, Mac Catalyst), giving users the platform-native experience:

ZSideMenuView(
 isOpen: $isMenuOpen,
 useNativeSidebar: true
) {
 // sidebar ...
} mainContent: {
 // main content ...
}

On compact size classes (iPhone), the custom swipe-driven menu is always used.

Custom animation

ZSideMenuView(
 isOpen: $isMenuOpen,
 animation: .spring(duration: 0.35, bounce: 0.15)
) {
 // ...
}

Disable landscape pinning

ZSideMenuView(
 isOpen: $isMenuOpen,
 pinSidebarInLandscape: false
) {
 // Sidebar will slide in/out even in landscape
}

Sidebar background

Apply a .background() modifier directly on ZSideMenuView. The sidebar's navigation background is automatically cleared, so your custom background shows through:

ZSideMenuView(isOpen: $isMenuOpen) {
 SidebarView()
} mainContent: {
 MainView()
}
.background(Color.gray) // sidebar background

Main content background

The main content has its own NavigationStack, so set the background inside that stack (e.g. on the root view or via .toolbarBackground):

ZSideMenuView(isOpen: $isMenuOpen) {
 SidebarView()
} mainContent: {
 NavigationStack {
 List { /* ... */ }
 .scrollContentBackground(.hidden) // hide default List background
 .background(Color.red) // main content background
 }
}

Full Example

A complete example app is included in the Example/ directory. It demonstrates:

  • Tab-based sidebar with selection state
  • Toolbar items in both sidebar and main content
  • Nested navigation with push/pop
  • Custom toolbar title styling
import SwiftUI
import ZSideMenu
struct ContentView: View {
 @State private var isMenuOpen = false
 @State private var selectedTab: MenuItem = .home
 var body: some View {
 ZSideMenuView(isOpen: $isMenuOpen) {
 SidebarView(isMenuOpen: $isMenuOpen, selectedTab: $selectedTab)
 } mainContent: {
 MainView(isMenuOpen: $isMenuOpen, selectedTab: $selectedTab)
 }
 }
}
struct SidebarView: View {
 @Binding var isMenuOpen: Bool
 @Binding var selectedTab: MenuItem
 var body: some View {
 NavigationStack {
 List {
 ForEach(MenuItem.allCases) { item in
 Button {
 selectedTab = item
 isMenuOpen = false
 } label: {
 Label(item.title, systemImage: item.icon)
 }
 }
 }
 .navigationTitle("Menu")
 .toolbar {
 ToolbarItemGroup(placement: .bottomBar) {
 Button("Create", systemImage: "plus") { }
 Spacer()
 Button("Settings", systemImage: "gear") { }
 }
 }
 }
 }
}
struct MainView: View {
 @Binding var isMenuOpen: Bool
 @Binding var selectedTab: MenuItem
 var body: some View {
 NavigationStack {
 List {
 Text("Content for \(selectedTab.title)")
 }
 .navigationTitle(selectedTab.title)
 .toolbar {
 ToolbarItem(placement: .navigation) {
 Button("Menu", systemImage: "line.horizontal.3") {
 isMenuOpen.toggle()
 }
 }
 }
 }
 }
}

Requirements

  • iOS 17.0+ / macCatalyst 17.0+
  • Swift 6.0+
  • Xcode 16+

License

MIT

About

A gesture-driven side menu for SwiftUI with native toolbar and navigation support.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /