|
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() |
|
} |
|
} |
|
} |
|
} |
|
} |