Powerful navigation in the Composable Architecture via the coordinator pattern

Overview

TCACoordinators

The coordinator pattern in the Composable Architecture

TCACoordinators brings a flexible approach to navigation in SwiftUI using the Composable Architecture (TCA). It allows you to manage complex navigation and presentation flows with a single piece of state, hoisted into a high-level coordinator view. Using this pattern, you can write isolated screen views that have zero knowledge of their context within the navigation flow of an app. It achieves this by combining existing tools in TCA such as Reducer.forEach, Reducer.pullback and SwitchStore with a novel approach to handling navigation in SwiftUI.

Usage Example

Step 1 - Create a screen reducer

First, identify all possible screens that are part of the particular navigation flow you're modelling. The goal will be to combine their reducers into a single reducer - one that can drive the behaviour of any of those screens. Both the state and action types will be the sum of the individual screens' state and action types:

enum ScreenState: Equatable {
  case numbersList(NumbersListState)
  case numberDetail(NumberDetailState)
}

enum ScreenAction {
  case numbersList(NumbersListAction)
  case numberDetail(NumberDetailAction)
}

And the screen reducer will combine each individual screens' reducers into one:

let screenReducer = Reducer<ScreenState, ScreenAction, Void>.combine(
  numbersListReducer
    .pullback(
      state: /ScreenState.numbersList,
      action: /ScreenAction.numbersList,
      environment: { _ in }
    ),
  numberDetailReducer
    .pullback(
      state: /ScreenState.numberDetail,
      action: /ScreenAction.numberDetail,
      environment: { _ in }
    )
)

Step 2 - Create a coordinator reducer

The coordinator will manage multiple screens in a navigation flow. Its state should include an array of ScreenStates, representing the navigation stack: i.e. appending a new screen state to this array will cause the corresponding screen to be pushed.

struct CoordinatorState: Equatable, IndexedScreenCoordinatorState {
  var screens: [ScreenState]
}

The coordinator's action should include two special cases. The first allows screen actions to be dispatched to the correct screen in the stack. The second allows the screens array to be updated automatically when a user taps back:

enum CoordinatorAction: IndexedScreenCoordinatorAction {
  case screenAction(Int, ScreenAction)
  case updateScreens([ScreenState])
}

The coordinator's reducer uses forEachIndexedScreen to apply the screenReducer to each screen in the screens array, and combines that with a second reducer that defines when new screens should be pushed or popped:

let coordinatorReducer: Reducer<CoordinatorState, CoordinatorAction, Void> = screenReducer
  .forEachIndexedScreen(environment: { _ in })
  .updateScreensOnInteraction()
  .combined(
    with: Reducer { state, action, environment in
      switch action {
      case .screenAction(_, .numbersList(.numberSelected(let number))):
        state.push(.numberDetail(.init(number: number)))

      case .screenAction(_, .numberDetail(.goBackTapped)):
        state.pop()

      case .screenAction(_, .numberDetail(.showDouble(let number))):
        state.push(.numberDetail(.init(number: number * 2)))

      default:
        break
      }
      return .none
    }
  )
  .cancelEffectsOnDismiss()

Note the call to cancelEffectsOnDismiss() at the end. It's often desirable to cancel any in-flight effects initiated by a particular screen when that screen is popped or dismissed. This would normally require a fair amount of boilerplate, but can now be achieved by simply chaining a call to cancelEffectsOnDismiss() on the reducer.

The call to updateScreensOnInteraction() ensures the screens array is updated whenever the user swipes back or taps the back button.

Step 3 - Create a coordinator view

With that in place, a CoordinatorView can be created. It will use a NavigationStore, which translates the array of screens into a nested list of views with invisible NavigationLinks. The NavigationStore takes a closure that can create the view for any screen in the navigation flow. A SwitchStore is the natural way to achieve that, with a CaseLet for each of the possible screens:

struct CoordinatorView: View {
  let store: Store<CoordinatorState, CoordinatorAction>

