Skip to content

Commit

Permalink
Add present and dismiss functionality to Navigation Coordinator
Browse files Browse the repository at this point in the history
  • Loading branch information
silkodenis committed May 2, 2024
1 parent 01c89e8 commit 2cb1090
Show file tree
Hide file tree
Showing 14 changed files with 382 additions and 82 deletions.
8 changes: 4 additions & 4 deletions Navigation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
2730D6172BDFEC4A00DB4FF6 /* NavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2730D6122BDFEC4A00DB4FF6 /* NavigationCoordinator.swift */; };
2730D6182BDFEC4A00DB4FF6 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2730D6132BDFEC4A00DB4FF6 /* RootView.swift */; };
2730D6192BDFEC4A00DB4FF6 /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2730D6142BDFEC4A00DB4FF6 /* Screen.swift */; };
2730D61A2BDFEC4A00DB4FF6 /* UnwindSegueModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2730D6152BDFEC4A00DB4FF6 /* UnwindSegueModifier.swift */; };
2730D61A2BDFEC4A00DB4FF6 /* RegisterSegueModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2730D6152BDFEC4A00DB4FF6 /* RegisterSegueModifier.swift */; };
2730D6382BDFEE2000DB4FF6 /* NavigationFlowUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2730D6372BDFEE2000DB4FF6 /* NavigationFlowUITests.swift */; };
2730D63A2BDFEE2A00DB4FF6 /* NavigationCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2730D6392BDFEE2A00DB4FF6 /* NavigationCoordinatorTests.swift */; };
2730D63B2BDFEE5700DB4FF6 /* AccessibilityID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2730D6082BDFEBEA00DB4FF6 /* AccessibilityID.swift */; };
Expand Down Expand Up @@ -56,7 +56,7 @@
2730D6122BDFEC4A00DB4FF6 /* NavigationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationCoordinator.swift; sourceTree = "<group>"; };
2730D6132BDFEC4A00DB4FF6 /* RootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
2730D6142BDFEC4A00DB4FF6 /* Screen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
2730D6152BDFEC4A00DB4FF6 /* UnwindSegueModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnwindSegueModifier.swift; sourceTree = "<group>"; };
2730D6152BDFEC4A00DB4FF6 /* RegisterSegueModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegisterSegueModifier.swift; sourceTree = "<group>"; };
2730D61F2BDFEDBC00DB4FF6 /* NavigationUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NavigationUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
2730D62C2BDFEDCA00DB4FF6 /* NavigationUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NavigationUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
2730D6372BDFEE2000DB4FF6 /* NavigationFlowUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationFlowUITests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -155,7 +155,7 @@
2730D6122BDFEC4A00DB4FF6 /* NavigationCoordinator.swift */,
2730D6132BDFEC4A00DB4FF6 /* RootView.swift */,
2730D6142BDFEC4A00DB4FF6 /* Screen.swift */,
2730D6152BDFEC4A00DB4FF6 /* UnwindSegueModifier.swift */,
2730D6152BDFEC4A00DB4FF6 /* RegisterSegueModifier.swift */,
);
path = Navigation;
sourceTree = "<group>";
Expand Down Expand Up @@ -313,7 +313,7 @@
2730D6172BDFEC4A00DB4FF6 /* NavigationCoordinator.swift in Sources */,
2730D6112BDFEBEA00DB4FF6 /* View+Ext.swift in Sources */,
2730D5F42BDFEB8A00DB4FF6 /* NavigationApp.swift in Sources */,
2730D61A2BDFEC4A00DB4FF6 /* UnwindSegueModifier.swift in Sources */,
2730D61A2BDFEC4A00DB4FF6 /* RegisterSegueModifier.swift in Sources */,
2730D6192BDFEC4A00DB4FF6 /* Screen.swift in Sources */,
2730D6182BDFEC4A00DB4FF6 /* RootView.swift in Sources */,
2730D6102BDFEBEA00DB4FF6 /* AccessibilityID.swift in Sources */,
Expand Down
35 changes: 31 additions & 4 deletions Navigation/Navigation/NavigationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ import SwiftUI

