Skip to content

PathBuilder

ohitsdaniel edited this page Apr 29, 2021 · 5 revisions

PathBuilder

PathBuilders define how a navigation path is built into a view hierarchy

public protocol PathBuilder 

Default Implementations

beforeBuild(perform:)

Performs an action before this PathBuilder is built.

func beforeBuild(perform action: @escaping () -> Void) -> _PathBuilder<Content> 

eraseCircularNavigationPath()

Erases circular navigation paths

public func eraseCircularNavigationPath() -> AnyPathBuilder 

NavigationTrees define a PathBuilder<Content: View> via their builder computed variable. The Screen PathBuilder adds NavigationNode<ScreenView, Successor>s to the NavigationTree.

One the one hand, this makes sure that all valid navigation paths are known at build time. On the other hand, this leads to problems if the NavigationTree contains a circular path.

The following NavigationTree leads to a circular path:

struct HomeScreen {
  let presentationStyle = ScreenPresentationStyle.push

  struct Builder: NavigationTree {
    var builder: _PathBuilder<
      NavigationNode<HomeView,
          NavigationNode<HomeView, ...> // <- Circular Type
        >
    > {
      Screen(
        HomeScreen.self,
        content: HomeView.init,
        nesting: {
          HomeScreen.Builder()
        }
      )
    }
  }
}

Whenever a Screen Builder is contained multiple times in a navigation tree, the Screen and its successors are contained recursively in the NavigationTrees content type.

Unfortunately, the compiler is not able to resolve recursive, generic types. To solve this, we can erase PathBuilders that lead to circular paths.

struct HomeScreen {
  let presentationStyle = ScreenPresentationStyle.push

  struct Builder: NavigationTree {
    var builder: _PathBuilder<
      NavigationNode<HomeView, AnyView> // <- No longer circular
    > {
      Screen(
        HomeScreen.self,
        content: HomeView.init,
        nesting: {
          HomeScreen
            .Builder()
            .eraseCircularNavigationPath()
        }
      )
    }
  }
}

onDismiss(perform:)

onDismiss allows to perform an action whenever the AnyScreen object built by the wrapped PathBuilder changes

func onDismiss(
    perform: @escaping (AnyScreen) -> Void
  ) -> _PathBuilder<OnDismissView<Content>> 

onDismiss keeps track of the last built AnyScreen and performs the defined action whenever a screen is no longer built. If your PathBuilder is wrapped in a conditional path builder, make sure to attach onDismiss to the outer-most conditional PathBuilder.

Example

PathBuilders.anyOf(
  DetailScreen.Builder(),
  SettingsScreen.Builder()
)
.onDismiss { (screen: AnyScreen) in
  print("Dismissed \(screen)")
}

Parameters

  • perform: Block to perform when the wrapped PathBuilder builds a new AnyScreen object. Called with the previously built AnyScreen instance.

onDismiss(perform:)

onDismiss allows to perform an action whenever aScreen is dismissed

func onDismiss<Dismissed: Screen>(
    perform: @escaping (Dismissed) -> Void
  ) -> _PathBuilder<OnDismissView<Content>> 

onDismiss keeps track of the last built Screen and performs the defined action whenever a screen is no longer built.

Example

 PathBuilders.if(
   screen: { (screen: DetailScreen) in
     PathBuilders.screen(
       DetailScreen.self,
       content: {
         DetailView(mainStore.detailStore)
       },
       nesting: SettingsScreen.Builder().onDismiss(of: SettingsScreen.self) {
         // only called if DetailScreen is contained in the current navigation path
         print("Dismissed settings screen")
       }
     )
     .beforeBuild {
       mainStore.selectedDetail = screen.detailID
     }
   }
 )
 .onDismiss { (screen: DetailScreen) in
   mainStore.selectedDetail = nil
 }

Parameters

  • perform: Block to perform when the wrapped PathBuilder no longer builds the defined Dismissed: Screen type. Called with the previously built Dismissed instance.

onDismiss(of:perform:)

onDismiss allows to perform an action whenever aScreen is dismissed

func onDismiss<Dismissed: Screen>(
    of: Dismissed.Type,
    perform: @escaping () -> Void
  ) -> _PathBuilder<OnDismissView<Content>> 