  var body: some View {
    NavigationStore(store: store) { scopedStore in
      SwitchStore(scopedStore) {
        CaseLet(
          state: /ScreenState.numbersList,
          action: ScreenAction.numbersList,
          then: NumbersListView.init
        )
        CaseLet(
          state: /ScreenState.numberDetail,
          action: ScreenAction.numberDetail,
          then: NumberDetailView.init
        )
      }
    }
  }
}

Advantages

This allows navigation to be managed with a single piece of state. As well as mutating the array directly, there are some useful protocol extensions to allow common interactions such as state.push(newScreen), state.pop(), state.popToRoot(), or even state.popTo(/ScreenState.numbersList). If the user taps or swipes back, or uses the long press gesture to go further back, the navigation state will automatically get updated to reflect the change.

This approach is flexible: if the flow of screens needs to change, the change can be made easily in one place. The screen views themselves no longer need to have any knowledge of any other screens in the navigation flow - they can simply send an action and leave the coordinator to decide whether a new view should be pushed or presented - which makes it easy to re-use them in different contexts.

Child Coordinators

The coordinator is just like any other UI unit in the Composable Architecture - comprising a View and a Reducer with State and Action types. This means they can be composed in all the normal ways SwiftUI and TCA allow. You can present a coordinator, add it to a TabView, even push a child coordinator onto the navigation stack of a parent coordinator. Note that NavigationStore does not wrap its content in a NavigationView - that way, multiple coordinators, each with its own NavigationStore, can be nested within a single NavigationView.

Presentation

The example given was a navigation flow, but it can be changed to a presentation flow by just changing the NavigationStore to a PresentationStore. Each new screen would then be presented rather than pushed.

Identifying Screens

In the example given, the coordinator's state conformed to IndexedScreenCoordinatorState and action to IndexedScreenCoordinatorAction. That means that screens were identified by their index in the screens array. This is safe because the index is stable for standard navigation updates - e.g. pushing and popping do not affect the indexes of existing screens. However, if you prefer to use Identifiable screens, you can manage the screens as an IdentifiedArray instead. You can then conform the state to IdentifiedScreenCoordinatorState and action to IdentifiedScreenCoordinatorAction, to gain the same terse API as the example above. There are also explicit versions of the APIs available, if you prefer not to conform to any protocols, e.g. if you wish to name properties and cases differently.

How does it work?

This library uses FlowStacks for hoisting navigation state out of individual screens. This blog post explains how that is achieved. FlowStacks can also be used in SwiftUI projects that do not use the Composable Architecture.

Limitations

SwiftUI does not currently support all possible mutations of the screens array. It does not allow more than one screen to be pushed, presented or dismissed in a single update. It's possible to pop any number of screens in one update for navigation flows but not for presentation flows. Hopefully, these limitations are temporary.

