A custom SwiftUI navigation container that replaces NavigationStack with horizontal sliding page transitions, an automatic back button, and fully customizable navigation bar content.
- iOS 17+ / macOS 14+
- Swift 5.9+
Add the package to your Xcode project via File > Add Package Dependencies, or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/<your-org>/MIENavigationView.git", from: "1.0.0")
]Routes must conform to Identifiable and Hashable:
enum AppRoute: Hashable, Identifiable {
case home
case detail(item: String)
case settings
var id: Self { self }
}import MIENavigationView
struct ContentView: View {
var body: some View {
MIENavigationView(root: AppRoute.home) { route in
switch route {
case .home:
HomeView()
case .detail(let item):
DetailView(item: item)
case .settings:
SettingsView()
}
}
}
}Child views access the navigator through the environment:
struct HomeView: View {
@Environment(MIENavigator<AppRoute>.self) var navigator
var body: some View {
VStack {
Button("Open Detail") {
navigator.push(.detail(item: "Example"))
}
Button("Settings") {
navigator.push(.settings)
}
}
.mieNavigationTitle("Home")
}
}The @MainActor @Observable class that manages the navigation stack.
let navigator = MIENavigator(root: AppRoute.home)
navigator.push(.detail(item: "id")) // Push a new route
navigator.pop() // Go back one level
navigator.popToRoot() // Return to the root route
navigator.replaceRoot(.settings) // Swap the root route entirely
navigator.replaceStack([.home, .settings]) // Replace the full stack when needed
navigator.currentRoute // The topmost route
navigator.canGoBack // true when stack has more than one route
navigator.stack // The read-only full route stackTwo initializers are available:
// Creates and owns its own navigator internally
MIENavigationView(root: AppRoute.home) { route in ... }
// Uses an external navigator (useful for menu-driven apps that switch roots)
let navigator = MIENavigator(root: AppRoute.home)
MIENavigationView(navigator: navigator) { route in ... }Apply these inside your page views to customize the navigation bar:
// Plain string (renders with .headline font)
.mieNavigationTitle("My Page")
// Custom view for full control over styling
.mieNavigationTitle(Text("Styled").font(.title2).bold().foregroundStyle(.white)).mieNavigationLeading {
Button("Cancel") { /* ... */ }
}
.mieNavigationTrailing {
Button { /* ... */ } label: {
Image(systemName: "gear")
}
}When using Image(systemName:) in a toolbar slot, apply .resizable().scaledToFit() to make it scale correctly — SF Symbols render at their intrinsic font size by default and will not fill the slot automatically:
.mieNavigationTrailing {
Button { /* ... */ } label: {
Image(systemName: "square.and.arrow.up")
.resizable()
.scaledToFit()
}
}// Solid color
.mieNavigationBarBackground(Color.blue)
// Material blur
.mieNavigationBarBackground(.ultraThinMaterial)
// Any view
.mieNavigationBarBackground(
LinearGradient(colors: [.purple, .blue], startPoint: .leading, endPoint: .trailing)
).mieNavigationBackButtonColor(.white)| Condition | Behavior |
|---|---|
| No title set | Title area is empty |
| Can go back (non-root page) | Automatic back button (chevron.left), always — overrides any custom leading item |
| At root + leading item set | Custom leading item shown |
| At root + no leading item | Empty leading area |
| No trailing item | Empty trailing area |
| No background set | System background color |
| No back button color set | Accent color |
When navigated past the root, swiping right from the leading edge pops back to the previous route. The completion threshold is 30% of the screen width.
For apps with a side menu that switches between top-level sections, provide an external navigator and call replaceRoot(_:):
struct AppShell: View {
@State var navigator = MIENavigator(root: AppRoute.home)
var body: some View {
HStack(spacing: 0) {
SideMenu { selection in
navigator.replaceRoot(selection)
}
MIENavigationView(navigator: navigator) { route in
switch route {
case .home: HomeView()
case .detail: DetailView()
case .settings: SettingsView()
}
}
}
}
}Sources/MIENavigationView/
├── MIENavigationView.swift # Main container with horizontal transitions
├── MIENavigator.swift # Observable navigation state manager
├── MIENavigationBar.swift # The header bar (title, leading, trailing, background)
├── MIENavigationPreferences.swift # SwiftUI PreferenceKeys for bar customization
└── MIENavigationModifiers.swift # Public .mieNavigation* view modifiers
MIT