onDismiss keeps track of the last built Screen and performs the defined action whenever a screen is no longer built.

Example

 PathBuilders.if(
   screen: { (screen: DetailScreen) in
     PathBuilders.screen(
       DetailScreen.self,
       content: {
         DetailView(mainStore.detailStore)
       },
       nesting: SettingsScreen.Builder.onDismiss(of: SettingsScreen.self) {
         // only called if DetailScreen is contained in the current navigation path
         print("Dismissed settings screen")
       }
     )
     .beforeBuild {
       mainStore.selectedDetail = screen.detailID
     }
   }
 )
 .onDismiss(of: DetailScreen.self) {
   mainStore.selectedDetail = nil
 }

Parameters

  • of: defines the Dismissed: Screen type
  • perform: Block to perform when the wrapped PathBuilder no longer builds the defined Dismissed: Screen type.

build(pathElement:)

public func build<S: Screen>(pathElement: S) -> Content? 

onDismiss(send:into:)

onDismiss allows to perform an action whenever the AnyScreen object built by the wrapped PathBuilder changes

public func onDismiss<State: Equatable, Action: Equatable>(
    send action: @escaping (AnyScreen) -> Action,
    into store: Store<State, Action>
  ) -> _PathBuilder<OnDismissView<Content>> 

onDismiss keeps track of the last built AnyScreen and performs the defined action whenever a screen is no longer built. If your PathBuilder is wrapped in a conditional path builder, make sure to attach onDismiss to the outer-most conditional PathBuilder.

Example

PathBuilders.anyOf(
  DetailScreen.Builder,
  SettingsScreen.Builder
)
.onDismiss(
  send: { screen in MainAction.dismissedScreen(screen) },
  into: mainStore
)

Parameters

  • action: Action sent on dismiss.
  • store: Store the action is sent into.

onDismiss(send:into:)

onDismiss allows to perform an action whenever aScreen is dismissed

public func onDismiss<Dismissed: Screen, State: Equatable, Action: Equatable>(
    send action: @escaping (Dismissed) -> Action,
    into store: Store<State, Action>
  ) -> _PathBuilder<OnDismissView<Content>> 

onDismiss keeps track of the last built Screen and performs the defined action whenever a screen is no longer built.

Example

 PathBuilders.if(
   screen: { (screen: DetailScreen) in
     PathBuilders.screen(
       DetailScreen.self,
       content: {
         DetailView(mainStore.detailStore)
       },
       nesting: SettingsScreen.Builder().onDismiss(of: SettingsScreen.self) {
         // only called if DetailScreen is contained in the current navigation path
         print("Dismissed settings screen")
       }
     )
     .beforeBuild {
       mainStore.selectedDetail = screen.detailID
     }
   }
 )
 .onDismiss(
   send: { (screen: DetailScreen) in MainAction.dismissDetail(id: screen.id) },
   into: mainStore
 )

Parameters

  • action: Action sent on dismiss.
  • store: Store the action is sent into.

onDismiss(of:send:into:)

onDismiss allows to perform an action whenever aScreen is dismissed

public func onDismiss<Dismissed: Screen, State: Equatable, Action: Equatable>(
    of screen: Dismissed.Type,
    send action: Action,
    into store: Store<State, Action>
  ) -> _PathBuilder<OnDismissView<Content>> 

onDismiss keeps track of the last built Screen and performs the defined action whenever a screen is no longer built.

Example

 PathBuilders.if(
   screen: { (screen: DetailScreen) in
     PathBuilders.screen(
       DetailScreen.self,
       content: {
         DetailView(mainStore.detailStore)
       },
       nesting: SettingsScreen.Builder().onDismiss(of: SettingsScreen.self) {
         // only called if DetailScreen is contained in the current navigation path
         print("Dismissed settings screen")
       }
     )
     .beforeBuild {
       mainStore.selectedDetail = screen.detailID
     }
   }
 )
 .onDismiss(
   of: DetailScreen.self,
   send: MainAction.dismissDetail,
   into: mainStore
 )

Parameters

  • of: defines the Dismissed: Screen type.
  • action: Action sent on dismiss.
  • store: Store the action is sent into.

Requirements

Content

associatedtype Content: View

build(pathElement:​)

func build(pathElement: AnyScreen) -> Content?
Clone this wiki locally