Comments
  • Previous State values being restored when transitioning back from detail screen

    Previous State values being restored when transitioning back from detail screen

    Not sure the best way to describe this issue, so I am just going to describe what is happening in my app.

    I have a screen to display a list of favorites that are stored in CoreData. From this list you can tap on an item to go to its detail page, from there, you can unfavorite the item. When you then go back to the list view, it updates the list from the onAppear instance method. The list correctly updates with that item being removed from favorites. But then roughly 0.5 later, the list updates again, this time with the old favorited item still in the list. If you then refresh the screen, or even put a 0.5 delay after onAppear is called, the correct list is shown, without the old favorited item.

    The weird part is though, when the second (unintended) update to the screen happens. The reducer is not being called again, and even the items in the state object didSet is not being called again. So it's almost seems like the previous state is just being restored by TCACoordinators.

    I've concluded this is not a CoreData issue by using other methods such as a singleton, or user defaults to save the values. I also don't believe this to be a regular TCA issue, as I cannot reproduce when using just regular NavigationLinks.

    Since it's a bit of code to show here, I made a small sample project demonstrating the issue that is 100% reproducible. CoordBug.zip

    Screen recording of my sample app I made demonstrating this. The label should be "changed from detail" but reverts back to "not set". IMB_OKTG2K

    opened by tyler927 7
  • How to cancel in-flight effect when we replace stack?

    How to cancel in-flight effect when we replace stack?

    Hi,

    We're replacing route stack in our app, and we'd like to know if there's an efficient way to cancel all in-flight effects in the previous route stack.

    Example Coordinator:

    let coordinatorReducer = Reducer { state, action, environment in
            case let .modeChanged(mode):
                switch mode {
                case .one:
                    state.routes = [.root(.one(.init()), embedInNavigationView: true)]
                case .two:
                    state.routes = [.root(.two(.init()), embedInNavigationView: true)]
                default:
                    break
                }
    }
    

    From the documentation it can cancel in-flight effect when that screen is popped or dismissed https://github.com/johnpatrickmorgan/TCACoordinators#cancellation-of-in-flight-effects-on-dismiss, however replacing route stack means we need to cancel all in-flight effects inside the route stack at once

    e.g. last route has pushed 3 screens (A -> B -> C) and replacing it to something new (D)

    [A -> B -> C] -> [D]
    

    How should I cancel the in-flight effect(s) of screen A, B and C?

    Best, Joseph

    opened by josephktcheung 6
  • 10th CaseLet setup got problem

    10th CaseLet setup got problem

    When I wanted to add the 10th CaseLet in coordinator view, the compiler threw errors: Cannot convert value of type '() -> TupleView<(CaseLet<ScreenState, ScreenAction,

    I thought this is because we can't have 10 subviews in one container, but after using Group the problems still exists. Any idea to solve this issue?

    opened by lmw4051 6
  • Does TCARouter need to have exactly one path through the flow?

    Does TCARouter need to have exactly one path through the flow?

    We've run into an odd issue. We have a flow that has two paths:

    A - B - C - D - E - F - G vs. A - B - C - D - H - F

    In the forward direction this works fine until we get to a screen after the separation. On that screen, as soon as we have any input to the reducer, our routes are set back to the previous value and we actually pop back a screen. This seems to be due to a recalculation of the root flow's body, which uses TCARouter with SwitchStore and CaseLets. Since there are more cases than presented screens, would that mismatch cause navigation to reset to a common point? We're also using the approach you outlined in the [shared state proof-of-concept], so we've separated our screens and routes using that approach.

    I can share whatever code you may need.

    opened by jshier 6
  • Add ability to automatically update parent state when child state changes

    Add ability to automatically update parent state when child state changes

    Sometimes, when I present a child view via navigation, I'd like to automatically have changes in that view be applied to a particular case/key path in the view that presented it. I'm thinking something like this:

    coordinatorReducer
        .onChange(
            of: /ScreenState.settings,
            updateAll: /ScreenState.home .. \HomeView.State.settings // imaginary syntax
        )
    

    Read this as "when a presented settings screen has any state change (rather than sending a special change action), update the settings portion of any home screens that happen to be presented." Could also have a version with updateFirst, updateAtIndex(_:), or others that might be useful.

    It doesn't seem possible to rewrite this with the library as-is, since screenReducer operates on one screen's changes, and forEach(Indexed|Identified)Route converts it to a reducer that operates on changes to the whole nav stack at once.

    opened by ZevEisenberg 6
  • TCARouter (and all children) re-render too frequently

    TCARouter (and all children) re-render too frequently

    Am experiencing some strange SwiftUI bugs involving a view that is inside of a TCACoordinator and it seems like subview are re-rendering whenever any piece of state in the entire routes array changes. I believe the issue stems from this piece of code:

      public var body: some View {
        WithViewStore(store) { viewStore in <---------
          Router(
            viewStore.binding(
              get: routes,
              send: updateRoutes
            ),
            buildView: { screen, index in
              let id = identifier(screen, index)
              screenContent(scopedStore(id: id, screen: screen))
            }
          )
        }
      }
    

    Since there is no scoping here in the WithViewStore, any change at all to the entire RoutesState causing the full View hierarchy to re-render. I'm trying to play around with different forms of removeDuplicates but am having trouble getting things to work. Will keep you posted but would love to know if you are aware of this issue.

    Thanks again!

    opened by dannyhertz 5
  • Data Flow through TCACoordinators?

    Data Flow through TCACoordinators?

    I'm having some issues coming up with a way to send data from my nested views back to my Coordinator state, when building a multi-step form.

    I've created a repository with a sample SwiftPM application, with a three step form, and a submission page. Currently, they don't have any means of communication with each other.

    In this example codebase, I'd like to be able to persist the values in Step 1, Step 2, and Step 3, so that the user can go back and forth between each page without losing any data, but I can't find any hook for when the current page state is updated. The updateRoutes action doesn't get called when I'd expect it to, only seemingly when the view is dismissed, not presented on top of.

    Is there something I'm missing with this? It feels like I should be able to keep Step 1, 2, and 3 state in my Coordinator state (or even a parent state), so that next time the user navigates from Step 1 -> Step 2, I can call:

    state.routes.push(.step2(state.step2State))
    

    and have that view pre-populated with whatever was entered from when they were last on the Step 2 page.

    Any help here would be massively appreciated! Thanks!

    opened by rhysm94 4
  • How can actions been passed from a root reducer through to a child reducer?

    How can actions been passed from a root reducer through to a child reducer?

    I'm facing an issue where I have an action that I wish to pass from a root reducer all the way through a chain of coordinators to a child reducer...

    I have an AppReducer which is the entry point of my app. I wish to send .initialise to this reducer, and feed it through to an OnboardingReducer which exists nested inside some coordinators, and the OnboardingReducer should act on receiving .initialise and do some work.

    The pseudo-structure is like this

    AppReducer
        AppCoordinatorReducer
            OnboardingCoordinatorReducer
                OnboardingReducer
    

    My AppView displays the AppCoordinatorView, and on appearance, sends .initialise to the viewStore, like so:

    public var body: some View {
    	WithViewStore(store) { viewStore in
    		AppCoordinatorView(
    			store: store.scope(
    				state: \.appCoordinatorState,
    				action: AppAction.appCoordinator
    			)
    		).onAppear {
    			viewStore.send(.initialise)
    		}
    	}
    }
    

    What is the proper method to pass that .initialise action though the app coordinator, through the onboarding coordinator to end up at its destination: OnboardingReducer?

    I tried the following, and it worked -- however, I don't think this is the correct approach, and I don't even fully understand why it worked... It seems fragile, relying on knowing the index of each coordinator's screen, etc...

    This is in my AppReducer:

    case .initialise:
    	return Effect(
    		value: .appCoordinator(
    			.routeAction(
    				0,
    				action: AppScreenAction.onboardingCoordinator(
    					OnboardingCoordinatorAction.routeAction(
    						0,
    						action: OnboardingScreenAction.welcome(
    							OnboardingAction.initialise
    						)
    					)
    				)
    			)
    		)
    	)
    
    opened by james-rantmedia 4
  • Switch rootView inside Coordinator?

    Switch rootView inside Coordinator?

    I wondered how to switch rootView before login and after login?

    The navigation flow I have now is: Onboarding Flow (Before Login) A -> B -> C

    Home Flow (After Login) 'D -> E -> F'

    The example code at the moment only split the coordinators in different tabs, but how to compose multiple coordinators with 1 single flow?

    Updated: At the moment, I came up with a solution by checking the computed property. And once either OnboardingCoordinator has either push or pop behavior then will trigger the flow property to be refreshed in AppState and causing AppView to switch which view will be presented.

    enum AppFlow: String {
      case onboarding
      case home
    }
    
    var flow: AppFlow {
        if TokenManager.getAuthToken() != nil {
          return .home
        } else {
          return .onboarding
        }    
      }
    
    struct AppView: View {
      let store: Store<AppState, AppAction>
      
      var body: some View {
        WithViewStore(self.store) { viewStore in
          switch viewStore.flow {
          case .onboarding:
            OnboardingCoordinatorView(
              store: store.scope(
                state: \AppState.onboardingCoordinator,
                action: AppAction.onboardingCoordinator
              )
            )
          case .home:
            CoordinatorView(
              store: store.scope(
                state: \AppState.coordinator,
                action: AppAction.coordinator
              )
            )
          }
        }
      }
    }
    
    
    opened by lmw4051 4
  • Do you have plans to support ReducerProtocol?

    Do you have plans to support ReducerProtocol?

    Hello.

    First of all, I've been using it well and thank you.

    So, There was a big update recently at TCA. It is ReducerProtocol.

    So I wonder if you have any plans to support. If you have a plan, would you tell me the timing?

    opened by dp221125 3
  • How to pass data back to the previous view?

    How to pass data back to the previous view?

    We can pass data to different view by using push such as state.routes.push(.numberDetail(.init(number: number))).

    But, how to pass data back to the previous page by using goBack or goBackTo?

    opened by lmw4051 3
  • Fairly consistent crash when dismissing (or swapping) a TCACoordinator flow in iOS16

    Fairly consistent crash when dismissing (or swapping) a TCACoordinator flow in iOS16

    Not sure exactly when it started (but it almost certainly after release of iOS 16) but we are seeing fairly consistent crashes when dismissing a sheet (or switching SwitchStore cases) containing a TCACoordinatorFlow (with more than a single screen). The stack trace is always the same and seems to involve some lower level UIKit NavigationController dismissal animation/callback. Not seeing it with my vanilla TCA NavigationView related flows so wondering if its related to some specific usage of NavigationViews in TCACoordinators/FlowStacks. Here is an example stack trace of the crash.

    Screen Shot 2022-10-15 at 10 35 58 AM

    Strangly enough we were able to get rid of the crash (for the SwitchStore case) by adding a transition in each CaseLet view, so it must be related to the default way the system is tearing down NavigationViews. Wasn't clear how I could apply a similar workaround for standard flows in sheet presentations though.

    Just wanted to put this on your radar to see if you or any other TCACoordinators fans are experiencing something similar and have found any good workarounds.

    Update: Another potential clue is that I notice when you dismiss a flow with several pushed views, it seems to try and pop to the root view of the flow. I noticed this because I always see the onAppear modifier/action called on the root view when the flow is dismissed. Feels like this could be related as perhaps the NavigationView is popping while also being removed from the screen entirely and there is some sort of collision. Any idea why that is happening?

    Update #2: Seems like this bug that causes the root view onAppear to fire on flow dismissal does not occur if if embedInNavigationView: false and wrap the flow myself.

    Thanks as always!

    opened by dannyhertz 4
  • Animate Transitions of Root View?

    Animate Transitions of Root View?

    Hi there!

    I've got my initial app screen driven by a coordinator, where it tries to get some data to determine if the user is logged in or not, and goes to an appropriate screen. The app starts by showing a splash screen to perform that logic, before transitioning to either the logged in screen, or the onboarding screen.

    Almost everything behaves exactly as expected, with the exception of transitions. The root views never transition between each other, they only ever immediately swap.

    I've tried a few things - pushing those state changes into a separate action that I return with .animate(.default) after, wrapping the state.routes = [.root(.onboarding)] in a withAnimation block, wrapping the initial action send in my splash screen with withAnimation, but none of them seem to work.

    Checking in the previous issues, it seems that some people have found a solution, but the code seems more geared towards FlowStacks than TCACoordinators. I'm wondering if there is a solution, even if it's potentially a bit hacky and verbose?

    FWIW, a little sketch of how my main view is set up is here:

    struct MainView: View {
      let store: Store<MainCoordinatorState, MainCoordinatorAction>
    
      var body: some View {
        TCARouter(store) { store in
          SwitchStore(store) { screen in
            CaseLet(
              state: /MainState.splashScreen,
              action: /MainAction.splashScreen
            ) { store in
              SplashScreenView(store: store)
                .transition(.slide)
            }
            
            CaseLet(
              state: /MainState.onboarding,
              action: /MainAction.onboarding
            ) { store in
              OnboardingView(store: store)
                .transition(.slide)
            }
            
            CaseLet(
              state: /MainState.mainTab,
              action: /MainAction.mainTab
            ) { store in
              MainTabView(store: store)
                .transition(.slide)
            }
          }
        }
      }
    }
    

    Have I placed the transition in the wrong place? Is there anything else I need to change?

    Thanks for a great library ๐Ÿ˜ƒ

    opened by rhysm94 5
  • Weird push/pop behavior in iOS 14 when embedding a SubCoordinator in a Coordinator in a WithViewStore ๐Ÿ˜…

    Weird push/pop behavior in iOS 14 when embedding a SubCoordinator in a Coordinator in a WithViewStore ๐Ÿ˜…

    Hi @johnpatrickmorgan !

    First, thanks for this great library that saved me some headaches with setting up initial states with complex hierarchy ๐Ÿ’ฏ

    However, I'd like to raise a weird bug I've found on iOS 14.5 that is not here anymore on iOS 15, but, you know, we need to manage both, at least for a year or so ๐Ÿ˜…

    So I've managed to squeeze the minimum config to reproduce the bug in the sample project attached to this issue.

    Here my navigation configuration:

    [AppView -> [Pushed1View -> Pushed2View]]
    

    A main Coordinator router that manages an AppView screen and a SubCoordinator screen. And the SubCoordinator router that manages a Pushed1View and a Pushed2View.

    The three screen (AppView, Pushed1View and Pushed2View) are displayed in one NavigationView stack using only push actions.

    So, here are my use cases:

    1. If I setup the Coordinator directly in my App Window as my root view, everything is fine ! โœ…
    2. If I setup the Coordinator inside an AppContainerView which embed the Coordinator inside a WithViewStore, then, when I push Pushed2View, it immediately pop back on iOS14 โŒ but its ok on iOS 15 โœ…
    3. If I setup the Coordinator inside an AppContainerView which embed the Coordinator inside a WithViewStore, but I don"t use a SubCoordinator, then, when I push Pushed2View, everything is fine โœ…
    4. If I setup the Coordinator directly in my App Window as my root view, but I embed the TCARouter inside a WithViewStore directly in the body of the CoordinatorView, then, when I push Pushed2View, it immediately pop back on iOS14 โŒ but its ok on iOS 15 โœ…
    5. If I show Pushed2View as a modal instead of a push, everything is fine too

    Hope my explanations are clear enough.

    Here is a screen recording to make this more visual too ๐Ÿ˜‰

    2022-06-13 17 27 37 - 01

    TCAMultiModalStateWithTCACoordinator-bugIOS14.zip

    opened by FredericRuaudel 6
Releases(0.3.0)
  • 0.3.0(Nov 17, 2022)

  • 0.2.0(Jun 27, 2022)

  • 0.1.2(May 9, 2022)

  • 0.1.0(Jan 13, 2022)

    • Presentation and navigation can now be managed in a single routes array, removing the need for separate PresentationStore and NavigationStore. The new TCARouter can handle both in one.
    • Convenience methods are now extensions on RoutableCollection, so this library no longer needs to duplicate the convenience methods from FlowStacks.
    • Large-scale navigation updates can now be made within an Effect.withDelaysIfUnsupported call, and will be broken down into smaller updates that SwiftUI supports. This works around a limitation in SwiftUI that only allows one screen to be pushed, presented or dismissed at a time.
    • Cancellation of in-flight effects is now managed as part of withRouteReducer function, which ensures effects are tagged for cancellation before combining with the reducer that updates routes. This fixes a bug where long-lived effects initiated by the coordinator would be cancelled if they were triggered by a screen that has been dismissed.
    Source code(tar.gz)
    Source code(zip)
Owner
John Patrick Morgan
Swift Developer in London
John Patrick Morgan
Simple example for the coordinator design pattern and using th Xcoordinator pod

Cordinator-Pattern-Sample This an Example and base for the coordinator design pattern using the XCoordinator pod ?? XCoordinator is a navigation frame

Ali Fayed 3 Sep 13, 2022
Modules to use with The Composable Architecture

TCA-Modules Modules to use with The Composable Architecture You can run Examples

Stanislau Parechyn 1 May 20, 2022
An alternative SwiftUI NavigationView implementing classic stack-based navigation giving also some more control on animations and programmatic navigation.

swiftui-navigation-stack An alternative SwiftUI NavigationView implementing classic stack-based navigation giving also some more control on animations

Matteo 753 Jan 2, 2023
A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles

A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles while pushing or popping a view controller for all orientations. And you don't need to write any line of code for it, it all happens automatically.

Zhouqi Mo 3.3k Dec 21, 2022
Models UI navigation patterns using TCA

Composable Navigation The Composable Navigation is a Swift Package that builds on top of The Composable Architecture (TCA, for short). It models UI na

Michael Heinzl 41 Dec 14, 2022
sRouting - The lightweight navigation framework for SwiftUI.

sRouting The lightweight navigation framework for SwiftUI. Overview sRouting using the native navigation mechanism in SwiftUI. It's easy to handle nav

Shiro 8 Aug 15, 2022
Change SwiftUI Navigation Bar Color for different View

SwiftUINavigationBarColor Change SwiftUI NavigationBar background color per screen. Usage For NavigationBarColor to work, you have to set the Navigati

