Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Models UI navigation patterns using TCA

License

NotificationsYou must be signed in to change notification settings

heinzl/swift-composable-navigation

Repository files navigation

CIcodecov

Composable Navigation

The Composable Navigation is a Swift Package that builds on top ofThe Composable Architecture (TCA, for short). It models UI navigation patterns using TCA. The corresponding navigation views are implemented in UIKit.

The concept is inspired by the coordinator pattern as it allows a clean separation between individual screens and the logic that ties those screens together. In a TCA world, a coordinator is represented by a state composed of its child views' sub-states and the navigation state. A reducer would then be able to manage the navigation state similar as a coordinator would do by observing actions from child views and presenting/dismissing other screens.

Features

Modal navigation

ModalNavigation models state and actions of a commonly used modal view presentation.Views can be presented with a certain style and dismissed. TheModalNavigationHandler listens to state changes and presents the provided views accordingly. Any state changes are reflected by the handler using UIKit.

Setting the current navigation item to a different screen will result in dismissing the old screen and presenting the new screen. Even changes to only the presentation style are reflected accordingly.

It also supports automatic state updates for pull-to-dismiss for views presented as a sheet.

This example shows how a modal-navigation could be implemented using an enum:

structOnboarding:Reducer{enumScreen{case logincase register}structState:Equatable{varmodalNavigation= ModalNavigation<Screen>.State()...}enumAction:Equatable{case modalNavigation(ModalNavigation<Screen>.Action)...}varbody:someReducer<State,Action>{Scope(state: \.modalNavigation, action:/Action.modalNavigation){ModalNavigation<Screen>()}Reduce{ state, actioninswitch action{case.loginButtonPressed:return.send(.modalNavigation(.presentFullScreen(.login)))case.anotherAction:return.send(.modalNavigation(.dismiss))}return.none}}}

Stack navigation

StackNavigation models state and actions of a stack-based scheme for navigating hierarchical content.Views can be pushed on the stack or popped from the stack. Even mutations to the whole stack can be performed. TheStackNavigationHandler listens to state changes and updates the view stack accordingly using UIKit.

It also supports automatic state updates for popping items via the leading-edge swipe gesture or the long press back-button menu.

This example shows how a series of text inputs could be implemented:

structRegister:Reducer{enumScreen{case emailcase firstNamecase lastName}structState:Equatable{varstackNavigation= StackNavigation<Screen>.State(items:[.email])...}enumAction:Equatable{case stackNavigation(StackNavigation<Screen>.Action)...}varbody:someReducer<State,Action>{Scope(state: \.stackNavigation, action:/Action.stackNavigation){StackNavigation<Screen>()}Reduce{ state, actioninswitch action{case.emailEntered:return.send(.stackNavigation(.pushItem(.firstName)))case.firstNameEntered:return.send(.stackNavigation(.pushItem(.lastName)))...}return.none}}}

Tab navigation

TabNavigation models state and actions of a tab-based scheme for navigating multiple child views.The active navigation item can be changed by setting a new item. Even mutations to items array can be performed (e.g. changing the tab order). TheTabNavigationHandler listens to state changes and updates the selected view or tab order accordingly.

Example:

structRoot{enumScreen:CaseIterable{case homecase listcase settings}structState:Equatable{vartabNavigation= TabNavigation<Screen>.State(            items:Screen.allCases,            activeItem:.home)...}enumAction:Equatable{case tabNavigation(TabNavigation<Screen>.Action)...}varbody:someReducer<State,Action>{Scope(state: \.tabNavigation, action:/Action.tabNavigation){TabNavigation<Screen>()}Reduce{ state, actioninswitch action{case.goToSettings:return.send(.tabNavigation(.setActiveItem(.settings)))...}return.none}}}

ViewProviding

TheViewProvider creates a view according to the given navigation item. It implementsViewProviding which requires the type to create aPresentable (e.g. a SwiftUI View or a UIViewController) for a given navigation item.

Navigation handler (likeStackNavigationHandler) expect aViewProvider. It is used to create new views. The navigation handler will reuse the already created view for.b if the stack of navigation items changes like this:[.a, .b, .c] ->[.x, .y, .b,]

structViewProvider:ViewProviding{letstore:Store<State,Action>func makePresentable(for navigationItem:Screen)->Presentable{switch navigationItem{case.a:returnViewA(store: store.scope(...))case.b:returnViewB(store: store.scope(...))}}}

Usage

A navigation container view can be integrated like any otherUIViewController in your app.

This is an example of aTabNavigationViewController in aSceneDelegate:

classSceneDelegate:UIResponder,UIWindowSceneDelegate{varwindow:UIWindow?    lazyvarstore:Store<App.State,App.Action>=...func scene(_ scene:UIScene, willConnectTo session:UISceneSession, options connectionOptions:UIScene.ConnectionOptions){guardlet windowScene= sceneas?UIWindowSceneelse{return}letcontroller=TabNavigationViewController(store: store.scope(state: \.tabNavigation,action:App.Action.tabNavigation),viewProvider:App.ViewProvider(store: store))letwindow=UIWindow(windowScene: windowScene)window.rootViewController= controllerself.window= windowwindow.makeKeyAndVisible()}...}

You can use the corresponding "handlers" instead e.g. (ModalNavigationHandler) if you already have a custom view controller implementation.Make sure to callnavigationHandler.setup(with: viewController) similar to this:

classExistingViewController:UIViewController{letviewStore:ViewStore<ExistingViewShowcase.State,ExistingViewShowcase.Action>varcancellables:Set<AnyCancellable>=[]letnavigationHandler:ModalNavigationHandler<ExistingViewShowcase.ViewProvider>init(store:Store<ExistingViewShowcase.State,ExistingViewShowcase.Action>){self.viewStore=ViewStore(store, observe:{ $0})self.navigationHandler=ModalNavigationHandler(store: store.scope(state: \.modalNavigation,action:ExistingViewShowcase.Action.modalNavigation), viewProvider:ExistingViewShowcase.ViewProvider(store: store))super.init(nibName:nil, bundle:nil)self.navigationHandler.setup(with:self)}...}

Existing UINavigationController

StackNavigationHandler can be initialized with theignorePreviousViewControllers: Bool parameter. When this parameter is set totrue theStackNavigationHandler will ignore the view controllers that are already on the stack. This is particularly helpful when Composable Navigation is used on top of already existing code.

Showcases

You can find multiple showcases in theExamples project.

The example app hosts multiple showcases (and UI tests), to run one of the showcase you need to changes the variableshowcase inSceneDelegate.swift.

...// 👉 Choose showcase 👈letshowcase:Showcase=.advanced...

License

This library is released under the MIT license. SeeLICENSE for details.

About

Models UI navigation patterns using TCA

Topics

Resources

License

Stars

Watchers

Forks

Contributors2

  •  
  •  

Languages


[8]ページ先頭

©2009-2025 Movatter.jp