{"id":24335805,"url":"https://github.com/ernest0-production/functionalnavigationflowkit","last_synced_at":"2025-07-16T20:37:13.837Z","repository":{"id":56911766,"uuid":"356882775","full_name":"Ernest0-Production/FunctionalNavigationFlowKit","owner":"Ernest0-Production","description":null,"archived":false,"fork":false,"pushed_at":"2022-06-07T19:02:59.000Z","size":95,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-29T13:19:01.715Z","etag":null,"topics":["cocoapods","ios","spm","swift","uikit","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ernest0-Production.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-11T13:59:43.000Z","updated_at":"2024-03-06T10:19:07.000Z","dependencies_parsed_at":"2022-08-21T03:20:09.101Z","dependency_job_id":null,"html_url":"https://github.com/Ernest0-Production/FunctionalNavigationFlowKit","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/Ernest0-Production/FunctionalNavigationFlowKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ernest0-Production%2FFunctionalNavigationFlowKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ernest0-Production%2FFunctionalNavigationFlowKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ernest0-Production%2FFunctionalNavigationFlowKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ernest0-Production%2FFunctionalNavigationFlowKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ernest0-Production","download_url":"https://codeload.github.com/Ernest0-Production/FunctionalNavigationFlowKit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ernest0-Production%2FFunctionalNavigationFlowKit/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265538798,"owners_count":23784661,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cocoapods","ios","spm","swift","uikit","xcode"],"created_at":"2025-01-18T05:35:36.450Z","updated_at":"2025-07-16T20:37:13.821Z","avatar_url":"https://github.com/Ernest0-Production.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FunctionalNavigationFlowKit\n\nФункциональный способ описания UI навигации. \n\n## 🗺 Пример\n```swift\nSetWindowRootFlow(\n    in: window,\n    configuration: .combine(.keyAndVisible, .animated(duration: 0.3)),\n    DeferredBuild(MainTabBarController.init, with: {  rootController in\n\n        SetTabBarItemsFlow(\n            in: rootController,\n            configuration: .titlePositionAdjustment(UIOffset(horizontal: 0.0, vertical: -4.0)),\n            items: [\n\n                DeferredBuild(UINavigationController.init, with: { navigationController in\n\n                    PushFlow(\n                        in: navigationController: navigationController,\n                        animated: false,\n                        configuration: .title(\"Feed\"),\n                        FeedViewController(\n\n                            searchFlow: PushFlow(\n                                in: navigationController,\n                                SearchViewController()\n                            ),\n\n                            itemFlow: { item in\n                                PushFlow(\n                                    in: navigationController,\n                                    configuration: .combine(\n                                        .title(item.name),\n                                        .hidesBottomBarWhenPushed\n                                    ),\n                                    ItemDetailsViewController(\n                                        with: item,\n                                        commentsFlow: PresentFlow(\n                                            in: navigationController,\n                                            CommentsViewController(item: item)\n                                        )\n                                    )\n                                )\n                            }\n\n                        )\n                    )\n\n                }),\n\n                DeferredBuild(UINavigationController.init, with: { navigationController in\n\n                    PushFlow(\n                        in: navigationController,\n                        configuration: .title(\"Profile\"),\n                        ProfileViewController(\n\n                            settingsFlow: PushFlow(\n                                in: navigationController,\n                                configuration: .title(\"Settings\"),\n                                SettingsViewController(\n                                    saveCompletionFlow: PopFlow(in: navigationController)    \n                                )\n                            ),\n\n                            logoutFlow: AuthorizationFlow\n\n                        )\n                    )\n\n                }\n\n            ]\n        )\n\n    })\n)\n\n```\n\n## Overview\n\n- [Требования](#requirements)\n- [В чем смысл?](#-в-чем-смысл)\n- [Конфигурация Flow](#%EF%B8%8F-конфигурация-flow)\n- [Как использовать?](#как-использовать)\n- [Зачем все это нужно?](#-зачем-все-это-нужно-чем-это-лучше-coordinator---паттерна)\n- [Установка](#installation)\n- [Контакты](#credits)\n- [Лицензия](#license)\n\n\n## Requirements\n\n- iOS 9.0+\n- Xcode 10.0+\n- Swift 5.0+\n\n\n## 🤨 В чем смысл?\nНавигация - это действие (push, flow, set, и т.д.).\\\nДействие можно описать ввиде кложура `() -\u003e Void`. Называть его будем просто `Flow`.\\\nТеперь можем написать фабрику методов для создания `Flow`. В данном фреймворке представлены следующие (базовые) методы:\n#### `PushFlow`\n[PushFlow.swift](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/Push/PushFlow.swift)\n```swift\nPushFlow(\n    in: myNavigationController,\n    MyViewController()\n)\n```\n#### `PopFlow`\n[PopFlow.swift](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/Push/PopFlow.swift)\n```swift\nPopFlow(in: myNavigationController)\n\nPopFlow(\n    in: myNavigationController,\n    to: secondViewControlller\n)\n\nPopToRootFlow(in: myNavigationController)\n```\n#### `PresentFlow`\n[PresentFlow.swift](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/Present/PresentFlow.swift)\n```swift\nPresent(\n    in: rootViewController,\n    MyModalViewController()\n)\n\n// Ищет самый верхний контроллер в окне и презентит в нём.\nPresent(\n    in: window,\n    MyModalViewController()\n)\n```\n#### `DismissFlow`\n[DismissFlow.swift](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/Present/DismissFlow.swift)\n```swift\nDismissFlow(myViewController)\n\n// Дисмисит контроллер, который презентовал указанный контроллер\nDismissFlow(in: presentedViewController)\n\n// Дисмисит самой верхний контроллер в окне\nDismissFlow(in: window)\n```\n#### `SetTabBarItemsFlow`\n[SetTabBarItemsFlow.swift](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/SetTabBarItems/SetTabBarItemsFlow.swift)\n```swift\nSetTabBarItemsFlow(\n    in: tabBarController,\n    items: [\n        FeedViewController(),\n        ProfileViewController(),\n    ]\n)\n```\n#### `SetWindowRootFlow`\n[SetWindowRootFlow.swift](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/SetWindowRoot/SetWindowRootFlow.swift)\n```swift\nSetWindowRootFlow(\n    in: window,\n    myRootViewController\n)\n```\n\nЕсли этого кажется недостаточным, вы можете написать свою функцию аналогичным образом через глобальные функции возвращающие Flow (т.е. кложур).\nНапример, флоу с пушем и удалением предыдущего экрана может быть реализован так:\n```swift\nPushReplacingFlow(\n    in: navigationController,\n    MyViewController()\n)\n\n\nfunc PushReplacingFlow(\n    in navigationController: UINavigationController,\n    animated: Bool = true,\n    _ viewController: @autoclosure @escaping UIViewController\n) -\u003e Flow { \n    return { \n        var newStack = navigationController.viewControllers\n        newStack.removeLast()\n        newStack.append(viewController())\n\n        navigationController.setViewControllers(\n            viewControllers: newStack,\n            animated: animated\n        )\n    }\n}\n```\n\n\n## ⚙️ Конфигурация `Flow`\nКаждый `Flow` имеет конфигурацию [`FlowConfiguration`](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/FlowConfiguration.swift), которая запускается **перед** и **после** выполнения.\n```swift\nlet configuration = FlowConfiguration\u003cUINavigationController, UIViewController\u003e(\n    prepare: { navigationController, viewController in  \n        navigationController.setNavigationBarHidden(true, animated: false)\n    },\n    completion: { navigationController, viewController in\n        Analytics.track(.openScreen(String(description: viewController)))\n    }\n)\n\nreturn PushFlow(\n    in: navigationController,\n    configuration: configuration,\n    SubscriptionViewController()\n)\n```\n\nФреймфорк уже предоставляет базовый набор конфигураций:\n---\n [`PushFlowConfiguration`](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/Push/PushFlowConfiguration.swift) == `FlowConfiguration\u003cUINavigationController, UIViewController\u003e`\n- `hidesBottomBarWhenPushed`\n- `title(String?)`\n- `titleView(UIView?)`\n- `navigationDelegate(UINavigationControllerDelegate)`\n---\n[`PresentFlowConfiguration`](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/Present/PresentFlowConfiguration.swift) == `FlowConfiguration\u003cUIViewController, UIViewController\u003e`\n- `transitionStyle(UIModalTransitionStyle)`\n- `presentationStyle(UIModalPresentationStyle)`\n- `modalInPresentation`\n- `transitionDelegate(UIViewControllerTransitioningDelegate)`\n---\n[`SetTabBarItemsFlowConfiguration`](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/SetTabBarItems/SetTabBarItemsFlowConfiguration.swift) == `FlowConfiguration\u003cUITabBarController, UIViewController\u003e`\n- `titlePositionAdjustment(UIOffset)`\n---\n[`SetWindowRootFlowConfiguration`](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/main/Sources/FunctionalNavigationFlowKit/SetWindowRoot/SetWindowRootFlowConfiguration.swift)== `FlowConfiguration\u003cUIWindow, UIViewController\u003e`\n- `keyAndVisible`\n- `animated(duration: TimeInterval, completion: Flow?)`\n---\n## ⏰ Ленивая инициализация\nПрезентуемые контроллеры инициализируются **только при запуске `Flow`!**\n\n`Push/Present/Set/...Flow` принимают `() -\u003e ViewController` билдер.\n\u003e Для каждого представленного `Flow` есть альтернативная инициализация с `@autoclosure` билдером.\n```swift\n// контроллер еще не проинициализирован\nlet flow = PushFlow(\n    in: myNavigationController,\n    MyViewController()\n)\n// контроллер проинициализирован и запушен\nflow()\n```\n\n## 🪆 Рекурсивная навигация\nИногда может возникнуть кейс с бесконечной вложенностью стэка экранов. Например, экран перехода на страницу профиля друга из экрана твоего профиля, а из экрана профиля друга, на экран профиля его друга.\n\nРеализовать такое с FunctionalNavigationFlowKit можно двумя способами.\\\n1. Вынести флоу профиля в отдельную переменную/функцию и вызывать ее внутри себя(рекурсивно) при открытии профиля друга.\n2. Использовать `RecursiveFlow`:\n```swift\nRecursiveFlow(with: myUserInfo, { (userInfo: UserInfo, profileFlow: (UserInfo) -\u003e Flow) in \n    PushFlow(\n        in: navigationController,\n        ProfileViewController(\n            userInfo: userInfo, \n            friendFlow: profileFlow // принимает кложур (UserInfo) -\u003e Flow\n        )\n}\n```\n# Как использовать?\n\nВходной точкой приложения является AppDelegate, потому `Flow` запускается оттуда.\nРассмотрим пример стандартного приложения с авторизацией:\n```swift\nfunc application(\n    _ application: UIApplication,\n     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n) -\u003e Bool {\n\n    window = UIWindow(frame: UIScreen.main.bounds)\n\n    if session.isAuthorized { \n        MainFlow(\n            in: window,\n            logoutFlow: AuthFlow\n        )()\n    } else { \n        AuthFlow(\n            in: window,\n            completionFlow: MainFlow\n        )()\n    }\n\n    return true\n}\n```\nПример создания MainFlow:\n```swift\nfunc MainFlow(\n    in window: UIWindow,\n    logoutFlow: @escaping Flow\n) -\u003e Flow { \n\n    // Some shared dependencies\n    let imageLoader = ImageLoader()\n    \n    return SetWindowRootFlow(\n        in: window,\n        configuration: .makeKeyAndVisible\n        DeferredBuild(UINavigationViewController.init) {  navigationController in \n\n            PushFlow(\n                in: navigationController,\n                FeedViewController(\n                    \n                    feedDetailsFlow: { (feed: Feed) in \n                        PushFlow(in: navigationController, FeedDetailsViewController(feed, imageLoader))\n                    },\n\n                    profileFlow: (userInfo) -\u003e Flow in \n                        PresentFlow(\n                            in: navigationController, \n                            ProfileViewController(\n                                userInfo,\n                                logoutFlow: logoutFlow\n                            )\n                        )\n                )\n            )\n\n        }\n    )\n}\n```\n\n---\n\n## 😑 Зачем все это нужно? Чем это лучше Coordinator - паттерна?\n1. Подобный подход позволяет **декларативно** описать **карту навигации**, скрывая детали презентации и сборки экрана.\n2. Делает **независимым** навигацию от конкретного экрана. Иными словами, презентуемый и презентующий контроллер не знают (и не должны знать), как будут отображаться в иерархии окон. Такой подход позволяет легко поменять тип презентации и контекст, в котором презентуется экран, без необходимости изменять что-то несвязанное с навигацией...*open/closed principle*  (пересекается с пунктом 1).\n3. В `swift` можно писать *вложенные функции*. Учитывая то, что при работе с навигацией **важно иметь доступ ко всей иерархии из любого презентуемого контекста** приложения, вложенные функциии являются удобным решением для описания какого-то внутреннего скоупа навигации. Имея доступ к внешним(глобальном для внутренней фунции) переменменным, пропадает необходимость использования *constructor/property/method injection* из ООП, которая часто используется в `Coordinator`-ах при передаче зависимостей в дочерние(n-ой вложенности) координаторы через дерево/цепочку вызовов, приводящих к созданию транзитивных зависимостей.\n4. Глубокая вложенность может показаться чем-то плохим, но в данном случае она описывает столь же глубокую вложенность навигации приложения. Можно вынести что-то в отдельные переменную, но пользы от этого меньше, чем вреда: теряешь доступ ко всей иерархии контроллеров (нижестоящих). Исключением для вынесения может разве что наличие нескольких точек перехода на один и тот же `Flow`. \n\n---\n\n## Installation\n\n#### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)\n\n```ruby\n# Podfile\nuse_frameworks!\n\ntarget 'YOUR_TARGET_NAME' do\n    pod 'FunctionalNavigationFlowKit'\nend\n```\n\n#### [Swift Package Manager](https://github.com/apple/swift-package-manager)\n\nCreate a `Package.swift` file.\n\n```swift\n// swift-tools-version:5.3\n\nimport PackageDescription\n\nlet package = Package(\n  name: \"YOUR_PROJECT_NAME\",\n  dependencies: [\n      .package(url: \"https://github.com/Ernest0-Production/FunctionalNavigationFlowKit.git\", from: \"0.0.2\")\n  ],\n  targets: [\n      .target(name: \"YOUR_TARGET_NAME\", dependencies: [\"FunctionalNavigationFlowKit\"])\n  ]\n)\n```\n\n### Credits\n\n- [Telegram](https://t.me/Ernest0n)\n\n\n### License\n\nFunctionalNavigationFlowKit is released under the MIT license. See [LICENSE](https://github.com/Ernest0-Production/FunctionalNavigationFlowKit/blob/master/LICENSE.md) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fernest0-production%2Ffunctionalnavigationflowkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fernest0-production%2Ffunctionalnavigationflowkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fernest0-production%2Ffunctionalnavigationflowkit/lists"}