Hai Feng Kao 18 Jul 15, 2022
An iOS view-controller navigation management. No inherit, using one line code to integrate.

KGNavigationBar Example An iOS view-controller navigation management. No inherit, using one line code to integrate. ไธ€ไธช iOS ๆŽงๅˆถๅ™จๅฏผ่ˆช็ฎก็†ๅบ“. ๆ— ้œ€็ปงๆ‰ฟ๏ผŒ ไธ€่กŒไปฃ็ ๅณๅฏๅฎž็Žฐ้›†ๆˆใ€‚

VanJay 5 Sep 6, 2021
๐Ÿงญ SwiftUI navigation done right

?? NavigationKit NavigationKit is a lightweight library which makes SwiftUI navigation super easy to use. ?? Installation ?? Swift Package Manager Usi

Alex Nagy 152 Dec 27, 2022
Navigation helpers for SwiftUI applications build with ComposableArchitecture

Swift Composable Presentation ?? Description Navigation helpers for SwiftUI applications build with ComposableArchitecture. More info about the concep

Dariusz Rybicki 52 Dec 14, 2022
A wrapper for NavigationView and NavigationLink that makes programmatic navigation a little friendlier.

NavigatorKit A wrapper for NavigationView and NavigationLink that makes programmatic navigation a little friendlier. NavigatorKit is an opinionated wr

