Skip to content

Commit

Permalink
Merge pull request #9 from flopshot/update-screen-view-and-demo
Browse files Browse the repository at this point in the history
update ScreenView protocol and example app
  • Loading branch information
flopshot authored Mar 2, 2022
2 parents 9b38bdc + 9971fb8 commit c171813
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 70 deletions.
21 changes: 10 additions & 11 deletions ExampleApp/Shared/DiveScreens.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import Navigator

struct GreenScreen: ScreenView {

@EnvironmentObject var navigator: Navigator<Screens, MyViewFactory>
@EnvironmentObject var navigator: Navigator<ScreenID, AppViewFactory>
@State var showNextScreen: Bool = false
var currentScreen: Screens
var screenId: ScreenID

var body: some View {
Color.green
Expand Down Expand Up @@ -51,9 +51,9 @@ struct GreenScreen: ScreenView {

struct BlueScreen: ScreenView {

@EnvironmentObject var navigator: Navigator<Screens, MyViewFactory>
@EnvironmentObject var navigator: Navigator<ScreenID, AppViewFactory>
@State var showNextScreen: Bool = false
var currentScreen: Screens
var screenId: ScreenID

var body: some View {

Expand Down Expand Up @@ -95,14 +95,13 @@ struct BlueScreen: ScreenView {
struct BlueScreen_Preview: PreviewProvider {
static var previews: some View {

let blueScreen = Screens.blueScreen()
let blueScreen = ScreenID.blueScreen()

BlueScreen(
currentScreen: blueScreen)
BlueScreen(screenId: blueScreen)
.environmentObject(
Navigator(
rootScreen: blueScreen,
viewFactory: MyViewFactory()
viewFactory: AppViewFactory()
)
)
}
Expand All @@ -113,13 +112,13 @@ struct BlueScreen_Preview: PreviewProvider {
struct GreenScreen_Preview: PreviewProvider {
static var previews: some View {

let greenScreen = Screens.greenScreen()
let greenScreen = ScreenID.greenScreen()

GreenScreen(currentScreen: greenScreen)
GreenScreen(screenId: greenScreen)
.environmentObject(
Navigator(
rootScreen: greenScreen,
viewFactory: MyViewFactory()
viewFactory: AppViewFactory()
)
)
}
Expand Down
4 changes: 2 additions & 2 deletions ExampleApp/Shared/ExampleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ struct NavigatorDemoApp: App {
var body: some Scene {
WindowGroup {

let navigator = Navigator(rootScreen: Screens.rootScreen, viewFactory: MyViewFactory())
let navigator = Navigator(rootScreen: ScreenID.rootScreen, viewFactory: AppViewFactory())

NavigationView.with(navigator) {
RootScreen(currentScreen: .rootScreen)
RootScreen(screenId: .rootScreen)
}
.accentColor(.black)

Expand Down
16 changes: 8 additions & 8 deletions ExampleApp/Shared/MyViewFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ import Foundation
import Navigator
import SwiftUI

class MyViewFactory: ViewFactory {
class AppViewFactory: ViewFactory {

@ViewBuilder
func makeView(screenType: ScreenWrapper<Screens>) -> some View {
func makeView(screenType: ScreenWrapper<ScreenID>) -> some View {
switch screenType {
case .screenWrapper(let myScreen):
switch myScreen {
case .screenWrapper(let screenId):
switch screenId {
case .greenScreen:
GreenScreen(currentScreen: myScreen!)
GreenScreen(screenId: screenId!)
case .rootScreen:
RootScreen(currentScreen: myScreen!)
RootScreen(screenId: screenId!)
case .blueScreen:
BlueScreen(currentScreen: myScreen!)
BlueScreen(screenId: screenId!)
case .none:
EmptyView()
}
}
}
}

enum Screens: Hashable {
enum ScreenID: Hashable {
case rootScreen
case blueScreen(id: UUID = UUID())
case greenScreen(id: UUID = UUID())
Expand Down
10 changes: 5 additions & 5 deletions ExampleApp/Shared/RootScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import Navigator

struct RootScreen: ScreenView {

@EnvironmentObject var navigator: Navigator<Screens, MyViewFactory>
@EnvironmentObject var navigator: Navigator<ScreenID, AppViewFactory>
@State var showNextScreen: Bool = false
var currentScreen: Screens
var screenId: ScreenID

var body: some View {
List {
Expand All @@ -34,11 +34,11 @@ struct RootScreen: ScreenView {
struct RootView_Preview: PreviewProvider {
static var previews: some View {
NavigationView {
RootScreen(currentScreen: .rootScreen)
RootScreen(screenId: .rootScreen)
.environmentObject(
Navigator(
rootScreen: Screens.rootScreen,
viewFactory: MyViewFactory()
rootScreen: ScreenID.rootScreen,
viewFactory: AppViewFactory()
)
)
}
Expand Down
12 changes: 6 additions & 6 deletions ExampleApp/Shared/Util.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ public func delay(_ seconds: Double) async {
try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
}

func randomScreen() -> Screens {
func randomScreen() -> ScreenID {
let randomFlag = Int.random(in: 0...1)
switch randomFlag {
case 0: return Screens.greenScreen()
case 1: return Screens.blueScreen()
case 0: return ScreenID.greenScreen()
case 1: return ScreenID.blueScreen()
default: fatalError()
}
}

extension Navigator where ScreenIdentifer == Screens {
extension Navigator where ScreenIdentifer == ScreenID {
func popToFirstGreenScreenOrRoot(id: UUID) {
if let greenScreen: Screens = navStack.keys.elements.first(where: {
if case Screens.greenScreen(let detailID) = $0 { return detailID == id }
if let greenScreen: ScreenID = navStack.keys.elements.first(where: {
if case ScreenID.greenScreen(let detailID) = $0 { return detailID == id }
return false
}) {
navStack[greenScreen]!.send(false)
Expand Down
61 changes: 31 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ navigate to other Views programatically in your app.

```swift
struct DetailScreen: ScreenView {
@EnvironmentObject var navigator: Navigator<Screens, MyViewFactory>
@EnvironmentObject var navigator: Navigator<ScreenID, AppViewFactory>
@State var showNextScreen: Bool = false
var currentScreen: Screens
var screenId: ScreenID

var body: some View {
Button("Next") {
Expand All @@ -47,28 +47,28 @@ along with any needed values. Ensure that it conforms
to `Hashable`

```swift
enum Screens: Hashable {
enum ScreenID: Hashable {
case rootScreen
case detailScreen(detailID: String)
}
```

Then create a class that conforms to `ViewFactory` and
implement `makeView(screenType:)` so that the given `Screens`
implement `makeView(screenType:)` so that the given `ScreenID`
enum returns the associated `View`.

```swift
class MyViewFactory: ViewFactory {
class AppViewFactory: ViewFactory {

@ViewBuilder
func makeView(screenType: ScreenWrapper<Screens>) -> some View {
func makeView(screenType: ScreenWrapper<ScreenID>) -> some View {
switch screenType {
case .screenWrapper(let myScreen):
switch myScreen {
case .screenWrapper(let screenId):
switch screenId {
case .rootScreen:
RootScreen()
case .detailScreen:
DetailScreen(currentScreen: myScreen!)
DetailScreen(currentScreen: screenId!)
case .none:
// EmptyView is fine in the exhaustive case
// as this is just a placeholder until the
Expand All @@ -84,16 +84,17 @@ class MyViewFactory: ViewFactory {
### 2. Initialize Library

In the `@main` app delcaration, where your root view
is the top level view in the navigation stack, inject
into the environment the necessary `Navigator` classes
and add the `NavigatorViewBinding` view modifier
is the top level view in the navigation stack (or wherever
you'd like to place a `NavigationView` with programatic navigation)
initialize the `NavigationView` using the `with(_:)` method to inject
your instance of `Navigator`

```swift
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
NavigationView.with(Navigator(rootScreen: Screens.rootScreen, viewFactory: MyViewFactory()) {
NavigationView.with(Navigator(rootScreen: ScreenID.rootScreen, viewFactory: AppViewFactory()) {
RootScreen()
}
}
Expand All @@ -108,9 +109,9 @@ and apply the `bindNavigation(_:binding:)` view modifier

```swift
struct RootScreen: ScreenView {
@EnvironmentObject var navigator: Navigator<Screens, MyViewFactory>
@EnvironmentObject var navigator: Navigator<ScreenID, AppViewFactory>
@State var showNextScreen: Bool = false
var currentScreen = .rootScreen
var screenId = .rootScreen

var body: some View {
List {
Expand All @@ -126,9 +127,9 @@ struct RootScreen: ScreenView {

```swift
struct DetailScreen: ScreenView {
@EnvironmentObject var navigator: Navigator<Screens, MyViewFactory>
@EnvironmentObject var navigator: Navigator<ScreenID, AppViewFactory>
@State var showNextScreen: Bool = false
var currentScreen: Screens
var screenId: ScreenID

var body: some View {
VStack {
Expand All @@ -149,14 +150,14 @@ struct DetailScreen: ScreenView {
```

Note that except for the `RootScreen` all other screens
should have an `id` in their `Screens` enum value in order
should have an `id` in their `ScreenID` enum value in order
to differentiate same screens in the navigation stack. This
can be in the form of a business logic id `detailID` or some
deafult id that has no business logic but merely used to id
the screen

```swift
enum Screens: Hashable {
enum ScreenID: Hashable {
// ...
case anotherScreen(id: UUID = UUID())
}
Expand All @@ -170,9 +171,9 @@ View navigation

```swift
struct RootScreen: ScreenView {
@EnvironmentObject var navigator: Navigator<Screens, MyViewFactory>
@EnvironmentObject var navigator: Navigator<ScreenID, AppViewFactory>
@State var showNextScreen: Bool = false
var currentScreen = .rootScreen
var screenId = .rootScreen

var body: some View {
List {
Expand All @@ -183,7 +184,7 @@ struct RootScreen: ScreenView {

// This can also be called at an abstraction layer like a ViewModel

navigator.navigate(to: Screens.detailScreen(detailID: "detail-123"))
navigator.navigate(to: ScreenID.detailScreen(detailID: "detail-123"))
}
}
.navigationTitle("Root Screen")
Expand All @@ -194,9 +195,9 @@ struct RootScreen: ScreenView {

```swift
struct DetailScreen: ScreenView {
@EnvironmentObject var navigator: Navigator<Screens, MyViewFactory>
@EnvironmentObject var navigator: Navigator<ScreenID, AppViewFactory>
@State var showNextScreen: Bool = false
var currentScreen: Screens
var screenId: ScreenID

var body: some View {
VStack {
Expand All @@ -220,7 +221,7 @@ struct DetailScreen: ScreenView {

## Advanced
As stated previously, the `Navigator` class has the property `navStack` which keeps
a stack (OrderedDictionary) of the `ScreenView` associated `Screens` enums that are
a stack (OrderedDictionary) of the `ScreenView` associated `ScreenID` enum values that are
currently active in the NavigationView. Clients can use it to execte custom navigation logic.
```swift

Expand All @@ -233,15 +234,15 @@ currently active in the NavigationView. Clients can use it to execte custom navi
As an example, we can use it create an extention funtion on `Navigator` called `popToDetailWithSpecificIdOrRoot(id: String)`

```swift
extension Navigator where ScreenIdentifer == Screens {
extension Navigator where ScreenIdentifer == ScreenID {
func popToDetailWithSpecificIdOrRoot(id: String) {

// Since all screens that have been pushed onto the NavigationView
// are stored in the navStack as keys, we can simply search through
// them for the specific detail screen with the called id

if let detailScreen: Screens = navStack.keys.elements.first(where: {
if case Screens.detailScreen(let detailID) = $0 {
if let detailScreen: ScreenID = navStack.keys.elements.first(where: {
if case ScreenID.detailScreen(let detailID) = $0 {
return detailID == id
}
return false
Expand Down Expand Up @@ -282,12 +283,12 @@ struct MyApp: App {

// Keep reference to the navigator, either as a local property or in an abstraction
// layer, such as an AppCoordinator
let navigator = Navigator(rootScreen: Screens.rootScreen, viewFactory: MyViewFactory())
let navigator = Navigator(rootScreen: ScreenID.rootScreen, viewFactory: AppViewFactory())

var body: some Scene {
WindowGroup {
NavigationView.with(navigator) {
RootScreen(currentScreen: .rootScreen)
RootScreen()
}
.task {
// mimic a system event, such as a notification
Expand Down
12 changes: 6 additions & 6 deletions Sources/Navigator/NavigationBindings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import SwiftUI
/// navigate between ScreenViews of their app, dynamically.
public struct NavigationBinding<ViewFactoryImpl: ViewFactory, ScreenIdentifer: Hashable>: ViewModifier {
let navigation: Navigator<ScreenIdentifer, ViewFactoryImpl>
let currentScreen: ScreenIdentifer
let screenId: ScreenIdentifer
var showNextScreenBinding: Binding<Bool>

public init(
navigation: Navigator<ScreenIdentifer, ViewFactoryImpl>,
currentScreen: ScreenIdentifer,
screenId: ScreenIdentifer,
showNextScreenBinding: Binding<Bool>
) {
self.navigation = navigation
self.currentScreen = currentScreen
self.screenId = screenId
self.showNextScreenBinding = showNextScreenBinding
}

Expand All @@ -32,7 +32,7 @@ public struct NavigationBinding<ViewFactoryImpl: ViewFactory, ScreenIdentifer: H
// This is the hidden NavigationLink of every bound ScreenView
// which delegates navigation to the native SwiftUI View navigation
NavigationLink(
destination: navigation.nextView(from: currentScreen),
destination: navigation.nextView(from: screenId),
isActive: showNextScreenBinding
) {
EmptyView()
Expand All @@ -41,15 +41,15 @@ public struct NavigationBinding<ViewFactoryImpl: ViewFactory, ScreenIdentifer: H
// This relays the Navigation publisher associated with the underlying
// ScreenView and updates the View's showNextScreenBinding to
// either push or pop Views
navigation.navStack.first(where: { $0.key == currentScreen })!.value,
navigation.navStack.first(where: { $0.key == screenId })!.value,
perform: { shouldShowNextScreen in
showNextScreenBinding.wrappedValue = shouldShowNextScreen
}
)
.onChange(of: showNextScreenBinding.wrappedValue, perform: { shouldShow in
// If the user swipes to dismiss or clicks the NavigationBar back
// button, this callback will update the Nav State in Navigation
if !shouldShow { navigation.onDismiss(currentScreen) }
if !shouldShow { navigation.onDismiss(screenId) }
})
}
}
Loading

0 comments on commit c171813

Please sign in to comment.