final class NavigationCoordinator<V: Hashable>: ObservableObject {
@Published var path = NavigationPath()
@Published var parent: NavigationCoordinator<V>?
@Published var modal: V?
var segues: [Identifier : Segue] = [:]

// MARK: - Stack Navigation

func push(_ value: V) {
path.append(value)
}

func pop() {
if !path.isEmpty {
path.removeLast()
Expand All @@ -39,23 +43,46 @@ final class NavigationCoordinator<V: Hashable>: ObservableObject {
}

func unwind(to identifier: Identifier, with value: Any? = nil) {
guard let segue = segues[identifier], path.count > segue.index else { return }
guard let segue = segues[identifier], path.count > segue.index, segue.type == .unwind else { return }

path.removeLast(path.count - segue.index)
segue.action?(value)
}

// MARK: - Modal Presentation

func present(_ value: V) {
modal = value
}

func dismiss(to identifier: Identifier? = nil, with value: Any? = nil) {
guard let identifier = identifier, let segue = parent?.segues[identifier],
segue.type == .dismiss else {
parent?.modal = nil
return
}

parent?.modal = nil
segue.action?(value)
}
}

extension NavigationCoordinator {
typealias Identifier = String

struct Segue {
let type: SegueType
let index: Int
let action: ((Any?) -> Void)?

enum SegueType {
case unwind
case dismiss
}
}

public func registerSegue(with identifier: Identifier, action: ((Any?) -> Void)? = nil) {
segues[identifier] = Segue(index: path.count, action: action)
public func registerSegue(_ type: Segue.SegueType, with identifier: Identifier, action: ((Any?) -> Void)? = nil) {
segues[identifier] = Segue(type: type, index: path.count, action: action)
}

private func removeInvalidSegues() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,26 @@

import SwiftUI

struct UnwindSegueModifier: ViewModifier {
struct RegisterSegueModifier: ViewModifier {
@EnvironmentObject var coordinator: NavigationCoordinator<Screen>

let type: NavigationCoordinator<Screen>.Segue.SegueType
let identifier: String
let action: ((Any?) -> Void)?

func body(content: Content) -> some View {
content.onAppear {
coordinator.registerSegue(with: identifier, action: action)
coordinator.registerSegue(type, with: identifier, action: action)
}
}
}

extension View {
func onUnwind(segue identifier: String, perform action: ((Any?) -> Void)? = nil) -> some View {
modifier(UnwindSegueModifier(identifier: identifier, action: action))
modifier(RegisterSegueModifier(type: .unwind, identifier: identifier, action: action))
}

func onDismiss(segue identifier: String, perform action: ((Any?) -> Void)? = nil) -> some View {
modifier(RegisterSegueModifier(type: .dismiss, identifier: identifier, action: action))
}
}
14 changes: 12 additions & 2 deletions Navigation/Navigation/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,24 @@
import SwiftUI

struct RootView: View {
@StateObject private var coordinator = NavigationCoordinator<Screen>()
@ObservedObject private var coordinator: NavigationCoordinator<Screen>
let root: Screen

internal init(_ root: Screen, withParent coordinator: NavigationCoordinator<Screen>? = nil) {
self.root = root
self.coordinator = NavigationCoordinator<Screen>()
self.coordinator.parent = coordinator
}

var body: some View {
NavigationStack(path: $coordinator.path) {
Screen.root.view
root.view
.navigationDestination(for: Screen.self) { screen in
screen.view
}
.sheet(item: $coordinator.modal) { screen in
RootView(screen, withParent: coordinator)
}
}
.environmentObject(coordinator)
}
Expand Down
10 changes: 6 additions & 4 deletions Navigation/Navigation/Screen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ enum Screen: Hashable {
case blueBook(text: String)
}

extension Screen: Identifiable {
var id: Int { self.hashValue }
}

extension Screen {
static var root: Self {
.orangeBook
}
// Dismiss segue identifiers
static let blueDismiss = "blueDismiss"

// Unwind segue identifiers

static let orangeBookSegue = "unwindToOrangeBook"
static let redBookSegue = "unwindToRedBook"
}
Expand Down
2 changes: 1 addition & 1 deletion Navigation/NavigationApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
struct NavigationApp: App {
var body: some Scene {
WindowGroup {
RootView()
RootView(.orangeBook)
}
}
}
2 changes: 2 additions & 0 deletions Navigation/Other/AccessibilityID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ enum AccessibilityID: String {
case popButton
case unwindButton
case popToRootButton
case dismissButton
case presentButton
}


28 changes: 21 additions & 7 deletions Navigation/Views/BlueBookView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ import SwiftUI

struct BlueBookView: View {
@EnvironmentObject var coordinator: NavigationCoordinator<Screen>
@State var dismissValue = ""
let text: String

var body: some View {
content
.navigationTitle("📘")
.onDismiss(segue: Screen.blueDismiss) { value in
guard let value = value as? String else { return }
dismissValue = value
}
}

var content: some View {
Expand All @@ -31,23 +36,32 @@ struct BlueBookView: View {
}

var title: some View {
Text(text).font(.largeTitle)
Text(text + dismissValue).font(.largeTitle)
.accessibility(identifier: .titleText)
}

var buttons: some View {
VStack(alignment: .leading, spacing: 10) {
Button(".pop()") {
coordinator.pop()
}.accessibility(identifier: .popButton)
Text("Stack Navigation:").font(.title)

Button(".pop()") { coordinator.pop() }
.accessibility(identifier: .popButton)

Button(".unwind(to: .redBookSegue, with: \"🔭\")") {
coordinator.unwind(to: Screen.redBookSegue, with: "🔭")
}.accessibility(identifier: .unwindButton)

Button(".popToRoot()") {
coordinator.popToRoot()
}.accessibility(identifier: .popToRootButton)
Button(".popToRoot()") { coordinator.popToRoot() }
.accessibility(identifier: .popToRootButton)

Text("Modal Presentation:").font(.title)

Button(".present(.orangeBook)") { coordinator.present(.orangeBook) }
.accessibility(identifier: .presentButton)

Button(".dismiss(to: .blueDismiss, with: \"🧸\")") {
coordinator.dismiss(to: Screen.blueDismiss, with: "🧸")
}.accessibility(identifier: .dismissButton)
}
.foregroundColor(.white)
.bold()
Expand Down
10 changes: 10 additions & 0 deletions Navigation/Views/GreenBookView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct GreenBookView: View {

var buttons: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Stack Navigation:").font(.title)

Button(".push(.blueBook(text: \"🖼️\"))") { coordinator.push(.blueBook(text: "🖼️")) }
.accessibility(identifier: .pushButton)

Expand All @@ -41,6 +43,14 @@ struct GreenBookView: View {

Button(".popToRoot()") { coordinator.popToRoot() }
.accessibility(identifier: .popToRootButton)

Text("Modal Presentation:").font(.title)

Button(".present(.orangeBook)") { coordinator.present(.orangeBook) }
.accessibility(identifier: .presentButton)

Button(".dismiss()") { coordinator.dismiss() }
.accessibility(identifier: .dismissButton)
}
.foregroundColor(.white)
.bold()
Expand Down
22 changes: 17 additions & 5 deletions Navigation/Views/OrangeBookView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,24 @@ struct OrangeBookView: View {
}
}
}

var buttons: some View {
Button(".push(.redBook)") { coordinator.push(.redBook) }
.accessibility(identifier: .pushButton)
.foregroundColor(.white)
.bold()
VStack(alignment: .leading, spacing: 10) {
Text("Stack Navigation:").font(.title)

Button(".push(.redBook)") { coordinator.push(.redBook) }
.accessibility(identifier: .pushButton)

Text("Modal Presentation:").font(.title)

Button(".present(.orangeBook)") { coordinator.present(.orangeBook) }
.accessibility(identifier: .presentButton)

Button(".dismiss()") { coordinator.dismiss() }
.accessibility(identifier: .dismissButton)
}
.foregroundColor(.white)
.bold()
}

var stack: some View {
Expand Down
16 changes: 13 additions & 3 deletions Navigation/Views/RedBookView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,27 @@ struct RedBookView: View {

var buttons: some View {
VStack(alignment: .leading, spacing: 10) {
Button(".push(.greenBook)") {coordinator.push(.greenBook)}
Text("Stack Navigation:").font(.title)

Button(".push(.greenBook)") { coordinator.push(.greenBook) }
.accessibility(identifier: .pushButton)

Button(".pop()") { coordinator.pop()}
Button(".pop()") { coordinator.pop() }
.accessibility(identifier: .popButton)

Button(".unwind(to: .orangeBookSegue)") { coordinator.unwind(to: Screen.orangeBookSegue)}
.accessibility(identifier: .unwindButton)

Button(".popToRoot()") {coordinator.popToRoot()}
Button(".popToRoot()") { coordinator.popToRoot() }
.accessibility(identifier: .popToRootButton)

Text("Modal Presentation:").font(.title)

Button(".present(.orangeBook)") { coordinator.present(.orangeBook) }
.accessibility(identifier: .presentButton)

Button(".dismiss()") { coordinator.dismiss() }
.accessibility(identifier: .dismissButton)
}
.foregroundColor(.white)
.bold()
Expand Down
Loading

0 comments on commit 2cb1090

Please sign in to comment.