Gyuri Grell 2 Jun 16, 2022
Cordova/Phonegap plugin for launching today's most popular navigation/ride apps to navigate to a destination.

Launch Navigator Cordova/Phonegap Plugin Cordova/Phonegap plugin for launching today's most popular navigation/ride apps to navigate to a destination.

null 0 Oct 25, 2021
Make SwiftUI Navigation be easy

VNavigator VNavigator is a clean and easy-to-use navigation in SwiftUI base on UINavigationController in UIKit Installation From CocoaPods CocoaPods i

Vu Vuong 10 Dec 6, 2022
Tools for making SwiftUI navigation simpler, more ergonomic and more precise.

SwiftUI Navigation Tools for making SwiftUI navigation simpler, more ergonomic and more precise. Motivation Tools Navigation overloads Navigation view

Point-Free 1.1k Jan 1, 2023
Simple custom navigation bar by swift

YoNavBarView Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installation YoNav

null 1 Nov 23, 2021
Easily hide and show a view controller's navigation bar (and tab bar) as a user scrolls

HidingNavigationBar An easy to use library (written in Swift) that manages hiding and showing a navigation bar as a user scrolls. Features Usage Custo

Tristan Himmelman 1k Dec 21, 2022
Simple and integrated way to customize navigation bar experience on iOS app.

NavKit Simple and integrated way to customize navigation bar experience on iOS app. It should save our time that we usually use to make abstraction of

Wilbert Liu 37 Dec 7, 2022
Replicating the 'clear' navigation bar style of the iOS 12 Apple TV app.

TONavigationBar TONavigationBar is an open-source subclass of UINavigationBar that adds the ability to set the background content of the navigation ba

Tim Oliver 247 Dec 7, 2022
SwiftUINavigation provides UIKit-like navigation in SwiftUI

SwiftUINavigation About SwiftUINavigation provides UIKit-like navigation in Swif

Bhimsen Padalkar 1 Mar 28, 2022