Skip to content

Instantly share code, notes, and snippets.

@conradev
Last active May 16, 2023 09:55
Show Gist options
  • Save conradev/ca279399da91535878ba9e48d1dce18e to your computer and use it in GitHub Desktop.
Save conradev/ca279399da91535878ba9e48d1dce18e to your computer and use it in GitHub Desktop.
NavigationStackView
import SwiftUI
@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
OnboardingView()
}
}
}
// MARK: OnboardingView
struct OnboardingView: View {
enum Screen: CaseIterable {
case welcome
case permissions
case signIn
case tutorial
}
@State
var stack: [Screen] = [.welcome]
var body: some View {
NavigationStackView(stack: $stack) { screen in
VStack {
switch screen {
case .welcome:
Text("Welcome")
Button("Continue") {
stack.append(.permissions)
}
case .permissions:
Text("Permissions")
Button("Continue") {
stack.append(.signIn)
}
case .signIn:
Text("Sign In")
Button("Continue") {
stack.append(.tutorial)
}
case .tutorial:
Text("Tutorial")
}
}
}
}
}
// MARK: NavigationStackView
struct NavigationStackView<Content, Tag>: View where Content: View, Tag: CaseIterable & Hashable, Tag.AllCases: RandomAccessCollection {
@Binding
var stack: [Tag]
private var depth: Int = 0
private var content: (Tag) -> Content
private var isRoot: Bool {
depth == 0
}
private var current: Tag? {
component(at: depth).wrappedValue
}
private var next: Binding<Tag?> {
component(at: depth + 1)
}
init(stack: Binding<[Tag]>, @ViewBuilder content: @escaping (Tag) -> Content) {
self.init(stack: stack, depth: 0, content: content)
}
private init(stack: Binding<[Tag]>, depth: Int, @ViewBuilder content: @escaping (Tag) -> Content) {
self._stack = stack
self.depth = depth
self.content = content
}
private func component(at index: Int) -> Binding<Tag?> {
.init {
if index < stack.count {
return stack[index]
} else {
return nil
}
} set: { newValue in
if let newValue = newValue {
if index < stack.count {
stack[index] = newValue
} else {
stack.append(newValue)
}
} else {
if index < stack.count {
stack.removeSubrange(index..<stack.endIndex)
}
}
}
}
var body: some View {
if isRoot {
NavigationView {
page
}
.navigationViewStyle(.stack)
} else {
page
}
}
@ViewBuilder
private var page: some View {
if let current = current {
content(current)
.overlay(links)
} else {
links
}
}
@ViewBuilder
private var links: some View {
VStack {
ForEach(Tag.allCases, id: \.self) { tag in
NavigationLink(tag: tag, selection: next) {
Self(stack: $stack, depth: depth + 1, content: content)
} label: {
EmptyView()
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment