├── Examples
├── Shared
│ └── PageTemplate
│ │ ├── Sources
│ │ └── PageTemplate
│ │ │ ├── Page
│ │ │ └── .gitkeep
│ │ │ └── PageTemplate.swift
│ │ ├── .gitignore
│ │ ├── Tests
│ │ └── PageTemplateTests
│ │ │ └── PageTemplateTests.swift
│ │ └── Package.swift
├── TabNavigator
│ ├── 01-TabBasic
│ │ ├── TabBasic
│ │ │ ├── AppDependency.swift
│ │ │ ├── Assets.xcassets
│ │ │ │ ├── Contents.json
│ │ │ │ ├── AccentColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── AppIcon.appiconset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Preview Content
│ │ │ │ └── Preview Assets.xcassets
│ │ │ │ │ └── Contents.json
│ │ │ ├── ContentView.swift
│ │ │ ├── TabBasic.entitlements
│ │ │ ├── Pages
│ │ │ │ ├── Tab1
│ │ │ │ │ ├── Tab1RouteBuilder.swift
│ │ │ │ │ └── Tab1Page.swift
│ │ │ │ ├── Tab2
│ │ │ │ │ ├── Tab2RouteBuilder.swift
│ │ │ │ │ └── Tab2Page.swift
│ │ │ │ ├── Tab3
│ │ │ │ │ ├── Tab3RouteBuilder.swift
│ │ │ │ │ └── Tab3Page.swift
│ │ │ │ ├── Tab4
│ │ │ │ │ ├── Tab4RouteBuilder.swift
│ │ │ │ │ └── Tab4Page.swift
│ │ │ │ └── Common
│ │ │ │ │ ├── Step1
│ │ │ │ │ ├── Step1RouteBuilder.swift
│ │ │ │ │ └── Step1Page.swift
│ │ │ │ │ ├── Step2
│ │ │ │ │ ├── Step2RouteBuilder.swift
│ │ │ │ │ └── Step2Page.swift
│ │ │ │ │ ├── Step3
│ │ │ │ │ ├── Step3RouteBuilder.swift
│ │ │ │ │ └── Step3Page.swift
│ │ │ │ │ └── Step4
│ │ │ │ │ ├── Step4RouteBuilder.swift
│ │ │ │ │ └── Step4Page.swift
│ │ │ ├── Component
│ │ │ │ └── PathIndicator.swift
│ │ │ ├── AppRouterBuilderGroup.swift
│ │ │ └── TabBasicApp.swift
│ │ └── 01-TabBasic.xcodeproj
│ │ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ ├── 03-TabEventSubscriber
│ │ ├── TabEventSubscriber
│ │ │ ├── AppDependency.swift
│ │ │ ├── Assets.xcassets
│ │ │ │ ├── Contents.json
│ │ │ │ ├── AccentColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── AppIcon.appiconset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Preview Content
│ │ │ │ └── Preview Assets.xcassets
│ │ │ │ │ └── Contents.json
│ │ │ ├── LogManager.swift
│ │ │ ├── TabEventSubscriber.entitlements
│ │ │ ├── Pages
│ │ │ │ ├── Common
│ │ │ │ │ ├── Step1
│ │ │ │ │ │ ├── Step1RouteBuilder.swift
│ │ │ │ │ │ └── Step1Page.swift
│ │ │ │ │ ├── Step3
│ │ │ │ │ │ ├── Step3RouteBuilder.swift
│ │ │ │ │ │ └── Step3Page.swift
│ │ │ │ │ ├── Step4
│ │ │ │ │ │ ├── Step4RouteBuilder.swift
│ │ │ │ │ │ └── Step4Page.swift
│ │ │ │ │ └── Step2
│ │ │ │ │ │ ├── Step2RouteBuilder.swift
│ │ │ │ │ │ └── Step2Page.swift
│ │ │ │ ├── Tab1
│ │ │ │ │ ├── Tab1RouteBuilder.swift
│ │ │ │ │ └── Tab1Page.swift
│ │ │ │ ├── Tab2
│ │ │ │ │ ├── Tab2RouteBuilder.swift
│ │ │ │ │ └── Tab2Page.swift
│ │ │ │ ├── Tab3
│ │ │ │ │ ├── Tab3RouteBuilder.swift
│ │ │ │ │ └── Tab3Page.swift
│ │ │ │ ├── Tab4
│ │ │ │ │ ├── Tab4RouteBuilder.swift
│ │ │ │ │ └── Tab4Page.swift
│ │ │ │ └── EventSubscriber.swift
│ │ │ ├── Info.plist
│ │ │ ├── Component
│ │ │ │ └── PathIndicator.swift
│ │ │ ├── AppRouterBuilderGroup.swift
│ │ │ └── TabEventSubscriberApp.swift
│ │ └── 03-TabEventSubscriber.xcodeproj
│ │ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── swiftpm
│ │ │ │ └── Package.resolved
│ │ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── TabEventSubscriber.xcscheme
│ └── 02-TabInjectionParameter
│ │ ├── TabInjectionParameter
│ │ ├── AppDependency.swift
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── Preview Content
│ │ │ └── Preview Assets.xcassets
│ │ │ │ └── Contents.json
│ │ ├── TabInjectionParameter.entitlements
│ │ ├── Pages
│ │ │ ├── Tab1
│ │ │ │ ├── Tab1RouteBuilder.swift
│ │ │ │ └── Tab1Page.swift
│ │ │ ├── Tab2
│ │ │ │ ├── Tab2RouteBuilder.swift
│ │ │ │ └── Tab2Page.swift
│ │ │ ├── Tab3
│ │ │ │ ├── Tab3RouteBuilder.swift
│ │ │ │ └── Tab3Page.swift
│ │ │ ├── Tab4
│ │ │ │ ├── Tab4RouteBuilder.swift
│ │ │ │ └── Tab4Page.swift
│ │ │ └── Common
│ │ │ │ ├── Step1
│ │ │ │ ├── Step1RouteBuilder.swift
│ │ │ │ └── Step1Page.swift
│ │ │ │ ├── Step3
│ │ │ │ ├── Step3RouteBuilder.swift
│ │ │ │ └── Step3Page.swift
│ │ │ │ ├── Step4
│ │ │ │ ├── Step4RouteBuilder.swift
│ │ │ │ └── Step4Page.swift
│ │ │ │ └── Step2
│ │ │ │ ├── Step2RouteBuilder.swift
│ │ │ │ └── Step2Page.swift
│ │ ├── Component
│ │ │ └── PathIndicator.swift
│ │ ├── AppRouteBuilderGroup.swift
│ │ └── TabInjectionParameterApp.swift
│ │ └── 02-TabInjectionParameter.xcodeproj
│ │ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── SingleNavigator
│ ├── 01-SingleBasic
│ ├── SingleBasic
│ │ ├── AppDependency.swift
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── Preview Content
│ │ │ └── Preview Assets.xcassets
│ │ │ │ └── Contents.json
│ │ ├── LogManager.swift
│ │ ├── SingleBasic.entitlements
│ │ ├── Page
│ │ │ ├── Home
│ │ │ │ ├── HomeRouteBuilder.swift
│ │ │ │ └── HomeView.swift
│ │ │ ├── Page1
│ │ │ │ ├── Page1RouteBuilder.swift
│ │ │ │ └── Page1View.swift
│ │ │ ├── Page2
│ │ │ │ ├── Page2RouteBuilder.swift
│ │ │ │ └── Page2View.swift
│ │ │ ├── Page3
│ │ │ │ ├── Page3RouteBuilder.swift
│ │ │ │ └── Page3View.swift
│ │ │ └── Page4
│ │ │ │ ├── Page4RouteBuilder.swift
│ │ │ │ └── Page4View.swift
│ │ ├── SingleBasicApp.swift
│ │ ├── AppRouterGroup.swift
│ │ └── Component
│ │ │ └── PathIndicator.swift
│ └── 01-SingleBasic.xcodeproj
│ │ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ │ └── xcshareddata
│ │ └── xcschemes
│ │ └── SingleBasic.xcscheme
│ ├── 03-SingleTicTacToe
│ └── SingleTicTacToe
│ │ ├── AppDependency.swift
│ │ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ │ ├── Page
│ │ ├── Home
│ │ │ ├── HomeRouteBuilder.swift
│ │ │ └── HomeView.swift
│ │ ├── Login
│ │ │ ├── LoginRouteBuilder.swift
│ │ │ └── LoginView.swift
│ │ ├── NewGame
│ │ │ ├── NewGameRouteBuilder.swift
│ │ │ └── NewGameView.swift
│ │ └── Game
│ │ │ ├── GameView.swift
│ │ │ └── GameRoutebuilder.swift
│ │ ├── AppRouterGroup.swift
│ │ ├── SingleTicTacToeApp.swift
│ │ └── Component
│ │ └── PathIndicator.swift
│ └── 02-SingleEventSubscriber
│ ├── SingleEventSubscriber
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── DeepLink
│ │ ├── DeepLinkItem.swift
│ │ └── DeepLinkParser.swift
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── AppDependency.swift
│ ├── SharedRootViewModel.swift
│ ├── LogManager.swift
│ ├── SingleEventSubscriber.entitlements
│ ├── Page
│ │ ├── Page3
│ │ │ ├── Page3RouteBuilder.swift
│ │ │ └── Page3View.swift
│ │ ├── Home
│ │ │ ├── HomeRouteBuilder.swift
│ │ │ └── HomeView.swift
│ │ ├── Page2
│ │ │ ├── Page2RouteBuilder.swift
│ │ │ ├── Page2LinkSubscriber.swift
│ │ │ └── Page2View.swift
│ │ └── Page1
│ │ │ ├── Page1RouteBuilder.swift
│ │ │ └── Page1View.swift
│ ├── AppRouterGroup.swift
│ ├── Component
│ │ └── PathIndicator.swift
│ ├── Info.plist
│ └── SingleEventSubscriberApp.swift
│ └── 02-SingleEventSubscriber.xcodeproj
│ └── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── swiftpm
│ └── Package.resolved
├── .gitignore
├── Sources
└── LinkNavigator
│ ├── Core
│ ├── EventNotification
│ │ └── TabbarEventNotification.swift
│ ├── Coordinate
│ │ └── SheetCoordinate.swift
│ ├── BaseComponent
│ │ ├── TabItem.swift
│ │ ├── Subscriber
│ │ │ └── LinkNavigatorSubscriberType.swift
│ │ ├── NavigationTarget.swift
│ │ └── LinkItem.swift
│ ├── Core
│ │ ├── TabLinkNavigator
│ │ │ ├── TabRootNavigationController.swift
│ │ │ └── TabLinkNavigator.swift
│ │ └── DetentConfiguration.swift
│ └── Protocol
│ │ ├── LinkNavigatorFindLocationUsable.swift
│ │ ├── EmptyValueType.swift
│ │ ├── TabLinkNavigatorProtocol.swift
│ │ └── LinkNavigatorProtocol.swift
│ ├── Deprecated
│ ├── RouteBuilderType.swift
│ ├── BaseNavigator.swift
│ └── EventObserver.swift
│ ├── Builder
│ ├── RouteBuilderOf.swift
│ ├── TabNavigationBuilder.swift
│ └── SingleNavigationBuilder.swift
│ ├── Components
│ ├── WrappingController.swift
│ ├── TabLinkNavigationView.swift
│ ├── Alert
│ │ ├── Alert.swift
│ │ └── ActionButton.swift
│ └── LinkNavigationView.swift
│ ├── DI
│ └── DependencyType.swift
│ └── Test
│ ├── SingleLinkNavigatorMock.swift
│ └── TabLinkNavigatorMock.swift
├── Example.xcworkspace
├── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── swiftpm
│ │ └── Package.resolved
└── contents.xcworkspacedata
├── Tests
└── LinkNavigatorTests
│ └── LinkNavigatorTests.swift
├── Package.swift
├── Package.resolved
└── LICENSE.md
/Examples/Shared/PageTemplate/Sources/PageTemplate/Page/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/AppDependency.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | struct AppDependency: DependencyType { }
4 |
--------------------------------------------------------------------------------
/Examples/Shared/PageTemplate/Sources/PageTemplate/PageTemplate.swift:
--------------------------------------------------------------------------------
1 | // The Swift Programming Language
2 | // https://docs.swift.org/swift-book
3 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/AppDependency.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | struct AppDependency: DependencyType { }
4 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/AppDependency.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | struct AppDependency: DependencyType { }
4 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/AppDependency.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | struct AppDependency: DependencyType { }
4 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/AppDependency.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | struct AppDependency: DependencyType { }
4 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 |
9 | ### Jetbrains
10 | .idea
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/DeepLink/DeepLinkItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct DeepLinkItem: Equatable, Codable {
4 | let deepLinkMessage: String
5 | }
6 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/AppDependency.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | struct AppDependency: DependencyType {
4 | let sharedRootViewModel: SharedRootViewModel
5 | }
6 |
--------------------------------------------------------------------------------
/Examples/Shared/PageTemplate/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/01-TabBasic.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/01-SingleBasic.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/03-TabEventSubscriber.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/02-SingleEventSubscriber.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/02-TabInjectionParameter.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/EventNotification/TabbarEventNotification.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum TabbarEventNotification: Equatable {
4 | public static let onMoved = Notification.Name("onMoved")
5 | public static let onSelectedTab = Notification.Name("onSelectedTab")
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/SharedRootViewModel.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | final class SharedRootViewModel: ObservableObject {
4 | @Published var text = "Initialized"
5 |
6 | func update(text: String) {
7 | self.text = text
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/ContentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct ContentView: View {
4 | var body: some View {
5 | VStack {
6 | Image(systemName: "globe")
7 | .imageScale(.large)
8 | Text("Hello, world!")
9 | }
10 | .padding()
11 | }
12 | }
13 |
14 | #Preview {
15 | ContentView()
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/01-TabBasic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/01-SingleBasic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Deprecated/RouteBuilderType.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public typealias MatchingViewController = MatchPathUsable & UIViewController
4 |
5 | // MARK: - RouteBuilder
6 |
7 | public protocol RouteBuilder {
8 | var matchPath: String { get }
9 |
10 | var build: (LinkNavigatorType, [String: String], DependencyType) -> MatchingViewController? { get }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/03-TabEventSubscriber.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/02-SingleEventSubscriber.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/02-TabInjectionParameter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/LogManager.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Logging
3 |
4 | // MARK: - LogManager
5 |
6 | struct LogManager {
7 | private let log = Logger(label: "io.forxifless.linknavigator.SingleBasic")
8 |
9 | static let `default` = LogManager()
10 | }
11 |
12 | extension LogManager {
13 | func debug(_ message: Logger.Message) {
14 | log.info(message)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/TabBasic.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/SingleBasic.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/LogManager.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Logging
3 |
4 | // MARK: - LogManager
5 |
6 | struct LogManager {
7 | private let log = Logger(label: "io.forxifless.linknavigator.SingleBasic")
8 |
9 | static let `default` = LogManager()
10 | }
11 |
12 | extension LogManager {
13 | func debug(_ message: Logger.Message) {
14 | log.info(message)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Tests/LinkNavigatorTests/LinkNavigatorTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import LinkNavigator
3 |
4 | final class LinkNavigatorTests: XCTestCase {
5 | func testExample() throws {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | // XCTAssertEqual(LinkNavigator().text, "Hello, World!")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/LogManager.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Logging
3 |
4 | // MARK: - LogManager
5 |
6 | struct LogManager {
7 | private let log = Logger(label: "io.forxifless.linknavigator.TabEventSubscriber")
8 |
9 | static let `default` = LogManager()
10 | }
11 |
12 | extension LogManager {
13 | func debug(_ message: Logger.Message) {
14 | log.info(message)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/TabEventSubscriber.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/TabInjectionParameter.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/SingleEventSubscriber.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/Shared/PageTemplate/Tests/PageTemplateTests/PageTemplateTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import PageTemplate
3 |
4 | final class PageTemplateTests: XCTestCase {
5 | func testExample() throws {
6 | // XCTest Documentation
7 | // https://developer.apple.com/documentation/xctest
8 |
9 | // Defining Test Cases and Test Methods
10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/01-TabBasic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "urlencodedform",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/interactord/URLEncodedForm",
7 | "state" : {
8 | "revision" : "5f51aa13d41b26fc8833cd040df3a3101768bb64",
9 | "version" : "1.0.8"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Tab1/Tab1RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab1RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab1"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Tab1Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Tab2/Tab2RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab2RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab2"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Tab2Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Tab3/Tab3RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab3RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab3"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Tab3Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Tab4/Tab4RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab4RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab4"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Tab4Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/03-TabEventSubscriber.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "urlencodedform",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/interactord/URLEncodedForm",
7 | "state" : {
8 | "revision" : "5f51aa13d41b26fc8833cd040df3a3101768bb64",
9 | "version" : "1.0.8"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Home/HomeRouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct HomeRouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "home"
9 |
10 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
11 | WrappingController(matchPath: matchPath) {
12 | HomeView(navigator: navigator)
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Page/Home/HomeRouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct HomeRouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "home"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | HomeView(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Common/Step1/Step1RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step1RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step1"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step1Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Common/Step2/Step2RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step2RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step2"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step2Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Common/Step3/Step3RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step3RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step3"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step3Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Common/Step4/Step4RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step4RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step4"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step4Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/02-TabInjectionParameter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "urlencodedform",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/interactord/URLEncodedForm",
7 | "state" : {
8 | "revision" : "5f51aa13d41b26fc8833cd040df3a3101768bb64",
9 | "version" : "1.0.8"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Page1/Page1RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page1RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "page1"
9 |
10 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
11 | WrappingController(matchPath: matchPath) {
12 | Page1View(navigator: navigator)
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Page2/Page2RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page2RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "page2"
9 |
10 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
11 | WrappingController(matchPath: matchPath) {
12 | Page2View(navigator: navigator)
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Page3/Page3RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page3RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "page3"
9 |
10 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
11 | WrappingController(matchPath: matchPath) {
12 | Page3View(navigator: navigator)
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Page4/Page4RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page4RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "page4"
9 |
10 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
11 | WrappingController(matchPath: matchPath) {
12 | Page4View(navigator: navigator)
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/02-SingleEventSubscriber.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "urlencodedform",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/interactord/URLEncodedForm",
7 | "state" : {
8 | "revision" : "5f51aa13d41b26fc8833cd040df3a3101768bb64",
9 | "version" : "1.0.8"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Page/Login/LoginRouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct LoginRouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "login"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | LoginView(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Tab1/Tab1RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab1RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab1"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Tab1Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Tab2/Tab2RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab2RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab2"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Tab2Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Tab3/Tab3RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab3RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab3"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Tab3Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Tab4/Tab4RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab4RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab4"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Tab4Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Page3/Page3RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page3RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "page3"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Page3View(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Common/Step1/Step1RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step1RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step1"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step1Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Common/Step3/Step3RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step3RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step3"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step3Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Common/Step4/Step4RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step4RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step4"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step4Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Common/Step1/Step1RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step1RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step1"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step1Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Common/Step3/Step3RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step3RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step3"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step3Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Common/Step4/Step4RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step4RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "step4"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | Step4Page(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/SingleBasicApp.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | @main
5 | struct SingleBasicApp: App {
6 | let singleNavigator = SingleLinkNavigator(
7 | routeBuilderItemList: AppRouterGroup().routers(),
8 | dependency: AppDependency())
9 |
10 | var body: some Scene {
11 | WindowGroup {
12 | LinkNavigationView(
13 | linkNavigator: singleNavigator,
14 | item: .init(path: "home"))
15 | .ignoresSafeArea()
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Deprecated/BaseNavigator.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | // MARK: - BaseNavigator
4 |
5 | public struct BaseNavigator {
6 | let viewController: UINavigationController
7 | }
8 |
9 | // MARK: UIViewControllerRepresentable
10 |
11 | extension BaseNavigator: UIViewControllerRepresentable {
12 | public func makeUIViewController(context _: Context) -> UINavigationController {
13 | viewController
14 | }
15 |
16 | public func updateUIViewController(_: UINavigationController, context _: Context) { }
17 | }
18 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/AppRouterGroup.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | // MARK: - AppRouterGroup
4 |
5 | public struct AppRouterGroup {
6 | public init() { }
7 | }
8 |
9 | extension AppRouterGroup {
10 |
11 | @MainActor
12 | func routers() -> [RouteBuilderOf] {
13 | [
14 | HomeRouteBuilder().generate(),
15 | LoginRouteBuilder().generate(),
16 | NewGameRouteBuilder().generate(),
17 | GameRouteBuilder().generate(),
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/AppRouterGroup.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | // MARK: - AppRouterGroup
4 |
5 | public struct AppRouterGroup {
6 | public init() { }
7 | }
8 |
9 | extension AppRouterGroup {
10 |
11 | @MainActor
12 | func routers() -> [RouteBuilderOf] {
13 | [
14 | HomeRouteBuilder().generate(),
15 | Page1RouteBuilder().generate(),
16 | Page2RouteBuilder().generate(),
17 | Page3RouteBuilder().generate(),
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/SingleTicTacToeApp.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | @main
5 | struct SingleTicTacToeApp: App {
6 | let singleNavigator = SingleLinkNavigator(
7 | routeBuilderItemList: AppRouterGroup().routers(),
8 | dependency: AppDependency())
9 |
10 | var body: some Scene {
11 | WindowGroup {
12 | LinkNavigationView(
13 | linkNavigator: singleNavigator,
14 | item: .init(path: "home"))
15 | .ignoresSafeArea()
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleURLTypes
6 |
7 |
8 | CFBundleTypeRole
9 | Editor
10 | CFBundleURLSchemes
11 |
12 | tab-e-s
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Page/NewGame/NewGameRouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct NewGameRouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "newGame"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | WrappingController(matchPath: matchPath) {
11 | NewGameView(navigator: navigator)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/Coordinate/SheetCoordinate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class SheetCoordinate: NSObject, UIAdaptivePresentationControllerDelegate {
4 |
5 | // MARK: Lifecycle
6 |
7 | init(sheetDidDismiss: @escaping (UIPresentationController) -> Void) {
8 | self.sheetDidDismiss = sheetDidDismiss
9 | }
10 |
11 | // MARK: Internal
12 |
13 | var sheetDidDismiss: (UIPresentationController) -> Void
14 |
15 | func presentationControllerDidDismiss(_ present: UIPresentationController) {
16 | sheetDidDismiss(present)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Component/PathIndicator.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct PathIndicator: View {
4 | let currentPath: String
5 |
6 | public var body: some View {
7 | VStack(spacing: 16) {
8 | Text("currentPath")
9 | .font(.headline)
10 |
11 | Text(currentPath)
12 | .font(.subheadline)
13 | }
14 | .padding()
15 | .frame(maxWidth: .infinity)
16 | .background(
17 | RoundedRectangle(cornerRadius: 12).fill(.green.opacity(0.3)))
18 | .padding(.horizontal)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/AppRouterGroup.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | // MARK: - AppRouterGroup
4 |
5 | public struct AppRouterGroup {
6 | public init() { }
7 | }
8 |
9 | extension AppRouterGroup {
10 |
11 | @MainActor
12 | func routers() -> [RouteBuilderOf] {
13 | [
14 | HomeRouteBuilder().generate(),
15 | Page1RouteBuilder().generate(),
16 | Page2RouteBuilder().generate(),
17 | Page3RouteBuilder().generate(),
18 | Page4RouteBuilder().generate(),
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Component/PathIndicator.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct PathIndicator: View {
4 | let currentPath: String
5 |
6 | public var body: some View {
7 | VStack(spacing: 16) {
8 | Text("currentPath")
9 | .font(.headline)
10 |
11 | Text(currentPath)
12 | .font(.subheadline)
13 | }
14 | .padding()
15 | .frame(maxWidth: .infinity)
16 | .background(
17 | RoundedRectangle(cornerRadius: 12).fill(.green.opacity(0.3)))
18 | .padding(.horizontal)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/BaseComponent/TabItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public struct TabItem {
5 | public let tag: Int
6 | public let tabItem: UITabBarItem?
7 | public let linkItem: LinkItem
8 | public let prefersLargeTitles: Bool
9 |
10 | public init(
11 | tag: Int,
12 | tabItem: UITabBarItem?,
13 | linkItem: LinkItem,
14 | prefersLargeTitles: Bool = false)
15 | {
16 | self.tag = tag
17 | self.tabItem = tabItem
18 | self.linkItem = linkItem
19 | self.prefersLargeTitles = prefersLargeTitles
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Component/PathIndicator.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct PathIndicator: View {
4 | let currentPath: String
5 |
6 | public var body: some View {
7 | VStack(spacing: 16) {
8 | Text("currentPath")
9 | .font(.headline)
10 |
11 | Text(currentPath)
12 | .font(.subheadline)
13 | }
14 | .padding()
15 | .frame(maxWidth: .infinity)
16 | .background(
17 | RoundedRectangle(cornerRadius: 12).fill(.green.opacity(0.3)))
18 | .padding(.horizontal)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Component/PathIndicator.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct PathIndicator: View {
4 | let currentPath: String
5 |
6 | public var body: some View {
7 | VStack(spacing: 16) {
8 | Text("currentPath")
9 | .font(.headline)
10 |
11 | Text(currentPath)
12 | .font(.subheadline)
13 | }
14 | .padding()
15 | .frame(maxWidth: .infinity)
16 | .background(
17 | RoundedRectangle(cornerRadius: 12).fill(.green.opacity(0.3)))
18 | .padding(.horizontal)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Component/PathIndicator.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct PathIndicator: View {
4 | let currentPath: String
5 |
6 | public var body: some View {
7 | VStack(spacing: 16) {
8 | Text("currentPath")
9 | .font(.headline)
10 |
11 | Text(currentPath)
12 | .font(.subheadline)
13 | }
14 | .padding()
15 | .frame(maxWidth: .infinity)
16 | .background(
17 | RoundedRectangle(cornerRadius: 12).fill(.green.opacity(0.3)))
18 | .padding(.horizontal)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Component/PathIndicator.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct PathIndicator: View {
4 | let currentPath: String
5 |
6 | public var body: some View {
7 | VStack(spacing: 16) {
8 | Text("currentPath")
9 | .font(.headline)
10 |
11 | Text(currentPath)
12 | .font(.subheadline)
13 | }
14 | .padding()
15 | .frame(maxWidth: .infinity)
16 | .background(
17 | RoundedRectangle(cornerRadius: 12).fill(.green.opacity(0.3)))
18 | .padding(.horizontal)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/Shared/PageTemplate/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "PageTemplate",
8 | platforms: [.iOS(.v14)],
9 | products: [
10 | .library(
11 | name: "PageTemplate",
12 | targets: ["PageTemplate"]),
13 | ],
14 | targets: [
15 | .target(
16 | name: "PageTemplate"),
17 | .testTarget(
18 | name: "PageTemplateTests",
19 | dependencies: ["PageTemplate"]),
20 | ])
21 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Builder/RouteBuilderOf.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public typealias RouteViewController = MatchPathUsable & UIViewController
4 |
5 | // MARK: - RouteBuilderOf
6 |
7 | public struct RouteBuilderOf {
8 |
9 | let matchPath: String
10 | let routeBuild: (RootNavigatorType, String, DependencyType) -> RouteViewController?
11 |
12 | public init(
13 | matchPath: String,
14 | routeBuild: @escaping (RootNavigatorType, String, DependencyType) -> RouteViewController?)
15 | {
16 | self.matchPath = matchPath
17 | self.routeBuild = routeBuild
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleURLTypes
6 |
7 |
8 | CFBundleTypeRole
9 | Editor
10 | CFBundleURLName
11 | DeepLink
12 | CFBundleURLSchemes
13 |
14 | single-ex
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Tab1/Tab1RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab1RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab1"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | let eventSubscriber = EventSubscriber()
11 |
12 | return WrappingController(matchPath: matchPath, eventSubscriber: eventSubscriber) {
13 | Tab1Page(navigator: navigator, eventSubscriber: eventSubscriber)
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Tab2/Tab2RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab2RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab2"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | let eventSubscriber = EventSubscriber()
11 |
12 | return WrappingController(matchPath: matchPath, eventSubscriber: eventSubscriber) {
13 | Tab2Page(navigator: navigator, eventSubscriber: eventSubscriber)
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Tab3/Tab3RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab3RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab3"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | let eventSubscriber = EventSubscriber()
11 |
12 | return WrappingController(matchPath: matchPath, eventSubscriber: eventSubscriber) {
13 | Tab3Page(navigator: navigator, eventSubscriber: eventSubscriber)
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Tab4/Tab4RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab4RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "tab4"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | let eventSubscriber = EventSubscriber()
11 |
12 | return WrappingController(matchPath: matchPath, eventSubscriber: eventSubscriber) {
13 | Tab4Page(navigator: navigator, eventSubscriber: eventSubscriber)
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/BaseComponent/Subscriber/LinkNavigatorSubscriberType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// A protocol that outlines methods for receiving a dictionary item. The dictionary contains key-value pairs where both the key and value are strings.
4 | public protocol LinkNavigatorItemSubscriberProtocol {
5 |
6 | /// Receives a dictionary item and performs an action with it.
7 | ///
8 | /// The dictionary parameter contains key-value pairs where both key and value are strings.
9 | ///
10 | /// - Parameter item: A dictionary containing string keys and values.
11 | func receive(encodedItemString: String)
12 | }
13 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Home/HomeRouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct HomeRouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "home"
9 | return .init(matchPath: matchPath) { navigator, _, diContainer -> RouteViewController? in
10 | guard let env: AppDependency = diContainer.resolve() else { return .none }
11 | return WrappingController(matchPath: matchPath) {
12 | HomeView(
13 | navigator: navigator,
14 | sharedViewModel: env.sharedRootViewModel)
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/AppRouterBuilderGroup.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | // MARK: - AppRouterBuilderGroup
4 |
5 | struct AppRouterBuilderGroup {
6 | init() { }
7 | }
8 |
9 | extension AppRouterBuilderGroup {
10 | @MainActor
11 | func routers() -> [RouteBuilderOf] {
12 | [
13 | Tab1RouteBuilder().generate(),
14 | Tab2RouteBuilder().generate(),
15 | Tab3RouteBuilder().generate(),
16 | Tab4RouteBuilder().generate(),
17 | Step1RouteBuilder().generate(),
18 | Step2RouteBuilder().generate(),
19 | Step3RouteBuilder().generate(),
20 | Step4RouteBuilder().generate(),
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/DeepLink/DeepLinkParser.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import LinkNavigator
3 | import URLEncodedForm
4 |
5 | enum DeepLinkParser {
6 | static func parse(url: URL, completeAction: @escaping (LinkItem?) -> Void) {
7 | guard let component = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
8 | completeAction(.none)
9 | return
10 | }
11 |
12 | let pathList = component.path.split(separator: "/").map(String.init)
13 | let item = try? URLEncodedFormDecoder().decode(DeepLinkItem.self, from: component.query ?? "")
14 | completeAction(.init(pathList: pathList, items: item))
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Tab1/Tab1Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab1Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Next to 'Step1'")
17 | }
18 |
19 | Spacer()
20 | }
21 | .onAppear {
22 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Tab2/Tab2Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab2Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Next to 'Step1'")
17 | }
18 |
19 | Spacer()
20 | }
21 | .onAppear {
22 | currentPath = navigator.getCurrentPaths().joined(separator: " -> ")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Tab3/Tab3Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab3Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Next to 'Step1'")
17 | }
18 |
19 | Spacer()
20 | }
21 | .onAppear {
22 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Tab4/Tab4Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab4Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Next to 'Step1'")
17 | }
18 |
19 | Spacer()
20 | }
21 | .onAppear {
22 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Page2/Page2RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page2RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "page2"
9 | return .init(matchPath: matchPath) { navigator, _, _ -> RouteViewController? in
10 | let linkSubscriber = Page2LinkSubscriber()
11 | return WrappingController(
12 | matchPath: matchPath,
13 | eventSubscriber: linkSubscriber)
14 | {
15 | Page2View(
16 | navigator: navigator,
17 | linkSubscriber: linkSubscriber)
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/AppRouterBuilderGroup.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | // MARK: - AppRouterBuilderGroup
4 |
5 | struct AppRouterBuilderGroup {
6 | init() { }
7 | }
8 |
9 | extension AppRouterBuilderGroup {
10 | @MainActor
11 | func routers() -> [RouteBuilderOf] {
12 | [
13 | Tab1RouteBuilder().generate(),
14 | Tab2RouteBuilder().generate(),
15 | Tab3RouteBuilder().generate(),
16 | Tab4RouteBuilder().generate(),
17 | Step1RouteBuilder().generate(),
18 | Step2RouteBuilder().generate(),
19 | Step3RouteBuilder().generate(),
20 | Step4RouteBuilder().generate(),
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/AppRouteBuilderGroup.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 |
3 | // MARK: - AppRouterBuilderGroup
4 |
5 | struct AppRouterBuilderGroup {
6 | init() { }
7 | }
8 |
9 | extension AppRouterBuilderGroup {
10 | @MainActor
11 | func routers() -> [RouteBuilderOf] {
12 | [
13 | Tab1RouteBuilder().generate(),
14 | Tab2RouteBuilder().generate(),
15 | Tab3RouteBuilder().generate(),
16 | Tab4RouteBuilder().generate(),
17 | Step1RouteBuilder().generate(),
18 | Step2RouteBuilder().generate(),
19 | Step3RouteBuilder().generate(),
20 | Step4RouteBuilder().generate(),
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Example.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-log",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/apple/swift-log",
7 | "state" : {
8 | "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
9 | "version" : "1.5.4"
10 | }
11 | },
12 | {
13 | "identity" : "urlencodedform",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/interactord/URLEncodedForm",
16 | "state" : {
17 | "revision" : "5f51aa13d41b26fc8833cd040df3a3101768bb64",
18 | "version" : "1.0.8"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Tab1/Tab1Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab1Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Next to 'Step1'")
17 | }
18 |
19 | Spacer()
20 | }
21 | .onAppear {
22 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Tab2/Tab2Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab2Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Next to 'Step1'")
17 | }
18 |
19 | Spacer()
20 | }
21 | .onAppear {
22 | currentPath = navigator.getCurrentPaths().joined(separator: " -> ")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Tab3/Tab3Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab3Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Next to 'Step1'")
17 | }
18 |
19 | Spacer()
20 | }
21 | .onAppear {
22 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Tab4/Tab4Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab4Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Next to 'Step1'")
17 | }
18 |
19 | Spacer()
20 | }
21 | .onAppear {
22 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Page/Game/GameView.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct GameView: View {
5 |
6 | let navigator: SingleLinkNavigator
7 | let injectionData: GameInjectionData?
8 |
9 | var gameTitle: String {
10 | injectionData?.gameTitle ?? ""
11 | }
12 |
13 | var body: some View {
14 | VStack(spacing: 30) {
15 | PathIndicator(currentPath: navigator.getCurrentPaths().joined(separator: " -> "))
16 | .padding(.top, 32)
17 |
18 | Text("\(gameTitle) 게임")
19 |
20 | Spacer()
21 |
22 | Button(action: {
23 | navigator.back(isAnimated: true)
24 | }) {
25 | Text("게임 종료")
26 | }
27 | }
28 | .padding()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/Core/TabLinkNavigator/TabRootNavigationController.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public class TabRootNavigationController: MatchPathUsable {
5 |
6 | // MARK: Lifecycle
7 |
8 | public init(
9 | matchPath: String,
10 | eventSubscriber: LinkNavigatorItemSubscriberProtocol? = nil,
11 | navigationController: UINavigationController = .init())
12 | {
13 | self.matchPath = matchPath
14 | self.eventSubscriber = eventSubscriber
15 | self.navigationController = navigationController
16 | }
17 |
18 | // MARK: Public
19 |
20 | public var matchPath: String
21 | public var eventSubscriber: LinkNavigatorItemSubscriberProtocol?
22 | public var navigationController: UINavigationController
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Page/Game/GameRoutebuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | // MARK: - GameRouteBuilder
5 |
6 | struct GameRouteBuilder {
7 |
8 | @MainActor
9 | func generate() -> RouteBuilderOf {
10 | let matchPath = "game"
11 | return .init(matchPath: matchPath) { navigator, items, _ -> RouteViewController? in
12 | let query: GameInjectionData? = items.decoded()
13 | return WrappingController(matchPath: matchPath) {
14 | GameView(navigator: navigator, injectionData: query)
15 | }
16 | }
17 | }
18 | }
19 |
20 | // MARK: - GameInjectionData
21 |
22 | struct GameInjectionData: Equatable, Codable {
23 | let gameTitle: String
24 | }
25 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Page2/Page2LinkSubscriber.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 | import LinkNavigator
3 |
4 | // MARK: - Page2LinkSubscriber
5 |
6 | class Page2LinkSubscriber: ObservableObject {
7 |
8 | // MARK: Lifecycle
9 |
10 | deinit {
11 | LogManager.default.debug("Page2LinkSubscriber deinit...")
12 | }
13 |
14 | // MARK: Internal
15 |
16 | @Published var linkAction: Page3View.Page2InjectionData? = .none
17 |
18 | }
19 |
20 | // MARK: LinkNavigatorItemSubscriberProtocol
21 |
22 | extension Page2LinkSubscriber: LinkNavigatorItemSubscriberProtocol {
23 | func receive(encodedItemString: String) {
24 | if let scope: Page3View.Page2InjectionData? = encodedItemString.decoded() {
25 | linkAction = scope
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Common/Step3/Step3Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step3Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step4"), isAnimated: true) }) {
16 | Text("Next to 'Step4'")
17 | }
18 |
19 | Button(action: { navigator.back(isAnimated: true) }) {
20 | Text("Back")
21 | }
22 |
23 | Spacer()
24 | }
25 | .onAppear {
26 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Common/Step3/Step3Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step3Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step4"), isAnimated: true) }) {
16 | Text("Next to 'Step4'")
17 | }
18 |
19 | Button(action: { navigator.back(isAnimated: true) }) {
20 | Text("Back")
21 | }
22 |
23 | Spacer()
24 | }
25 | .onAppear {
26 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Common/Step1/Step1Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step1Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step2"), isAnimated: true) }) {
16 | Text("Next to 'Step2'")
17 | }
18 |
19 | Button(action: { navigator.close(isAnimated: true, completeAction: { }) }) {
20 | Text("Close Sheet")
21 | }
22 |
23 | Spacer()
24 | }
25 | .onAppear {
26 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/01-SingleBasic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "bb9e2737c1f97e916f9e4c704318f739665f2247eb5a4813ef801c95711336a5",
3 | "pins" : [
4 | {
5 | "identity" : "swift-log",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/apple/swift-log",
8 | "state" : {
9 | "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
10 | "version" : "1.6.1"
11 | }
12 | },
13 | {
14 | "identity" : "urlencodedform",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/interactord/URLEncodedForm",
17 | "state" : {
18 | "revision" : "5f51aa13d41b26fc8833cd040df3a3101768bb64",
19 | "version" : "1.0.8"
20 | }
21 | }
22 | ],
23 | "version" : 3
24 | }
25 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Page1/Page1RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page1RouteBuilder {
5 |
6 | @MainActor
7 | func generate() -> RouteBuilderOf {
8 | let matchPath = "page1"
9 | return .init(matchPath: matchPath) { navigator, items, diContainer -> RouteViewController? in
10 | guard let env: AppDependency = diContainer.resolve() else { return .none }
11 | let query: HomeToPage1Item? = items.decoded()
12 | let deepLinkItem: DeepLinkItem? = items.decoded()
13 | return WrappingController(matchPath: matchPath) {
14 | Page1View(
15 | navigator: navigator,
16 | item: query,
17 | deepLinkItem: deepLinkItem,
18 | sharedViewModel: env.sharedRootViewModel)
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Common/Step2/Step2RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | // MARK: - Step2RouteBuilder
5 |
6 | struct Step2RouteBuilder {
7 |
8 | @MainActor
9 | func generate() -> RouteBuilderOf {
10 | let matchPath = "step2"
11 | return .init(matchPath: matchPath) { navigator, items, _ -> RouteViewController? in
12 | let param: Step2InjectionData = items.decoded() ?? .init(message: "")
13 | return WrappingController(matchPath: matchPath) {
14 | Step2Page(navigator: navigator, message: param.message)
15 | }
16 | }
17 | }
18 | }
19 |
20 | // MARK: - Step2InjectionData
21 |
22 | public struct Step2InjectionData: Codable {
23 | public let message: String
24 |
25 | public init(message: String) {
26 | self.message = message
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Common/Step2/Step2RouteBuilder.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | // MARK: - Step2RouteBuilder
5 |
6 | struct Step2RouteBuilder {
7 |
8 | @MainActor
9 | func generate() -> RouteBuilderOf {
10 | let matchPath = "step2"
11 | return .init(matchPath: matchPath) { navigator, items, _ -> RouteViewController? in
12 | let param: Step2InjectionData = items.decoded() ?? .init(message: "")
13 |
14 | return WrappingController(matchPath: matchPath) {
15 | Step2Page(navigator: navigator, injectionData: param)
16 | }
17 | }
18 | }
19 | }
20 |
21 | // MARK: - Step2InjectionData
22 |
23 | public struct Step2InjectionData: Codable {
24 | public let message: String
25 |
26 | public init(message: String) {
27 | self.message = message
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.8
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "LinkNavigator",
8 | platforms: [
9 | .iOS(.v13),
10 | ],
11 | products: [
12 | .library(
13 | name: "LinkNavigator",
14 | targets: ["LinkNavigator"]),
15 | ],
16 | dependencies: [
17 | .package(
18 | url: "https://github.com/interactord/URLEncodedForm",
19 | .upToNextMajor(from: "1.0.8")),
20 | .package(
21 | url: "https://github.com/airbnb/swift",
22 | .upToNextMajor(from: "1.0.6")),
23 | ],
24 | targets: [
25 | .target(
26 | name: "LinkNavigator",
27 | dependencies: [
28 | "URLEncodedForm",
29 | ]),
30 | .testTarget(
31 | name: "LinkNavigatorTests",
32 | dependencies: ["LinkNavigator"]),
33 | ])
34 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Page/Home/HomeView.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct HomeView: View {
5 |
6 | // MARK: Internal
7 |
8 | let navigator: SingleLinkNavigator
9 |
10 | var body: some View {
11 | VStack(spacing: 30) {
12 | PathIndicator(currentPath: navigator.getCurrentPaths().joined(separator: " -> "))
13 | .padding(.top, 32)
14 |
15 | Button(action: {
16 | navigator.sheet(linkItem: .init(path: "login"), isAnimated: true)
17 | }) {
18 | Text("go to Login")
19 | }
20 |
21 | Spacer()
22 | }
23 | .padding()
24 | .onAppear {
25 | if isLogin {
26 | navigator.replace(linkItem: .init(path: "newGame"), isAnimated: true)
27 | }
28 | }
29 | }
30 |
31 | // MARK: Private
32 |
33 | @State private var isLogin = UserDefaults.standard.bool(forKey: "isLogin")
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/airbnb/swift",
7 | "state" : {
8 | "revision" : "bc6aa7c3e21b6ab951ce75afc0a6e6d16fd6caef",
9 | "version" : "1.0.6"
10 | }
11 | },
12 | {
13 | "identity" : "swift-argument-parser",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/apple/swift-argument-parser",
16 | "state" : {
17 | "revision" : "c8ed701b513cf5177118a175d85fbbbcd707ab41",
18 | "version" : "1.3.0"
19 | }
20 | },
21 | {
22 | "identity" : "urlencodedform",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/interactord/URLEncodedForm",
25 | "state" : {
26 | "revision" : "5f51aa13d41b26fc8833cd040df3a3101768bb64",
27 | "version" : "1.0.8"
28 | }
29 | }
30 | ],
31 | "version" : 2
32 | }
33 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/SingleEventSubscriberApp.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | @main
5 | struct SingleEventSubscriberApp: App {
6 | let singleNavigator = SingleLinkNavigator(
7 | routeBuilderItemList: AppRouterGroup().routers(),
8 | dependency: AppDependency(sharedRootViewModel: .init()))
9 |
10 | var body: some Scene {
11 | WindowGroup {
12 | LinkNavigationView(
13 | linkNavigator: singleNavigator,
14 | item: .init(path: "home"))
15 | .ignoresSafeArea()
16 | .onOpenURL { url in
17 | DeepLinkParser.parse(url: url) { linkItem in
18 | guard let linkItem else { return }
19 | singleNavigator.getCurrentPaths().isEmpty
20 | ? singleNavigator.next(linkItem: .init(path: "home"), isAnimated: true)
21 | : singleNavigator.replace(linkItem: linkItem, isAnimated: true)
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Common/Step2/Step2Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step2Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step3"), isAnimated: true) }) {
16 | Text("Next to 'Step3'")
17 | }
18 |
19 | Button(action: { navigator.fullSheet(linkItem: .init(path: "step1"), isAnimated: true, prefersLargeTitles: false) }) {
20 | Text("Open Full-Sheet 'Step1'")
21 | }
22 |
23 | Button(action: { navigator.back(isAnimated: true) }) {
24 | Text("Back")
25 | }
26 |
27 | Spacer()
28 | }
29 | .onAppear {
30 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/EventSubscriber.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 | import LinkNavigator
3 |
4 | // MARK: - EventSubscriber
5 |
6 | class EventSubscriber {
7 |
8 | // MARK: Lifecycle
9 |
10 | deinit {
11 | LogManager.default.debug("EventSubscriber deinit...")
12 | }
13 |
14 | // MARK: Internal
15 |
16 | let action: PassthroughSubject = .init()
17 |
18 | }
19 |
20 | // MARK: LinkNavigatorItemSubscriberProtocol
21 |
22 | extension EventSubscriber: LinkNavigatorItemSubscriberProtocol {
23 | func receive(encodedItemString: String) {
24 | if let scope: EventParam = encodedItemString.decoded() {
25 | action.send(scope)
26 | }
27 | }
28 | }
29 |
30 | // MARK: - EventParam
31 |
32 | public struct EventParam: Codable {
33 | public let action: Action
34 |
35 | public init(action: Action) {
36 | self.action = action
37 | }
38 | }
39 |
40 | // MARK: EventParam.Action
41 |
42 | extension EventParam {
43 | public enum Action: Codable {
44 | case sendMessage(String)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Components/WrappingController.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | // MARK: - MatchPathUsable
4 |
5 | public protocol MatchPathUsable {
6 | var matchPath: String { get }
7 | var eventSubscriber: LinkNavigatorItemSubscriberProtocol? { get }
8 | }
9 |
10 | // MARK: - WrappingController
11 |
12 | public final class WrappingController: UIHostingController, MatchPathUsable {
13 |
14 | // MARK: Lifecycle
15 |
16 | public init(
17 | matchPath: String,
18 | title: String? = .none,
19 | eventSubscriber: LinkNavigatorItemSubscriberProtocol? = .none,
20 | @ViewBuilder content: () -> Content)
21 | {
22 | self.matchPath = matchPath
23 | self.eventSubscriber = eventSubscriber
24 | super.init(rootView: content())
25 | super.title = title ?? matchPath
26 | }
27 |
28 | required init?(coder _: NSCoder) {
29 | fatalError("init(coder:) has not been implemented")
30 | }
31 |
32 | // MARK: Public
33 |
34 | public let matchPath: String
35 | public let eventSubscriber: LinkNavigatorItemSubscriberProtocol?
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Common/Step3/Step3Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step3Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step4"), isAnimated: true) }) {
16 | Text("Next to 'Step4'")
17 | }
18 |
19 | Button(action: {
20 | navigator.reloadLast(
21 | linkItem: .init(path: "step2", items: Step2InjectionData(message: "Replaced message!")),
22 | isAnimated: true)
23 | }) {
24 | Text("Replaced 'Step2' message")
25 | }
26 |
27 | Button(action: { navigator.back(isAnimated: true) }) {
28 | Text("Back")
29 | }
30 |
31 | Spacer()
32 | }
33 | .onAppear {
34 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/Protocol/LinkNavigatorFindLocationUsable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// MARK: - LinkNavigatorFindLocationUsable
4 | ///
5 | /// A protocol that defines interfaces for retrieving the current paths in a router page navigation.
6 | public protocol LinkNavigatorFindLocationUsable {
7 |
8 | /// Retrieves the current paths in the router page.
9 | ///
10 | /// This method is used to obtain the current paths that are being navigated in the router page.
11 | /// It returns an array of strings representing the paths.
12 | ///
13 | /// - Returns: An array of strings representing the current paths.
14 | func getCurrentPaths() -> [String]
15 |
16 | /// Retrieves the root current paths in the router page.
17 | ///
18 | /// This method is used to obtain the root paths that are being navigated in the router page.
19 | /// Similar to `getCurrentPaths`, but returns the root paths. It returns an array of strings representing the paths.
20 | ///
21 | /// - Returns: An array of strings representing the root current paths.
22 | func getCurrentRootPaths() -> [String]
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Page4/Page4View.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page4View: View {
5 |
6 | // MARK: Internal
7 |
8 | let navigator: SingleLinkNavigator
9 |
10 | var body: some View {
11 | VStack(spacing: 30) {
12 | PathIndicator(currentPath: paths.joined(separator: " -> "))
13 | .padding(.top, 32)
14 |
15 | Button(action: {
16 | navigator.backOrNext(linkItem: .init(path: "home"), isAnimated: true)
17 | }) {
18 | Text("back to Home")
19 | }
20 |
21 | Button(action: {
22 | navigator.back(isAnimated: true)
23 | }) {
24 | Text("back")
25 | }
26 |
27 | Button(action: {
28 | navigator.replace(linkItem: .init(path: "home"), isAnimated: false)
29 | }) {
30 | Text("reset")
31 | .foregroundColor(.red)
32 | }
33 |
34 | Spacer()
35 | }
36 | .padding()
37 | .onAppear {
38 | paths = navigator.getCurrentPaths()
39 | }
40 | }
41 |
42 | // MARK: Private
43 |
44 | @State private var paths: [String] = []
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/BaseComponent/NavigationTarget.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// `NavigationTarget` enum defines the target where a navigation action should be directed in a `LinkNavigator`.
4 | /// This enum is a part of a navigation handling system where the concept of 'Base' and 'Sub' is used to manage navigation flows more efficiently.
5 | /// 'Base' refers to a UINavigationViewController instance whereas 'Sub' refers to a UIViewController that is presented using UINavigationViewController's present function like alerts, sheets, modals etc.
6 | public enum NavigationTarget: Equatable {
7 |
8 | /// The default case is used when it is not determined whether the current screen is 'Base' or 'Sub', or when it needs to automatically decide the appropriate target to present.
9 | case `default`
10 |
11 | /// The root case refers to the base UINavigationViewController which serves as the main navigation controller.
12 | case root
13 |
14 | /// The sub case is used to refer to a UIViewController instance that is presented using the present method of UINavigationViewController like alerts, sheets, modals etc.
15 | case sub
16 | }
17 |
--------------------------------------------------------------------------------
/Example.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
17 |
20 |
22 |
23 |
25 |
26 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Page3/Page3View.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | // MARK: - Page3View
5 |
6 | struct Page3View: View {
7 |
8 | let navigator: SingleLinkNavigator
9 | @State var message = ""
10 |
11 | var body: some View {
12 | VStack(spacing: 30) {
13 | PathIndicator(currentPath: navigator.getCurrentPaths().joined(separator: " -> "))
14 | .padding(.top, 32)
15 |
16 | VStack(spacing: 16) {
17 | TextField("Type message here", text: $message)
18 | .textFieldStyle(.roundedBorder)
19 | .padding(.horizontal)
20 |
21 | Button(action: {
22 | navigator.rootSend(
23 | item: .init(
24 | path: "page2",
25 | items: Page2InjectionData(message: message)))
26 | navigator.back(isAnimated: true)
27 |
28 | }) {
29 | Text("back with Message")
30 | }
31 | }
32 |
33 | Spacer()
34 | }
35 | .padding()
36 | }
37 | }
38 |
39 | // MARK: Page3View.Page2InjectionData
40 |
41 | extension Page3View {
42 | struct Page2InjectionData: Equatable, Codable {
43 | let message: String
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Common/Step1/Step1Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step1Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.next(linkItem: .init(path: "step2"), isAnimated: true) }) {
16 | Text("Next to 'Step2'")
17 | }
18 |
19 | Button(action: { navigator.close(isAnimated: true, completeAction: { }) }) {
20 | Text("Close Sheet")
21 | }
22 |
23 | Button(action: {
24 | navigator.alert(model: .init(
25 | title: "Test Title",
26 | message: "Message",
27 | buttons: [
28 | .init(
29 | title: "OK",
30 | style: .default),
31 | ],
32 | flagType: .default))
33 | }) {
34 | Text("Show Alert")
35 | }
36 |
37 | Spacer()
38 | }
39 | .onAppear {
40 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Page/Login/LoginView.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct LoginView: View {
5 |
6 | // MARK: Internal
7 |
8 | let navigator: SingleLinkNavigator
9 |
10 | var body: some View {
11 | VStack(spacing: 30) {
12 | PathIndicator(currentPath: navigator.getCurrentPaths().joined(separator: " -> "))
13 | .padding(.top, 32)
14 |
15 | VStack(spacing: 16) {
16 | TextField("id", text: $message)
17 | .textFieldStyle(.roundedBorder)
18 | .padding(.horizontal)
19 |
20 | SecureField("password", text: $password)
21 | .textFieldStyle(.roundedBorder)
22 | .padding(.horizontal)
23 |
24 | Button(action: {
25 | UserDefaults.standard.setValue(true, forKey: "isLogin")
26 | navigator.close(isAnimated: true) {
27 | navigator.rootReloadLast(items: .init(path: "home"), isAnimated: true)
28 | }
29 | }) {
30 | Text("Login")
31 | }
32 | }
33 | Spacer()
34 | }
35 | .padding()
36 | }
37 |
38 | // MARK: Private
39 |
40 | @State private var message = ""
41 | @State private var password = ""
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Licensed under the **MIT** license
2 |
3 | > Copyright (c) 2022 Scott moon (via: interactord@gmail.com)
4 | >
5 | > Permission is hereby granted, free of charge, to any person obtaining
6 | > a copy of this software and associated documentation files (the
7 | > "Software"), to deal in the Software without restriction, including
8 | > without limitation the rights to use, copy, modify, merge, publish,
9 | > distribute, sublicense, and/or sell copies of the Software, and to
10 | > permit persons to whom the Software is furnished to do so, subject to
11 | > the following conditions:
12 | >
13 | > The above copyright notice and this permission notice shall be
14 | > included in all copies or substantial portions of the Software.
15 | >
16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Pages/Common/Step4/Step4Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step4Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.sheet(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Open 'Step1' sheet")
17 | }
18 |
19 | Button(action: { navigator.back(isAnimated: true) }) {
20 | Text("Back")
21 | }
22 |
23 | Button(action: {
24 | navigator.remove(pathList: ["step2", "step3"])
25 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
26 | }) {
27 | Text("Remove path 'Step2, Step3'")
28 | }
29 |
30 | Button(action: { navigator.backOrNext(linkItem: .init(path: navigator.getCurrentRootPaths().first ?? ""), isAnimated: true)
31 | }) {
32 | Text("Back to root")
33 | }
34 |
35 | Spacer()
36 | }
37 | .onAppear {
38 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/03-SingleTicTacToe/SingleTicTacToe/Page/NewGame/NewGameView.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct NewGameView: View {
5 |
6 | let navigator: SingleLinkNavigator
7 | @State var gameTitle = ""
8 |
9 | var body: some View {
10 | VStack(spacing: 30) {
11 | PathIndicator(currentPath: navigator.getCurrentPaths().joined(separator: " -> "))
12 | .padding(.top, 32)
13 |
14 | VStack(spacing: 16) {
15 | Text("새로운 게임 생성")
16 | TextField("Type message here", text: $gameTitle)
17 | .textFieldStyle(.roundedBorder)
18 | .padding(.horizontal)
19 |
20 | Button(action: {
21 | navigator.next(
22 | linkItem: .init(
23 | path: "game",
24 | items: GameInjectionData(gameTitle: gameTitle)),
25 | isAnimated: true)
26 | }) {
27 | Text("게임 시작")
28 | }
29 | }
30 |
31 | Spacer()
32 |
33 | Button(action: {
34 | UserDefaults.standard.setValue(false, forKey: "isLogin")
35 | navigator.replace(linkItem: .init(path: "home"), isAnimated: true)
36 | }) {
37 | Text("logout")
38 | }
39 |
40 | Spacer()
41 | }
42 | .padding()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Common/Step4/Step4Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step4Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.sheet(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Open 'Step1' sheet")
17 | }
18 |
19 | Button(action: { navigator.back(isAnimated: true) }) {
20 | Text("Back")
21 | }
22 |
23 | Button(action: {
24 | navigator.remove(pathList: ["step2", "step3"])
25 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
26 | }) {
27 | Text("Remove path 'Step2, Step3'")
28 | }
29 |
30 | Button(action: { navigator.backOrNext(linkItem: .init(path: navigator.getCurrentRootPaths().first ?? ""), isAnimated: true)
31 | }) {
32 | Text("Back to root")
33 | }
34 |
35 | Spacer()
36 | }
37 | .onAppear {
38 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Common/Step4/Step4Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step4Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 |
8 | var body: some View {
9 | VStack(spacing: 16) {
10 | PathIndicator(currentPath: currentPath)
11 | .padding(.top, 32)
12 |
13 | Spacer()
14 |
15 | Button(action: { navigator.sheet(linkItem: .init(path: "step1"), isAnimated: true) }) {
16 | Text("Open 'Step1' sheet")
17 | }
18 |
19 | Button(action: { navigator.back(isAnimated: true) }) {
20 | Text("Back")
21 | }
22 |
23 | Button(action: {
24 | navigator.remove(pathList: ["step2", "step3"])
25 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
26 | }) {
27 | Text("Remove path 'Step2, Step3'")
28 | }
29 |
30 | Button(action: { navigator.backOrNext(linkItem: .init(path: navigator.getCurrentRootPaths().first ?? ""), isAnimated: true)
31 | }) {
32 | Text("Back to root")
33 | }
34 |
35 | Spacer()
36 | }
37 | .onAppear {
38 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Page2/Page2View.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page2View: View {
5 |
6 | // MARK: Internal
7 |
8 | let navigator: SingleLinkNavigator
9 |
10 | var body: some View {
11 | VStack(spacing: 30) {
12 | PathIndicator(currentPath: paths.joined(separator: " -> "))
13 | .padding(.top, 32)
14 |
15 | Button(action: {
16 | navigator.next(linkItem: .init(path: "page3"), isAnimated: true)
17 | }) {
18 | Text("go to next Page")
19 | }
20 |
21 | Button(action: {
22 | navigator.rootNext(linkItem: .init(path: "page3"), isAnimated: true)
23 | }) {
24 | Text("**root** next")
25 | }
26 |
27 | Button(action: {
28 | navigator.remove(pathList: ["page1"])
29 | paths = navigator.getCurrentPaths()
30 | }) {
31 | Text("remove Page 1")
32 | .foregroundColor(.red)
33 | }
34 |
35 | Button(action: {
36 | navigator.back(isAnimated: true)
37 | }) {
38 | Text("back")
39 | }
40 |
41 | Spacer()
42 | }
43 | .padding()
44 | .onAppear {
45 | paths = navigator.getCurrentPaths()
46 | }
47 | }
48 |
49 | // MARK: Private
50 |
51 | @State private var paths: [String] = []
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Components/TabLinkNavigationView.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 |
4 | // MARK: - TabLinkNavigationView
5 |
6 | public struct TabLinkNavigationView {
7 | let linkNavigator: TabLinkNavigator
8 | let isHiddenDefaultTabbar: Bool
9 | let tabItemList: [TabItem]
10 | let isAnimatedForUpdateTabbar: Bool
11 |
12 | public init(
13 | linkNavigator: TabLinkNavigator,
14 | isHiddenDefaultTabbar: Bool,
15 | tabItemList: [TabItem],
16 | isAnimatedForUpdateTabbar: Bool = false)
17 | {
18 | self.linkNavigator = linkNavigator
19 | self.isHiddenDefaultTabbar = isHiddenDefaultTabbar
20 | self.tabItemList = tabItemList
21 | self.isAnimatedForUpdateTabbar = isAnimatedForUpdateTabbar
22 | }
23 | }
24 |
25 | // MARK: UIViewControllerRepresentable
26 |
27 | extension TabLinkNavigationView: UIViewControllerRepresentable {
28 | public func makeUIViewController(context _: Context) -> UITabBarController {
29 | UITabBarController()
30 | }
31 |
32 | public func updateUIViewController(_ uiViewController: UITabBarController, context _: Context) {
33 | uiViewController.setViewControllers(linkNavigator.launch(tagItemList: tabItemList), animated: isAnimatedForUpdateTabbar)
34 | uiViewController.tabBar.isHidden = isHiddenDefaultTabbar
35 | linkNavigator.mainController = uiViewController
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Common/Step1/Step1Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step1Page: View {
5 | let navigator: TabPartialNavigator
6 | @State var currentPath = ""
7 | @State var message = ""
8 |
9 | var nextTabPath: String {
10 | switch navigator.getCurrentPaths().first ?? "tab1" {
11 | case "tab1": "tab2"
12 | case "tab2": "tab3"
13 | case "tab3": "tab4"
14 | case "tab4": "tab1"
15 | default: "tab1"
16 | }
17 | }
18 |
19 | var body: some View {
20 | VStack(spacing: 16) {
21 | PathIndicator(currentPath: currentPath)
22 | .padding(.top, 32)
23 |
24 | TextField("Type message here", text: $message)
25 | .textFieldStyle(.roundedBorder)
26 | .padding(.horizontal)
27 |
28 | Spacer()
29 |
30 | Button(action: { navigator.next(linkItem: .init(
31 | path: "step2",
32 | items: Step2InjectionData(message: message)), isAnimated: true) })
33 | {
34 | Text("Next to 'Step2'")
35 | }
36 |
37 | Button(action: { navigator.close(isAnimated: true, completeAction: { }) }) {
38 | Text("Close Sheet")
39 | }
40 |
41 | Spacer()
42 | }
43 | .onAppear {
44 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Page2/Page2View.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page2View: View {
5 |
6 | // MARK: Lifecycle
7 |
8 | init(
9 | navigator: SingleLinkNavigator,
10 | linkSubscriber: Page2LinkSubscriber)
11 | {
12 | self.navigator = navigator
13 | self.linkSubscriber = linkSubscriber
14 | }
15 |
16 | // MARK: Internal
17 |
18 | var body: some View {
19 | VStack(spacing: 30) {
20 | PathIndicator(currentPath: navigator.getCurrentPaths().joined(separator: " -> "))
21 | .padding(.top, 32)
22 |
23 | Button(action: {
24 | navigator.backOrNext(linkItem: .init(path: "page3"), isAnimated: true)
25 | }) {
26 | Text("go to next Page")
27 | }
28 |
29 | GroupBox {
30 | VStack(spacing: 10) {
31 | HStack {
32 | Image(systemName: "envelope")
33 | Text("page3 event handler")
34 | }
35 | .font(.footnote)
36 | .foregroundColor(.secondary)
37 |
38 | Text(linkSubscriber.linkAction?.message ?? "-")
39 | }
40 | }
41 |
42 | Spacer()
43 | }
44 | .padding()
45 | }
46 |
47 | // MARK: Private
48 |
49 | private let navigator: SingleLinkNavigator
50 | @ObservedObject private var linkSubscriber: Page2LinkSubscriber
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/Pages/Common/Step2/Step2Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step2Page: View {
5 | let navigator: TabPartialNavigator
6 | let message: String
7 | @State var currentPath = ""
8 |
9 | var body: some View {
10 | VStack(spacing: 16) {
11 | PathIndicator(currentPath: currentPath)
12 | .padding(.top, 32)
13 |
14 | GroupBox {
15 | VStack(spacing: 10) {
16 | HStack {
17 | Image(systemName: "envelope")
18 | Text("injected message from Step1")
19 | }
20 | .font(.footnote)
21 | .foregroundColor(.secondary)
22 |
23 | Text(message)
24 | }
25 | }
26 |
27 | Spacer()
28 |
29 | Button(action: { navigator.next(linkItem: .init(path: "step3"), isAnimated: true) }) {
30 | Text("Next to 'Step3'")
31 | }
32 |
33 | Button(action: { navigator.fullSheet(linkItem: .init(path: "step1"), isAnimated: true, prefersLargeTitles: false) }) {
34 | Text("Open Full-Sheet 'Step1'")
35 | }
36 |
37 | Button(action: { navigator.back(isAnimated: true) }) {
38 | Text("Back")
39 | }
40 |
41 | Spacer()
42 | }
43 | .onAppear {
44 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Page3/Page3View.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page3View: View {
5 |
6 | // MARK: Internal
7 |
8 | let navigator: SingleLinkNavigator
9 |
10 | var body: some View {
11 | VStack(spacing: 30) {
12 | PathIndicator(currentPath: paths.joined(separator: " -> "))
13 | .padding(.top, 32)
14 |
15 | Button(action: {
16 | navigator.next(linkItem: .init(path: "page4"), isAnimated: true)
17 | }) {
18 | Text("go to next Page")
19 | }
20 |
21 | Button(action: {
22 | navigator.remove(pathList: ["page1", "page2"])
23 | paths = navigator.getCurrentPaths()
24 | }) {
25 | Text("remove Page 1 and 2")
26 | .foregroundColor(.red)
27 | }
28 |
29 | Button(action: {
30 | navigator.back(isAnimated: true)
31 | }) {
32 | Text("back")
33 | }
34 |
35 | Button(action: {
36 | navigator.close(isAnimated: true, completeAction: { })
37 | }) {
38 | Text("close (only available in modal)")
39 | .foregroundColor(.red)
40 | }
41 |
42 | Spacer()
43 | }
44 | .padding()
45 | .onAppear {
46 | paths = navigator.getCurrentPaths()
47 | }
48 | }
49 |
50 | // MARK: Private
51 |
52 | @State private var paths: [String] = []
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Common/Step2/Step2Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Step2Page: View {
5 | let navigator: TabPartialNavigator
6 | let injectionData: Step2InjectionData
7 |
8 | @State var currentPath = ""
9 |
10 | var body: some View {
11 | VStack(spacing: 16) {
12 | PathIndicator(currentPath: currentPath)
13 | .padding(.top, 32)
14 |
15 | GroupBox {
16 | VStack(spacing: 10) {
17 | HStack {
18 | Image(systemName: "envelope")
19 | Text("injected message from send event")
20 | }
21 | .font(.footnote)
22 | .foregroundColor(.secondary)
23 |
24 | Text(injectionData.message)
25 | }
26 | }
27 |
28 | Spacer()
29 |
30 | Button(action: { navigator.next(linkItem: .init(path: "step3"), isAnimated: true) }) {
31 | Text("Next to 'Step3'")
32 | }
33 |
34 | Button(action: { navigator.fullSheet(linkItem: .init(path: "step1"), isAnimated: true, prefersLargeTitles: false) }) {
35 | Text("Open Full-Sheet 'Step1'")
36 | }
37 |
38 | Button(action: { navigator.back(isAnimated: true) }) {
39 | Text("Back")
40 | }
41 |
42 | Spacer()
43 | }
44 | .onAppear {
45 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Home/HomeView.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | // MARK: - HomeView
5 |
6 | struct HomeView: View {
7 |
8 | let navigator: SingleLinkNavigator
9 | @ObservedObject var sharedViewModel: SharedRootViewModel
10 | @State var message = ""
11 |
12 | var body: some View {
13 | VStack(spacing: 30) {
14 | PathIndicator(currentPath: navigator.getCurrentPaths().joined(separator: " -> "))
15 | .padding(.top, 32)
16 |
17 | Text("Shared Text: \(sharedViewModel.text)")
18 |
19 | VStack(spacing: 16) {
20 | TextField("Type message here", text: $message)
21 | .textFieldStyle(.roundedBorder)
22 | .padding(.horizontal)
23 |
24 | Button(action: {
25 | navigator.backOrNext(
26 | linkItem: .init(
27 | path: "page1",
28 | items: HomeToPage1Item(message: message)),
29 | isAnimated: true)
30 | }) {
31 | Text("go to next Page with Message")
32 | }
33 | }
34 |
35 | Button(action: {
36 | navigator.backOrNext(linkItem: .init(path: "page1"), isAnimated: true)
37 | }) {
38 | Text("go to next Page")
39 | }
40 |
41 | Spacer()
42 | }
43 | .padding()
44 | }
45 | }
46 |
47 | // MARK: - HomeToPage1Item
48 |
49 | struct HomeToPage1Item: Equatable, Codable {
50 | let message: String
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Deprecated/EventObserver.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 |
4 | /// If you need to update an event between each page, please use the corresponding class.
5 | ///
6 | /// -Example:
7 | /// ```swift
8 | /// struct AppDependency: DependencyType {
9 | /// let eventObserver = .init(state: .init(tabType: .home)
10 | /// }
11 | ///
12 | /// struct TabState: Equatable {
13 | /// let tabType: TabType
14 | ///
15 | /// enum TabType: Equatable {
16 | /// case home
17 | /// case search
18 | /// case setting
19 | /// }
20 | /// }
21 | ///
22 | /// .init(
23 | /// matchPath: "home",
24 | /// routeBuild: { navigator, _, env in
25 | /// WrappingController(matchPath: "home") {
26 | /// VStack {
27 | /// Spacer()
28 | /// Text("home")
29 | /// Button(action: {
30 | /// env.eventObserver.state.tabType = .search
31 | /// }) {
32 | /// Text("push")
33 | /// }
34 | /// Button(action: {
35 | /// navigator.back(isAnimated: true)
36 | /// }) {
37 | /// Text("back")
38 | /// }
39 | /// Spacer()
40 | /// }
41 | /// }
42 | /// }),
43 | ///
44 | /// ```
45 | ///
46 |
47 | public final class EventObserver: ObservableObject {
48 |
49 | // MARK: Lifecycle
50 |
51 | public init(state: State) {
52 | self.state = state
53 | }
54 |
55 | // MARK: Public
56 |
57 | @Published public var state: State {
58 | didSet {
59 | objectWillChange.send()
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Page1/Page1View.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page1View: View {
5 |
6 | // MARK: Internal
7 |
8 | let navigator: SingleLinkNavigator
9 |
10 | var body: some View {
11 | VStack(spacing: 30) {
12 | PathIndicator(currentPath: paths.joined(separator: " -> "))
13 | .padding(.top, 32)
14 |
15 | Button(action: {
16 | navigator.next(linkItem: .init(path: "page2"), isAnimated: true)
17 | }) {
18 | Text("go to next Page")
19 | }
20 |
21 | Button(action: {
22 | navigator.backOrNext(linkItem: .init(path: Bool.random() ? "home" : "page2"), isAnimated: true)
23 | }) {
24 | Text("backOrNext")
25 | }
26 |
27 | Button(action: {
28 | navigator.rootBackOrNext(linkItem: .init(path: Bool.random() ? "home" : "page2"), isAnimated: true)
29 | }) {
30 | Text("**root** backOrNext")
31 | }
32 |
33 | Button(action: {
34 | navigator.back(isAnimated: true)
35 | }) {
36 | Text("back")
37 | }
38 |
39 | Button(action: {
40 | navigator.close(isAnimated: true, completeAction: { })
41 | }) {
42 | Text("close (only available in modal)")
43 | .foregroundColor(.red)
44 | }
45 |
46 | Spacer()
47 | }
48 | .padding()
49 | .onAppear {
50 | paths = navigator.getCurrentPaths()
51 | }
52 | }
53 |
54 | // MARK: Private
55 |
56 | @State private var paths: [String] = []
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Tab1/Tab1Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab1Page: View {
5 | let navigator: TabPartialNavigator
6 | let eventSubscriber: EventSubscriber
7 |
8 | @State var currentPath = ""
9 |
10 | var body: some View {
11 | VStack(spacing: 16) {
12 | PathIndicator(currentPath: currentPath)
13 | .padding(.top, 32)
14 |
15 | Spacer()
16 |
17 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
18 | Text("Next to 'Step1'")
19 | }
20 |
21 | Button(action: {
22 | navigator.moveTab(path: "tab2")
23 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
24 | navigator.send(
25 | targetTabPath: "tab2",
26 | linkItem: .init(
27 | path: "tab2",
28 | items: EventParam(action: .sendMessage("Message From \(navigator.getCurrentPaths().first ?? "-")"))))
29 | }
30 | }) {
31 | Text("Send event and move tab to 'tab2'")
32 | }
33 |
34 | Spacer()
35 | }
36 | .onReceive(eventSubscriber.action) { event in
37 | switch event.action {
38 | case .sendMessage(let message):
39 | navigator.backOrNext(
40 | linkItem: .init(pathList: ["step1", "step2"], items: Step2InjectionData(message: message)),
41 | isAnimated: true)
42 | }
43 | }
44 | .onAppear {
45 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Tab2/Tab2Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab2Page: View {
5 | let navigator: TabPartialNavigator
6 | let eventSubscriber: EventSubscriber
7 |
8 | @State var currentPath = ""
9 |
10 | var body: some View {
11 | VStack(spacing: 16) {
12 | PathIndicator(currentPath: currentPath)
13 | .padding(.top, 32)
14 |
15 | Spacer()
16 |
17 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
18 | Text("Next to 'Step1'")
19 | }
20 |
21 | Button(action: {
22 | navigator.moveTab(path: "tab3")
23 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
24 | navigator.send(
25 | targetTabPath: "tab3",
26 | linkItem: .init(
27 | path: "tab3",
28 | items: EventParam(action: .sendMessage("Message From \(navigator.getCurrentPaths().first ?? "-")"))))
29 | }
30 | }) {
31 | Text("Send event and move tab to 'tab3'")
32 | }
33 |
34 | Spacer()
35 | }
36 | .onReceive(eventSubscriber.action) { event in
37 | switch event.action {
38 | case .sendMessage(let message):
39 | navigator.backOrNext(
40 | linkItem: .init(pathList: ["step1", "step2"], items: Step2InjectionData(message: message)),
41 | isAnimated: true)
42 | }
43 | }
44 | .onAppear {
45 | currentPath = navigator.getCurrentPaths().joined(separator: " -> ")
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Tab3/Tab3Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab3Page: View {
5 | let navigator: TabPartialNavigator
6 | let eventSubscriber: EventSubscriber
7 |
8 | @State var currentPath = ""
9 |
10 | var body: some View {
11 | VStack(spacing: 16) {
12 | PathIndicator(currentPath: currentPath)
13 | .padding(.top, 32)
14 |
15 | Spacer()
16 |
17 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
18 | Text("Next to 'Step1'")
19 | }
20 |
21 | Button(action: {
22 | navigator.moveTab(path: "tab4")
23 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
24 | navigator.send(
25 | targetTabPath: "tab4",
26 | linkItem: .init(
27 | path: "tab4",
28 | items: EventParam(action: .sendMessage("Message From \(navigator.getCurrentPaths().first ?? "-")"))))
29 | }
30 | }) {
31 | Text("Send event and move tab to 'tab4'")
32 | }
33 |
34 | Spacer()
35 | }
36 | .onReceive(eventSubscriber.action) { event in
37 | switch event.action {
38 | case .sendMessage(let message):
39 | navigator.backOrNext(
40 | linkItem: .init(pathList: ["step1", "step2"], items: Step2InjectionData(message: message)),
41 | isAnimated: true)
42 | }
43 | }
44 | .onAppear {
45 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/Pages/Tab4/Tab4Page.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Tab4Page: View {
5 | let navigator: TabPartialNavigator
6 | let eventSubscriber: EventSubscriber
7 |
8 | @State var currentPath = ""
9 |
10 | var body: some View {
11 | VStack(spacing: 16) {
12 | PathIndicator(currentPath: currentPath)
13 | .padding(.top, 32)
14 |
15 | Spacer()
16 |
17 | Button(action: { navigator.next(linkItem: .init(path: "step1"), isAnimated: true) }) {
18 | Text("Next to 'Step1'")
19 | }
20 |
21 | Button(action: {
22 | navigator.moveTab(path: "tab1")
23 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
24 | navigator.send(
25 | targetTabPath: "tab1",
26 | linkItem: .init(
27 | path: "tab1",
28 | items: EventParam(action: .sendMessage("Message From \(navigator.getCurrentPaths().first ?? "-")"))))
29 | }
30 | }) {
31 | Text("Send event and move tab to 'tab1'")
32 | }
33 |
34 | Spacer()
35 | }
36 | .onReceive(eventSubscriber.action) { event in
37 | switch event.action {
38 | case .sendMessage(let message):
39 | navigator.backOrNext(
40 | linkItem: .init(pathList: ["step1", "step2"], items: Step2InjectionData(message: message)),
41 | isAnimated: true)
42 | }
43 | }
44 | .onAppear {
45 | currentPath = navigator.getCurrentPaths().joined(separator: " > ")
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/01-TabBasic/TabBasic/TabBasicApp.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | @main
5 | struct TabBasicApp: App {
6 | let tabLinkNavigator = TabLinkNavigator(
7 | routeBuilderItemList: AppRouterBuilderGroup().routers(),
8 | dependency: AppDependency())
9 |
10 | var body: some Scene {
11 | WindowGroup {
12 | TabLinkNavigationView(
13 | linkNavigator: tabLinkNavigator,
14 | isHiddenDefaultTabbar: false,
15 | tabItemList: [
16 | .init(
17 | tag: .zero,
18 | tabItem: .init(
19 | title: "tab1",
20 | image: UIImage(systemName: "house"),
21 | selectedImage: UIImage(systemName: "house.fill")),
22 | linkItem: .init(path: "tab1")),
23 | .init(
24 | tag: 1,
25 | tabItem: .init(
26 | title: "tab2",
27 | image: UIImage(systemName: "folder"),
28 | selectedImage: UIImage(systemName: "folder.fill")),
29 | linkItem: .init(path: "tab2")),
30 | .init(
31 | tag: 2,
32 | tabItem: .init(
33 | title: "tab3",
34 | image: UIImage(systemName: "heart"),
35 | selectedImage: UIImage(systemName: "heart.fill")),
36 | linkItem: .init(path: "tab3")),
37 | .init(
38 | tag: 3,
39 | tabItem: .init(
40 | title: "tab4",
41 | image: UIImage(systemName: "person"),
42 | selectedImage: UIImage(systemName: "person.fill")),
43 | linkItem: .init(path: "tab4")),
44 | ])
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/DI/DependencyType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | // MARK: - DependencyType
4 |
5 | /// The `DependencyType` protocol is utilized to resolve dependencies in a DI container.
6 | ///
7 | /// This protocol is designed to return an instance of a specific type through the `resolve` method. It's utilized in conjunction with `RouteBuilder` to inject external functionalities, akin to the SideEffect in MVI or UseCase in Clean Architecture, into individual pages.
8 | ///
9 | /// Developers using the library can facilitate dependency injection by adding this protocol for external dependencies and injecting it into the `LinkNavigator` initialization function. This allows the injected container to be utilized during page creation through `RouteBuilder`.
10 | ///
11 | /// The `resolve` function is passed to `RouteBuilder` with a declared protocol of `DependencyType`. During the build, it allows for the transformation into the necessary DI type for use in each page.
12 | public protocol DependencyType {
13 | /// This function attempts to resolve a dependency of the specified type `T`.
14 | ///
15 | /// It tries to cast `self` to the specified type and returns it if successful. If the cast fails, it returns `nil`. This is used to fetch necessary dependencies at runtime, facilitating dependency injection and inversion of control.
16 | func resolve() -> T?
17 | }
18 |
19 | extension DependencyType {
20 | /// Default implementation of the `resolve` function.
21 | ///
22 | /// It tries to cast `self` to the specified type and returns it if successful. If the cast fails, it returns `nil`.
23 | public func resolve() -> T? {
24 | self as? T
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/Protocol/EmptyValueType.swift:
--------------------------------------------------------------------------------
1 | // import Foundation
2 | //
3 | //// MARK: - EmptyValueType
4 | //
5 | ///// A protocol that defines an `empty` property for types that conform to it.
6 | ///// This property represents the 'empty' state for that particular type, providing a standardized way to define what constitutes an 'empty' state for different types.
7 | // public protocol EmptyValueType {
8 | //
9 | // /// A property representing the 'empty' state of the conforming type.
10 | // static var empty: Self { get }
11 | // }
12 | //
13 | //// MARK: - String + EmptyValueType
14 | //
15 | ///// An extension that conforms `String` type to the `EmptyValueType` protocol.
16 | ///// By conforming to this protocol, a `String` type can represent its 'empty' state through the `empty` property, which in this case, is an empty string ("").
17 | // extension String: EmptyValueType {
18 | //
19 | // /// The representation of an 'empty' state for a `String` type, defined as an empty string ("").
20 | // public static var empty: Self { "" }
21 | // }
22 | //
23 | //// MARK: - Dictionary + EmptyValueType
24 | //
25 | ///// An extension that conforms `Dictionary` with `String` keys and `String` values to the `EmptyValueType` protocol.
26 | ///// By conforming to this protocol, a dictionary with `String` keys and `String` values can represent its 'empty' state through the `empty` property, which in this case, is an empty dictionary (`[:]`).
27 | // extension [String: String]: EmptyValueType {
28 | //
29 | // /// The representation of an 'empty' state for a dictionary with `String` keys and `String` values, defined as an empty dictionary (`[:]`).
30 | // public static var empty: [String: String] { [:] }
31 | // }
32 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/02-TabInjectionParameter/TabInjectionParameter/TabInjectionParameterApp.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import PageTemplate
3 | import SwiftUI
4 |
5 | @main
6 | struct TabInjectionParameterApp: App {
7 | let tabLinkNavigator = TabLinkNavigator(
8 | routeBuilderItemList: AppRouterBuilderGroup().routers(),
9 | dependency: AppDependency())
10 |
11 | var body: some Scene {
12 | WindowGroup {
13 | TabLinkNavigationView(
14 | linkNavigator: tabLinkNavigator,
15 | isHiddenDefaultTabbar: false,
16 | tabItemList: [
17 | .init(
18 | tag: .zero,
19 | tabItem: .init(
20 | title: "tab1",
21 | image: UIImage(systemName: "house"),
22 | selectedImage: UIImage(systemName: "house.fill")),
23 | linkItem: .init(path: "tab1")),
24 | .init(
25 | tag: 1,
26 | tabItem: .init(
27 | title: "tab2",
28 | image: UIImage(systemName: "folder"),
29 | selectedImage: UIImage(systemName: "folder.fill")),
30 | linkItem: .init(path: "tab2")),
31 | .init(
32 | tag: 2,
33 | tabItem: .init(
34 | title: "tab3",
35 | image: UIImage(systemName: "heart"),
36 | selectedImage: UIImage(systemName: "heart.fill")),
37 | linkItem: .init(path: "tab3")),
38 | .init(
39 | tag: 3,
40 | tabItem: .init(
41 | title: "tab4",
42 | image: UIImage(systemName: "person"),
43 | selectedImage: UIImage(systemName: "person.fill")),
44 | linkItem: .init(path: "tab4")),
45 | ])
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/02-SingleEventSubscriber/SingleEventSubscriber/Page/Page1/Page1View.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct Page1View: View {
5 |
6 | let navigator: SingleLinkNavigator
7 | let item: HomeToPage1Item?
8 | let deepLinkItem: DeepLinkItem?
9 | @ObservedObject var sharedViewModel: SharedRootViewModel
10 |
11 | var body: some View {
12 | VStack(spacing: 30) {
13 | PathIndicator(currentPath: navigator.getCurrentPaths().joined(separator: " -> "))
14 | .padding(.top, 32)
15 |
16 | Text("Shared Text: \(sharedViewModel.text)")
17 |
18 | Button(action: { sharedViewModel.update(text: "Page1View Updated!!") }) {
19 | Text("Change Shared ViewModel Text")
20 | }
21 |
22 | Button(action: { navigator.back(isAnimated: true) }) {
23 | Text("Back")
24 | }
25 |
26 | GroupBox {
27 | VStack(spacing: 10) {
28 | HStack {
29 | Image(systemName: "envelope")
30 | Text("HomePage Received Message")
31 | }
32 | .font(.footnote)
33 | .foregroundColor(.secondary)
34 |
35 | Text(item?.message ?? "-")
36 | }
37 | }
38 |
39 | GroupBox {
40 | VStack(spacing: 10) {
41 | HStack {
42 | Image(systemName: "envelope")
43 | Text("DeepLink Received Message")
44 | }
45 | .font(.footnote)
46 | .foregroundColor(.secondary)
47 |
48 | Text(deepLinkItem?.deepLinkMessage ?? "-")
49 | }
50 | }
51 |
52 | Button(action: {
53 | navigator.backOrNext(linkItem: .init(path: "page2"), isAnimated: true)
54 | }) {
55 | Text("go to next Page")
56 | }
57 |
58 | Spacer()
59 | }
60 | .padding()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/SingleBasic/Page/Home/HomeView.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import SwiftUI
3 |
4 | struct HomeView: View {
5 |
6 | // MARK: Internal
7 |
8 | let navigator: SingleLinkNavigator
9 |
10 | var body: some View {
11 | VStack(spacing: 30) {
12 | PathIndicator(currentPath: paths.joined(separator: " -> "))
13 | .padding(.top, 32)
14 |
15 | Button(action: {
16 | navigator.backOrNext(linkItem: .init(path: "page1"), isAnimated: true)
17 | }) {
18 | Text("go to next Page")
19 | }
20 |
21 | Button(action: {
22 | navigator.backOrNext(linkItem: .init(pathList: ["page1", "page2", "page3"]), isAnimated: true)
23 | }) {
24 | Text("go to last Page")
25 | }
26 |
27 | Button(action: {
28 | let alertModel = Alert(
29 | title: "Title",
30 | message: "message",
31 | buttons: [.init(title: "OK", style: .default, action: { LogManager.default.debug("OK Tapped") })],
32 | flagType: .default)
33 | navigator.alert(target: .default, model: alertModel)
34 | }) {
35 | Text("show alert")
36 | .foregroundColor(.orange)
37 | }
38 |
39 | Button(action: {
40 | navigator.sheet(linkItem: .init(pathList: ["page1", "page2"]), isAnimated: true)
41 | }) {
42 | Text("open Page 2 as Sheet")
43 | .foregroundColor(.purple)
44 | }
45 |
46 | Button(action: {
47 | if #available(iOS 15.0, *) {
48 | navigator.detentSheet(linkItem: .init(pathList: ["page1", "page2"]), isAnimated: true, configuration: .default)
49 | } else {
50 | navigator.sheet(linkItem: .init(pathList: ["page1", "page2"]), isAnimated: true)
51 | }
52 | }) {
53 | Text("open Page 2 as Detent Sheet")
54 | .foregroundColor(.purple)
55 | }
56 |
57 | Button(action: {
58 | navigator.fullSheet(linkItem: .init(pathList: ["page1", "page2"]), isAnimated: true, prefersLargeTitles: true)
59 | }) {
60 | Text("open Page 2 as Full Screen Sheet")
61 | .foregroundColor(.purple)
62 | }
63 |
64 | Spacer()
65 | }
66 | .padding()
67 | .onAppear {
68 | paths = navigator.getCurrentPaths()
69 | }
70 | }
71 |
72 | // MARK: Private
73 |
74 | @State private var paths: [String] = []
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Components/Alert/Alert.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | /// A struct representing a customizable alert.
4 | ///
5 | /// This struct allows you to create an alert with a specified title, message, an array of buttons, and a flag type.
6 | public struct Alert: Equatable {
7 |
8 | // MARK: Lifecycle
9 |
10 | /// Initializes a new alert with the provided parameters.
11 | ///
12 | /// - Parameters:
13 | /// - title: The title of the alert. Defaults to nil, which will be transformed to an empty string internally.
14 | /// - message: The message that the alert displays.
15 | /// - buttons: An array of `ActionButton` objects that represent the buttons in the alert.
16 | /// - flagType: The type of the flag that categorizes the alert.
17 | public init(title: String? = .none, message: String?, buttons: [ActionButton], flagType: FlagType) {
18 | self.title = title ?? ""
19 | self.message = message ?? ""
20 | self.buttons = buttons
21 | self.flagType = flagType
22 | }
23 |
24 | // MARK: Public
25 |
26 | /// Enumeration representing the different flag types an alert can have.
27 | ///
28 | /// Flag types help to categorize the alerts into various categories such as error or default.
29 | public enum FlagType: Equatable {
30 | case error /// < Represents an error flag type, used to indicate that the alert is presenting an error.
31 | case `default` /// < Represents a default flag type, used when no specific categorization is needed.
32 | }
33 |
34 | // MARK: Internal
35 |
36 | /// The title of the alert.
37 | let title: String?
38 | /// The message that the alert displays.
39 | let message: String
40 | /// An array of `ActionButton` objects representing the buttons in the alert.
41 | let buttons: [ActionButton]
42 | /// The flag type categorizing the alert.
43 | let flagType: FlagType
44 |
45 | /// Builds the `UIAlertController` using the properties of this `Alert` instance.
46 | ///
47 | /// This function constructs a `UIAlertController` with the title, message, and buttons defined in this `Alert` instance.
48 | ///
49 | /// - Returns: A `UIAlertController` instance configured with the properties of this `Alert`.
50 | func build() -> UIAlertController {
51 | let controller = UIAlertController(title: title, message: message, preferredStyle: .alert)
52 | for button in buttons {
53 | controller.addAction(button.buildAlertButton())
54 | }
55 | return controller
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Components/Alert/ActionButton.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | /// A struct representing a customizable action button.
4 | ///
5 | /// This struct allows you to create a button with a specified title, style, and action closure.
6 | public struct ActionButton: Equatable {
7 |
8 | // MARK: Lifecycle
9 |
10 | /// Initializes a new action button with the provided parameters.
11 | ///
12 | /// - Parameters:
13 | /// - title: The title of the button. Defaults to nil.
14 | /// - style: The style of the button, which determines its appearance and behavior.
15 | /// - action: The closure to execute when the button is pressed. Defaults to an empty closure.
16 | public init(title: String? = .none, style: ActionStyle, action: @escaping () -> Void = { }) {
17 | self.title = title ?? "title"
18 | self.style = style
19 | self.action = action
20 | }
21 |
22 | // MARK: Public
23 |
24 | /// Enumeration representing the different styles a button can have.
25 | ///
26 | /// These styles influence the button's appearance and behavior in the UI.
27 | public enum ActionStyle {
28 | case `default` /// < Represents the default style for a button.
29 | case cancel /// < Represents a cancel style, generally used for cancel buttons.
30 | case destructive /// < Represents a destructive style, generally used for actions that have destructive behaviors.
31 |
32 | /// Computed property that maps ActionStyle cases to their corresponding UIAlertAction.Style counterparts.
33 | var uiRawValue: UIAlertAction.Style {
34 | switch self {
35 | case .default: return .default
36 | case .cancel: return .cancel
37 | case .destructive: return .destructive
38 | }
39 | }
40 | }
41 |
42 | /// Equatable protocol method to compare two ActionButton instances.
43 | ///
44 | /// - Parameters:
45 | /// - lhs: An ActionButton instance.
46 | /// - rhs: Another ActionButton instance.
47 | ///
48 | /// - Returns: A boolean indicating whether the two instances are equal.
49 | public static func == (lhs: Self, rhs: Self) -> Bool {
50 | lhs.title == rhs.title
51 | }
52 |
53 | // MARK: Internal
54 |
55 | /// The title of the action button.
56 | let title: String
57 | /// The style of the action button.
58 | let style: ActionStyle
59 | /// The closure to be executed when the button is pressed.
60 | let action: () -> Void
61 |
62 | /// Creates a UIAlertAction instance with the properties of this ActionButton.
63 | ///
64 | /// - Returns: A UIAlertAction instance with the button's title, style, and action closure.
65 | func buildAlertButton() -> UIAlertAction {
66 | .init(title: title, style: style.uiRawValue) { _ in action() }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/TabEventSubscriber/TabEventSubscriberApp.swift:
--------------------------------------------------------------------------------
1 | import LinkNavigator
2 | import PageTemplate
3 | import SwiftUI
4 |
5 | // MARK: - TabEventSubscriberApp
6 |
7 | @main
8 | struct TabEventSubscriberApp: App {
9 | let tabLinkNavigator = TabLinkNavigator(
10 | routeBuilderItemList: AppRouterBuilderGroup().routers(),
11 | dependency: AppDependency())
12 |
13 | let tabList: [TabItem] = [
14 | .init(
15 | tag: .zero,
16 | tabItem: .init(
17 | title: "tab1",
18 | image: UIImage(systemName: "house"),
19 | selectedImage: UIImage(systemName: "house.fill")),
20 | linkItem: .init(path: "tab1")),
21 | .init(
22 | tag: 1,
23 | tabItem: .init(
24 | title: "tab2",
25 | image: UIImage(systemName: "folder"),
26 | selectedImage: UIImage(systemName: "folder.fill")),
27 | linkItem: .init(path: "tab2")),
28 | .init(
29 | tag: 2,
30 | tabItem: .init(
31 | title: "tab3",
32 | image: UIImage(systemName: "heart"),
33 | selectedImage: UIImage(systemName: "heart.fill")),
34 | linkItem: .init(path: "tab3")),
35 | .init(
36 | tag: 3,
37 | tabItem: .init(
38 | title: "tab4",
39 | image: UIImage(systemName: "person"),
40 | selectedImage: UIImage(systemName: "person.fill")),
41 | linkItem: .init(path: "tab4")),
42 | ]
43 |
44 | var body: some Scene {
45 | WindowGroup {
46 | TabLinkNavigationView(
47 | linkNavigator: tabLinkNavigator,
48 | isHiddenDefaultTabbar: false,
49 | tabItemList: tabList)
50 | .onOpenURL { url in
51 | /// You can test deep links by setting the URL Scheme to "tab-e-s".
52 | /// Example:
53 | /// tab-e-s://navigation/tab2/step1/step2?message=opened+by+deep+link
54 |
55 | guard
56 | let tabPath = getTabPathByDeeplink(url: url),
57 | let linkItem = getLinkItemByDeepLink(url: url),
58 | let targetTab = tabLinkNavigator.targetPartialNavigator(tabPath: tabPath)
59 | else { return }
60 |
61 | tabLinkNavigator.moveTab(targetPath: tabPath)
62 | targetTab.replace(linkItem: linkItem, isAnimated: true)
63 | }
64 | }
65 | }
66 | }
67 |
68 | extension TabEventSubscriberApp {
69 | private func getTabPathByDeeplink(url: URL) -> String? {
70 | let components = URLComponents(string: url.absoluteString)
71 | return components?.path.split(separator: "/").map(String.init).first
72 | }
73 |
74 | private func getLinkItemByDeepLink(url: URL) -> LinkItem? {
75 | let components = URLComponents(string: url.absoluteString)
76 | guard let paths = components?.path.split(separator: "/").map(String.init) else { return .none }
77 |
78 | return .init(pathList: paths, itemsString: components?.query ?? "")
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/Core/DetentConfiguration.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @available(iOS 15.0, *)
4 | public struct DetentConfiguration {
5 |
6 | // MARK: Lifecycle
7 |
8 | /// Initializes a new DetentConfiguration.
9 | ///
10 | /// - Parameters:
11 | /// - detents: An array of `UISheetPresentationController.Detent` defining the sizes of the sheet.
12 | /// - cornerRadius: An optional `CGFloat` specifying the corner radius of the sheet.
13 | /// - largestUndimmedDetentIdentifier: An optional detent identifier specifying the largest undimmed size.
14 | /// - prefersScrollingExpandsWhenScrolledToEdge: A Boolean value indicating if scrolling should expand the sheet.
15 | /// - prefersGrabberVisible: A Boolean value indicating if the grabber should be visible.
16 | /// - prefersEdgeAttachedInCompactHeight: A Boolean value indicating if the sheet should be edge-attached in compact height.
17 | /// - widthFollowsPreferredContentSizeWhenEdgeAttached: A Boolean value indicating if the sheet's width should follow the preferred content size when edge-attached.
18 | /// - selectedDetentIdentifier: An optional detent identifier specifying the currently selected detent.
19 | public init(
20 | detents: [UISheetPresentationController.Detent],
21 | cornerRadius: CGFloat? = nil,
22 | largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = nil,
23 | prefersScrollingExpandsWhenScrolledToEdge: Bool = true,
24 | prefersGrabberVisible: Bool = false,
25 | prefersEdgeAttachedInCompactHeight: Bool = false,
26 | widthFollowsPreferredContentSizeWhenEdgeAttached: Bool = false,
27 | selectedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = nil)
28 | {
29 | self.detents = detents
30 | self.cornerRadius = cornerRadius
31 | self.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier
32 | self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
33 | self.prefersGrabberVisible = prefersGrabberVisible
34 | self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
35 | self.widthFollowsPreferredContentSizeWhenEdgeAttached = widthFollowsPreferredContentSizeWhenEdgeAttached
36 | self.selectedDetentIdentifier = selectedDetentIdentifier
37 | }
38 |
39 | // MARK: Public
40 |
41 | public static let `default` = DetentConfiguration(detents: [.medium(), .large()])
42 |
43 | public let detents: [UISheetPresentationController.Detent]
44 | public let cornerRadius: CGFloat?
45 | public let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
46 | public let prefersScrollingExpandsWhenScrolledToEdge: Bool
47 | public let prefersGrabberVisible: Bool
48 | public let prefersEdgeAttachedInCompactHeight: Bool
49 | public let widthFollowsPreferredContentSizeWhenEdgeAttached: Bool
50 | public let selectedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Builder/TabNavigationBuilder.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | // MARK: - TabNavigationBuilder
5 |
6 | public struct TabNavigationBuilder {
7 |
8 | // MARK: Lifecycle
9 |
10 | /// Initializes a new instance of `NavigationBuilder`.
11 | ///
12 | /// - Parameters:
13 | /// - rootNavigator: The root navigator object.
14 | /// - routeBuilderList: An array of route builders.
15 | /// - dependency: The dependency required for constructing the routes.
16 | public init(
17 | rootNavigator: Root,
18 | routeBuilderList: [RouteBuilderOf],
19 | dependency: DependencyType)
20 | {
21 | self.rootNavigator = rootNavigator
22 | self.routeBuilderList = routeBuilderList
23 | self.dependency = dependency
24 | }
25 |
26 | // MARK: Public
27 |
28 | public typealias MatchedViewController = MatchPathUsable & UIViewController
29 |
30 | // MARK: Internal
31 |
32 | /// The root navigator object.
33 | let rootNavigator: Root
34 |
35 | /// An array of `RouteBuilderOf` objects used to construct the routes.
36 | let routeBuilderList: [RouteBuilderOf]
37 |
38 | /// The dependency required for constructing the routes.
39 | let dependency: DependencyType
40 |
41 | }
42 |
43 | extension TabNavigationBuilder {
44 | public func build(item: LinkItem) -> [RouteViewController] {
45 | item.pathList.compactMap { path in
46 | routeBuilderList.first(where: { $0.matchPath == path })?.routeBuild(rootNavigator, item.encodedItemString, dependency)
47 | }
48 | }
49 |
50 | public func pickBuild(item: LinkItem) -> RouteViewController? {
51 | routeBuilderList
52 | .first(where: { $0.matchPath == (item.pathList.first ?? "") })?
53 | .routeBuild(rootNavigator, item.encodedItemString, dependency)
54 | }
55 |
56 | public func firstPick(controller: UINavigationController?, item: LinkItem)
57 | -> RouteViewController?
58 | {
59 | guard let controller, let first = item.pathList.first else { return .none }
60 | return controller.viewControllers
61 | .compactMap { $0 as? RouteViewController }
62 | .first(where: { $0.matchPath == first })
63 | }
64 |
65 | public func lastPick(controller: UINavigationController?, item: LinkItem) -> RouteViewController? {
66 | guard let controller, let last = item.pathList.last else { return .none }
67 | return controller.viewControllers
68 | .compactMap { $0 as? RouteViewController }
69 | .last(where: { $0.matchPath == last })
70 | }
71 |
72 | public func exceptFilter(
73 | controller: UINavigationController?,
74 | item: LinkItem) -> [RouteViewController]
75 | {
76 | (controller?.viewControllers ?? [])
77 | .compactMap { $0 as? MatchedViewController }
78 | .filter { !item.pathList.contains($0.matchPath) }
79 | }
80 |
81 | public func isContainSequence(item: LinkItem) -> Bool {
82 | let currentPathJoin = routeBuilderList.map { $0.matchPath }.joined(separator: ",")
83 | let itemPathJoin = item.pathList.joined(separator: ",")
84 |
85 | return currentPathJoin.contains(itemPathJoin)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Components/LinkNavigationView.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 |
4 | // MARK: - LinkNavigationView
5 |
6 | /// Represents a view for navigation using `SingleLinkNavigator`.
7 | ///
8 | /// This struct is used to initialize a navigation view with a link navigator and a link item. The link navigator
9 | /// facilitates various navigation actions such as adding, removing, or presenting sheets using a `UINavigationController`.
10 | /// The link item holds information about the path of the page to be requested by the navigator and the details of the
11 | /// item to be injected into the page. The `item` parameter in the initializer represents the `UINavigationController`
12 | /// stack that will be injected and displayed to the user once the view is rendered.
13 | public struct LinkNavigationView {
14 |
15 | /// A `SingleLinkNavigator` instance that handles various navigation actions.
16 | let linkNavigator: SingleLinkNavigator
17 |
18 | /// A `LinkItem` instance that contains information about the page to be requested and the item to be injected.
19 | let item: LinkItem
20 |
21 | let prefersLargeTitles: Bool
22 |
23 | /// Initializes a new instance of `LinkNavigationView`.
24 | ///
25 | /// - Parameters:
26 | /// - linkNavigator: A `SingleLinkNavigator` instance used for navigating between pages.
27 | /// - item: A `LinkItem` instance that contains the path and item details for the page to be requested.
28 | public init(linkNavigator: SingleLinkNavigator, item: LinkItem, prefersLargeTitles: Bool = false) {
29 | self.linkNavigator = linkNavigator
30 | self.item = item
31 | self.prefersLargeTitles = prefersLargeTitles
32 | }
33 | }
34 |
35 | // MARK: UIViewControllerRepresentable
36 |
37 | extension LinkNavigationView: UIViewControllerRepresentable {
38 |
39 | /// Creates a `UINavigationController` instance that is initially populated with view controllers
40 | /// based on the `LinkItem` instance provided during initialization.
41 | ///
42 | /// - Parameter context: The context in which the `UIViewController` is created.
43 | ///
44 | /// - Returns: A `UINavigationController` instance that serves as the base for navigation actions.
45 | public func makeUIViewController(context _: Context) -> UINavigationController {
46 | let vc = UINavigationController()
47 | vc.navigationBar.prefersLargeTitles = prefersLargeTitles
48 | vc.setViewControllers(linkNavigator.launch(item: item), animated: false)
49 | return vc
50 | }
51 |
52 | /// Updates the `rootController` of the `linkNavigator` with the provided `UINavigationController` instance.
53 | ///
54 | /// This method allows the update of pages based on the `UINavigationController`, facilitating page updates whenever the view updates.
55 | ///
56 | /// - Parameters:
57 | /// - uiViewController: The `UINavigationController` instance that serves as the base for navigation actions.
58 | /// - context: The context in which the update occurs.
59 | public func updateUIViewController(_ uiViewController: UINavigationController, context _: Context) {
60 | linkNavigator.rootController = uiViewController
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Examples/SingleNavigator/01-SingleBasic/01-SingleBasic.xcodeproj/xcshareddata/xcschemes/SingleBasic.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
42 |
44 |
50 |
51 |
52 |
53 |
59 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/Examples/TabNavigator/03-TabEventSubscriber/03-TabEventSubscriber.xcodeproj/xcshareddata/xcschemes/TabEventSubscriber.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
42 |
44 |
50 |
51 |
52 |
53 |
59 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/BaseComponent/LinkItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import URLEncodedForm
3 |
4 | // MARK: - LinkItem
5 |
6 | /// Represents a link item that contains paths and associated items.
7 | /// It is used to manage the links and state values that are injected into a page.
8 | public struct LinkItem {
9 |
10 | // MARK: Lifecycle
11 |
12 | /// Initializes a LinkItem instance with a given path list and an items parameter.
13 |
14 | /// - Parameters:
15 | /// - path: A string representing the path.
16 | /// - itemsString: The objects to be injected into pathList are in string format (e.g., queryString, base64EncodedString, etc.).
17 | /// - isBase64EncodedItemsString: The 'itemsString' indicates whether it is base64 encoded or not.
18 | public init(path: String, itemsString: String = "", isBase64EncodedItemsString: Bool = false) {
19 | pathList = [path]
20 | encodedItemString = isBase64EncodedItemsString ? itemsString : itemsString.encodedBase64()
21 | }
22 |
23 | /// - Parameters:
24 | /// - pathList: An array of strings representing the path list.
25 | /// - itemsString: The objects to be injected into pathList are in string format (e.g., queryString, base64EncodedString, etc.).
26 | /// - isBase64EncodedItemsString: The 'itemsString' indicates whether it is base64 encoded or not.
27 | public init(pathList: [String], itemsString: String = "", isBase64EncodedItemsString: Bool = false) {
28 | self.pathList = pathList
29 | encodedItemString = isBase64EncodedItemsString ? itemsString : itemsString.encodedBase64()
30 | }
31 |
32 | /// - Parameters:
33 | /// - path: A string representing the path.
34 | /// - items: The object to be injected into the RouteBuilder corresponding to each path .
35 | public init(path: String, items: Codable?) {
36 | pathList = [path]
37 | encodedItemString = items?.encoded() ?? ""
38 | }
39 |
40 | /// - Parameters:
41 | /// - pathList: An array of strings representing the path list.
42 | /// - items: The object to be injected into the RouteBuilder corresponding to each path .
43 | public init(pathList: [String], items: Codable?) {
44 | self.pathList = pathList
45 | encodedItemString = items?.encoded() ?? ""
46 | }
47 |
48 | // MARK: Internal
49 |
50 | /// An array of strings representing the path list.
51 | public let pathList: [String]
52 |
53 | /// A parameter containing the items associated with the path or path list.
54 | public let encodedItemString: String
55 | }
56 |
57 | extension LinkItem {
58 | @available(*, deprecated, message: "Please use init(pathList:itemsString:isBase64EncodedItemsString:) instead.")
59 | public init(pathList: [String], items: String) {
60 | self.pathList = pathList
61 | encodedItemString = items
62 | }
63 |
64 | @available(*, deprecated, message: "Please use init(path:itemsString:isBase64EncodedItemsString:) instead.")
65 | public init(path: String, items: String) {
66 | pathList = [path]
67 | encodedItemString = items
68 | }
69 | }
70 |
71 | // MARK: Equatable
72 |
73 | extension LinkItem: Equatable {
74 | public static func == (lhs: Self, rhs: Self) -> Bool {
75 | lhs.pathList == rhs.pathList
76 | && lhs.encodedItemString == rhs.encodedItemString
77 | }
78 | }
79 |
80 | extension String {
81 |
82 | // MARK: Public
83 |
84 | public func decoded() -> T? {
85 | guard !isEmpty else { return .none }
86 | if let decodedValue = self as? T {
87 | return decodedValue
88 | }
89 | guard let data = Data(base64Encoded: self) else { return .none }
90 | return (try? JSONDecoder().decode(T.self, from: data)) ?? (try? URLEncodedFormDecoder().decode(T.self, from: data))
91 | }
92 |
93 | // MARK: Fileprivate
94 |
95 | fileprivate func encodedBase64() -> Self {
96 | data(using: .utf8)?.base64EncodedString() ?? self
97 | }
98 | }
99 |
100 | extension Encodable {
101 | public func encoded() -> String {
102 | guard let data = try? JSONEncoder().encode(self) else { return "" }
103 |
104 | return data.base64EncodedString()
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Builder/SingleNavigationBuilder.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import UIKit
3 |
4 | // MARK: - SingleNavigationBuilder
5 |
6 | /// A class that facilitates the construction of a navigation structure.
7 | ///
8 | /// Generics:
9 | /// - Root: The type representing the root navigator.
10 | /// - ItemValue: A type that conforms to `EmptyValueType` representing the item values.
11 | public class SingleNavigationBuilder {
12 |
13 | // MARK: Lifecycle
14 |
15 | /// Initializes a new instance of `NavigationBuilder`.
16 | ///
17 | /// - Parameters:
18 | /// - rootNavigator: The root navigator object.
19 | /// - routeBuilderList: An array of route builders.
20 | /// - dependency: The dependency required for constructing the routes.
21 | public init(
22 | rootNavigator: Root,
23 | routeBuilderList: [RouteBuilderOf],
24 | dependency: DependencyType)
25 | {
26 | self.rootNavigator = rootNavigator
27 | self.routeBuilderList = routeBuilderList
28 | self.dependency = dependency
29 | }
30 |
31 | // MARK: Internal
32 |
33 | /// The root navigator object.
34 | let rootNavigator: Root
35 |
36 | /// An array of `RouteBuilderOf` objects used to construct the routes.
37 | let routeBuilderList: [RouteBuilderOf]
38 |
39 | /// The dependency required for constructing the routes.
40 | let dependency: DependencyType
41 | }
42 |
43 | extension SingleNavigationBuilder {
44 |
45 | // MARK: Public
46 |
47 | public func isContainSequence(item: LinkItem) -> Bool {
48 | let currentPathJoin = routeBuilderList.map { $0.matchPath }.joined(separator: ",")
49 | let itemPathJoin = item.pathList.joined(separator: ",")
50 |
51 | return currentPathJoin.contains(itemPathJoin)
52 | }
53 |
54 | // MARK: Internal
55 |
56 | /// Builds a list of `RouteViewController` based on the provided `LinkItem`.
57 | ///
58 | /// - Parameter item: A `LinkItem` containing the paths and associated values.
59 | /// - Returns: An array of `RouteViewController` constructed based on the `LinkItem`.
60 | func build(item: LinkItem) -> [RouteViewController] {
61 | item.pathList.compactMap { path in
62 | routeBuilderList.first(where: { $0.matchPath == path })?.routeBuild(rootNavigator, item.encodedItemString, dependency)
63 | }
64 | }
65 |
66 | /// Selects and builds a `RouteViewController` based on the first match in the `LinkItem`.
67 | ///
68 | /// - Parameter item: A `LinkItem` containing the paths and associated values.
69 | /// - Returns: A `RouteViewController` instance based on the first matching path in the `LinkItem` or nil if no match is found.
70 | func pickBuild(item: LinkItem) -> RouteViewController? {
71 | routeBuilderList
72 | .first(where: { $0.matchPath == (item.pathList.first ?? "") })?
73 | .routeBuild(rootNavigator, item.encodedItemString, dependency)
74 | }
75 |
76 | /// Selects the first `RouteViewController` in the navigation stack that matches the first path in the `LinkItem`.
77 | ///
78 | /// - Parameters:
79 | /// - controller: An optional `UINavigationController`.
80 | /// - item: A `LinkItem` containing the paths and associated values.
81 | /// - Returns: A `RouteViewController` instance that matches the first path in the `LinkItem` or nil if no match is found.
82 | func firstPick(controller: UINavigationController?, item: LinkItem) -> RouteViewController? {
83 | guard let controller, let first = item.pathList.first else { return .none }
84 | return controller.viewControllers
85 | .compactMap { $0 as? RouteViewController }
86 | .first(where: { $0.matchPath == first })
87 | }
88 |
89 | /// Selects the last `RouteViewController` in the navigation stack that matches the first path in the `LinkItem`.
90 | ///
91 | /// - Parameters:
92 | /// - controller: An optional `UINavigationController`.
93 | /// - item: A `LinkItem` containing the paths and associated values.
94 | /// - Returns: A `RouteViewController` instance that matches the first path in the `LinkItem` or nil if no match is found.
95 | func lastPick(controller: UINavigationController?, item: LinkItem) -> RouteViewController? {
96 | guard let controller, let last = item.pathList.first else { return .none }
97 | return controller.viewControllers
98 | .compactMap { $0 as? RouteViewController }
99 | .last(where: { $0.matchPath == last })
100 | }
101 |
102 | /// Filters out `RouteViewController` instances that match the paths in the `LinkItem` from the navigation stack.
103 | ///
104 | /// - Parameters:
105 | /// - controller: An optional `UINavigationController`.
106 | /// - item: A `LinkItem` containing the paths and associated values.
107 | /// - Returns: An array of `RouteViewController` instances that do not match any path in the `LinkItem`.
108 | func exceptFilter(controller: UINavigationController?, item: LinkItem) -> [RouteViewController] {
109 | (controller?.viewControllers ?? [])
110 | .compactMap { $0 as? RouteViewController }
111 | .filter { !item.pathList.contains($0.matchPath) }
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Test/SingleLinkNavigatorMock.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | // MARK: - SingleLinkNavigatorMock
5 |
6 | public final class SingleLinkNavigatorMock {
7 |
8 | // MARK: Lifecycle
9 |
10 | public init(value: Value = .init(), event: Event = .init()) {
11 | self.value = value
12 | self.event = event
13 | }
14 |
15 | // MARK: Public
16 |
17 | public var value: Value
18 | public var event: Event
19 |
20 | public func resetEvent() {
21 | event = .init()
22 | }
23 |
24 | public func resetValue() {
25 | value = .init()
26 | }
27 |
28 | public func resetAll() {
29 | resetEvent()
30 | resetValue()
31 | }
32 |
33 | }
34 |
35 | extension SingleLinkNavigatorMock {
36 |
37 | public struct Value: Equatable, Sendable {
38 | public var currentPaths: [String] = []
39 | public var currentRootPaths: [String] = []
40 | public var rangePaths: [String] = []
41 |
42 | public init() { }
43 | }
44 |
45 | public struct Event: Equatable, Sendable {
46 |
47 | // MARK: Lifecycle
48 |
49 | public init() { }
50 |
51 | // MARK: Public
52 |
53 | public var getCurrentPaths: Int = .zero
54 | public var getCurrentRootPaths: Int = .zero
55 | public var next: Int = .zero
56 | public var rootNext: Int = .zero
57 | public var sheet: Int = .zero
58 | public var fullSheet: Int = .zero
59 | public var customSheet: Int = .zero
60 | public var replace: Int = .zero
61 | public var backOrNext: Int = .zero
62 | public var rootBackOrNext: Int = .zero
63 | public var back: Int = .zero
64 | public var remove: Int = .zero
65 | public var rootRemove: Int = .zero
66 | public var backToLast: Int = .zero
67 | public var close: Int = .zero
68 | public var range: Int = .zero
69 | public var rootReloadLast: Int = .zero
70 | public var alert: Int = .zero
71 | public var send: Int = .zero
72 | public var mainSend: Int = .zero
73 | public var allSend: Int = .zero
74 | public var rootBackToLast: Int = .zero
75 | public var rootSend: Int = .zero
76 | public var allRootSend: Int = .zero
77 | public var mergeReplace: Int = .zero
78 | }
79 | }
80 |
81 | // MARK: LinkNavigatorFindLocationUsable
82 |
83 | extension SingleLinkNavigatorMock: LinkNavigatorFindLocationUsable {
84 | public func getCurrentPaths() -> [String] {
85 | event.getCurrentPaths += 1
86 | return value.currentPaths
87 | }
88 |
89 | public func getCurrentRootPaths() -> [String] {
90 | event.getCurrentRootPaths += 1
91 | return value.currentRootPaths
92 | }
93 | }
94 |
95 | // MARK: LinkNavigatorProtocol
96 |
97 | extension SingleLinkNavigatorMock: LinkNavigatorProtocol {
98 | public func mergeReplace(linkItem: LinkItem, isAnimated: Bool) {
99 | event.mergeReplace += 1
100 | }
101 |
102 | public func rootBackToLast(path _: String, isAnimated _: Bool) {
103 | event.rootBackToLast += 1
104 | }
105 |
106 | public func rootSend(item _: LinkItem) {
107 | event.rootSend += 1
108 | }
109 |
110 | public func allRootSend(item _: LinkItem) {
111 | event.allRootSend += 1
112 | }
113 |
114 | public func next(linkItem _: LinkItem, isAnimated _: Bool) {
115 | event.next += 1
116 | }
117 |
118 | public func rootNext(linkItem _: LinkItem, isAnimated _: Bool) {
119 | event.rootNext += 1
120 | }
121 |
122 | public func sheet(linkItem _: LinkItem, isAnimated _: Bool) {
123 | event.sheet += 1
124 | }
125 |
126 | @available(iOS 15.0, *)
127 | public func detentSheet(linkItem _: LinkItem, isAnimated _: Bool, configuration _: DetentConfiguration) {
128 | event.sheet += 1
129 | }
130 |
131 | public func fullSheet(linkItem _: LinkItem, isAnimated _: Bool, prefersLargeTitles _: Bool?) {
132 | event.fullSheet += 1
133 | }
134 |
135 | public func customSheet(
136 | linkItem _: LinkItem,
137 | isAnimated _: Bool,
138 | iPhonePresentationStyle _: UIModalPresentationStyle,
139 | iPadPresentationStyle _: UIModalPresentationStyle,
140 | prefersLargeTitles _: Bool?)
141 | {
142 | event.customSheet += 1
143 | }
144 |
145 | public func replace(linkItem _: LinkItem, isAnimated _: Bool) {
146 | event.replace += 1
147 | }
148 |
149 | public func backOrNext(linkItem _: LinkItem, isAnimated _: Bool) {
150 | event.backOrNext += 1
151 | }
152 |
153 | public func rootBackOrNext(linkItem _: LinkItem, isAnimated _: Bool) {
154 | event.rootBackOrNext += 1
155 | }
156 |
157 | public func back(isAnimated _: Bool) {
158 | event.back += 1
159 | }
160 |
161 | public func remove(pathList _: [String]) {
162 | event.remove += 1
163 | }
164 |
165 | public func rootRemove(pathList _: [String]) {
166 | event.rootRemove += 1
167 | }
168 |
169 | public func backToLast(path _: String, isAnimated _: Bool) {
170 | event.backToLast += 1
171 | }
172 |
173 | public func close(isAnimated _: Bool, completeAction _: @escaping () -> Void) {
174 | event.close += 1
175 | }
176 |
177 | public func range(path _: String) -> [String] {
178 | event.range += 1
179 | return value.rangePaths
180 | }
181 |
182 | public func rootReloadLast(items _: LinkItem, isAnimated _: Bool) {
183 | event.rootReloadLast += 1
184 | }
185 |
186 | public func alert(target _: NavigationTarget, model _: Alert) {
187 | event.rootReloadLast += 1
188 | }
189 |
190 | public func send(item _: LinkItem) {
191 | event.send += 1
192 | }
193 |
194 | public func mainSend(item _: LinkItem) {
195 | event.mainSend += 1
196 | }
197 |
198 | public func allSend(item _: LinkItem) {
199 | event.allSend += 1
200 | }
201 |
202 | }
203 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Test/TabLinkNavigatorMock.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | // MARK: - TabLinkNavigatorMock
5 |
6 | public final class TabLinkNavigatorMock {
7 |
8 | // MARK: Lifecycle
9 |
10 | public init(
11 | value: Value = .init(),
12 | event: Event = .init())
13 | {
14 | self.value = value
15 | self.event = event
16 | }
17 |
18 | // MARK: Public
19 |
20 | public var value: Value
21 | public var event: Event
22 |
23 | public func resetEvent() {
24 | event = .init()
25 | }
26 |
27 | public func resetValue() {
28 | value = .init()
29 | }
30 |
31 | public func resetAll() {
32 | resetEvent()
33 | resetValue()
34 | }
35 | }
36 |
37 | extension TabLinkNavigatorMock {
38 | public struct Value: Equatable, Sendable {
39 | public var currentPaths: [String] = []
40 | public var currentRootPaths: [String] = []
41 | public var rangePaths: [String] = []
42 |
43 | public init() { }
44 | }
45 |
46 | public struct Event: Equatable, Sendable {
47 |
48 | // MARK: Lifecycle
49 |
50 | public init() { }
51 |
52 | // MARK: Public
53 |
54 | public var next: Int = .zero
55 | public var rootNext: Int = .zero
56 | public var sheet: Int = .zero
57 | public var fullSheet: Int = .zero
58 | public var customSheet: Int = .zero
59 | public var replace: Int = .zero
60 | public var rootReplace: Int = .zero
61 | public var backOrNext: Int = .zero
62 | public var rootBackOrNext: Int = .zero
63 | public var back: Int = .zero
64 | public var remove: Int = .zero
65 | public var rootRemove: Int = .zero
66 | public var backToLast: Int = .zero
67 | public var rootBackToLast: Int = .zero
68 | public var close: Int = .zero
69 | public var reloadLast: Int = .zero
70 | public var rootReloadLast: Int = .zero
71 | public var alert: Int = .zero
72 | public var send: Int = .zero
73 | public var currentTabSend: Int = .zero
74 | public var mainSend: Int = .zero
75 | public var allSend: Int = .zero
76 | public var currentTabAllSend: Int = .zero
77 | public var moveTab: Int = .zero
78 | public var range: Int = .zero
79 | public var getCurrentRootPaths: Int = .zero
80 | public var getCurrentPaths: Int = .zero
81 | public var mergeReplace: Int = .zero
82 | }
83 | }
84 |
85 | // MARK: LinkNavigatorFindLocationUsable, TabLinkNavigatorProtocol
86 |
87 | extension TabLinkNavigatorMock: LinkNavigatorFindLocationUsable, TabLinkNavigatorProtocol {
88 |
89 | public func alert(model _: Alert) {
90 | event.alert += 1
91 | }
92 |
93 | public func getCurrentPaths() -> [String] {
94 | event.getCurrentPaths += 1
95 | return value.currentPaths
96 | }
97 |
98 | public func getCurrentRootPaths() -> [String] {
99 | event.getCurrentRootPaths += 1
100 | return value.currentRootPaths
101 | }
102 |
103 | public func next(linkItem _: LinkItem, isAnimated _: Bool) {
104 | event.next += 1
105 | }
106 |
107 | public func rootNext(linkItem _: LinkItem, isAnimated _: Bool) {
108 | event.rootNext += 1
109 | }
110 |
111 | public func sheet(linkItem _: LinkItem, isAnimated _: Bool) {
112 | event.sheet += 1
113 | }
114 |
115 | public func fullSheet(linkItem _: LinkItem, isAnimated _: Bool, prefersLargeTitles _: Bool?) {
116 | event.fullSheet += 1
117 | }
118 |
119 | public func customSheet(
120 | linkItem _: LinkItem,
121 | isAnimated _: Bool,
122 | iPhonePresentationStyle _: UIModalPresentationStyle,
123 | iPadPresentationStyle _: UIModalPresentationStyle,
124 | prefersLargeTitles _: Bool?)
125 | {
126 | event.customSheet += 1
127 | }
128 |
129 | public func replace(linkItem _: LinkItem, isAnimated _: Bool) {
130 | event.replace += 1
131 | }
132 |
133 | public func rootReplace(linkItem _: LinkItem, isAnimated _: Bool, closeAll _: Bool) {
134 | event.rootReplace += 1
135 | }
136 |
137 | public func backOrNext(linkItem _: LinkItem, isAnimated _: Bool) {
138 | event.backOrNext += 1
139 | }
140 |
141 | public func rootBackOrNext(linkItem _: LinkItem, isAnimated _: Bool) {
142 | event.rootBackOrNext += 1
143 | }
144 |
145 | public func back(isAnimated _: Bool) {
146 | event.back += 1
147 | }
148 |
149 | public func remove(pathList _: [String]) {
150 | event.remove += 1
151 | }
152 |
153 | public func rootRemove(pathList _: [String]) {
154 | event.rootRemove += 1
155 | }
156 |
157 | public func backToLast(path _: String, isAnimated _: Bool) {
158 | event.backToLast += 1
159 | }
160 |
161 | public func rootBackToLast(path _: String, isAnimated _: Bool) {
162 | event.rootBackToLast += 1
163 | }
164 |
165 | public func close(isAnimated _: Bool, completeAction _: @escaping () -> Void) {
166 | event.close += 1
167 | }
168 |
169 | public func range(path _: String) -> [String] {
170 | event.range += 1
171 | return value.rangePaths
172 | }
173 |
174 | public func reloadLast(linkItem _: LinkItem, isAnimated _: Bool) {
175 | event.reloadLast += 1
176 | }
177 |
178 | public func rootReloadLast(linkItem _: LinkItem, isAnimated _: Bool) {
179 | event.rootReloadLast += 1
180 | }
181 |
182 | public func alert(target _: NavigationTarget, model _: Alert) {
183 | event.alert += 1
184 | }
185 |
186 | public func send(targetTabPath _: String?, linkItem _: LinkItem) {
187 | event.send += 1
188 | }
189 |
190 | public func currentTabSend(linkItem _: LinkItem) {
191 | event.currentTabSend += 1
192 | }
193 |
194 | public func mainSend(linkItem _: LinkItem) {
195 | event.mainSend += 1
196 | }
197 |
198 | public func allSend(linkItem _: LinkItem) {
199 | event.allSend += 1
200 | }
201 |
202 | public func currentTabAllSend(linkItem _: LinkItem) {
203 | event.currentTabAllSend += 1
204 | }
205 |
206 | public func moveTab(path _: String) {
207 | event.moveTab += 1
208 | }
209 |
210 | public func mergeReplace(linkItem: LinkItem, isAnimated: Bool) {
211 | event.mergeReplace += 1
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/Core/TabLinkNavigator/TabLinkNavigator.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | // MARK: - TabLinkNavigator
4 |
5 | public final class TabLinkNavigator {
6 |
7 | // MARK: Lifecycle
8 |
9 | public init(
10 | routeBuilderItemList: [RouteBuilderOf],
11 | dependency: DependencyType)
12 | {
13 | self.routeBuilderItemList = routeBuilderItemList
14 | self.dependency = dependency
15 | coordinate = .init(sheetDidDismiss: { [weak self] presentVC in
16 | if presentVC.presentedViewController == self?.fullSheetController {
17 | self?.fullSheetController = .none
18 | } else {
19 | self?.modalController = .none
20 | }
21 | presentVC.delegate = .none
22 | })
23 | }
24 |
25 | // MARK: Public
26 |
27 | public let routeBuilderItemList: [RouteBuilderOf]
28 | public let dependency: DependencyType
29 |
30 | public var tabRootPartialNavigators: [TabPartialNavigator] = []
31 |
32 | public var owner: LinkNavigatorItemSubscriberProtocol? = .none
33 |
34 | public weak var mainController: UITabBarController?
35 |
36 | public var currentTabRootPath: String? {
37 | ((mainController?.selectedViewController as? UINavigationController)?.viewControllers.first as? MatchPathUsable)?.matchPath
38 | }
39 |
40 | public var currentPath: String? {
41 | ((mainController?.selectedViewController as? UINavigationController)?.topViewController as? MatchPathUsable)?.matchPath
42 | }
43 |
44 | // MARK: Internal
45 |
46 | var modalController: UINavigationController? = .none
47 | var fullSheetController: UINavigationController? = .none
48 |
49 | // MARK: Private
50 |
51 | // MARK: - Private Properties
52 |
53 | private var coordinate: SheetCoordinate = .init(sheetDidDismiss: { _ in })
54 |
55 | }
56 |
57 | extension TabLinkNavigator {
58 | public func targetController(targetTabPath: String) -> UINavigationController? {
59 | tabRootPartialNavigators.first(where: { $0.getCurrentRootPaths().first == targetTabPath })?.currentTabNavigationController
60 | }
61 |
62 | public func targetPartialNavigator(tabPath: String) -> TabPartialNavigator? {
63 | tabRootPartialNavigators.first(where: { $0.getCurrentRootPaths().first == tabPath })
64 | }
65 | }
66 |
67 | extension TabLinkNavigator {
68 | public func launch(tagItemList: [TabItem]) -> [UINavigationController] {
69 | let partialNavigators = tagItemList
70 | .reduce([(Bool, TabPartialNavigator)]()) { curr, next in
71 | let newNavigatorList = TabPartialNavigator(
72 | rootNavigator: self,
73 | tabItem: next,
74 | routeBuilderItemList: routeBuilderItemList,
75 | dependency: dependency)
76 | return curr + [(next.prefersLargeTitles, newNavigatorList)]
77 | }
78 |
79 | tabRootPartialNavigators = partialNavigators.map(\.1)
80 |
81 | return partialNavigators
82 | .map { prefersLargeTitles, navigator in
83 | let partialNavigationVC = navigator.launch(rootPath: navigator.tabItem.linkItem.pathList.first ?? "")
84 | let item = tagItemList.first(where: { $0.linkItem == navigator.tabItem.linkItem })
85 | partialNavigationVC.navigationController.tabBarItem = item?.tabItem
86 | partialNavigationVC.navigationController.navigationBar.prefersLargeTitles = prefersLargeTitles
87 | return partialNavigationVC.navigationController
88 | }
89 | }
90 | }
91 |
92 | extension TabLinkNavigator {
93 | public func sheetOpen(
94 | subViewController: UINavigationController,
95 | isAnimated: Bool,
96 | type: UIModalPresentationStyle,
97 | presentWillAction: @escaping (UINavigationController) -> Void = { _ in },
98 | presentDidAction: @escaping (UINavigationController) -> Void = { _ in })
99 | {
100 | if modalController != .none {
101 | modalController?.dismiss(animated: true)
102 | modalController = .none
103 | }
104 |
105 | presentWillAction(subViewController)
106 |
107 | switch type {
108 | case .fullScreen, .overFullScreen:
109 | if let fullSheetController {
110 | subViewController.modalPresentationStyle = .formSheet
111 | fullSheetController.present(subViewController, animated: isAnimated)
112 | modalController = subViewController
113 | } else {
114 | mainController?.present(subViewController, animated: isAnimated)
115 | fullSheetController = subViewController
116 | }
117 |
118 | default:
119 | if let fullSheetController {
120 | fullSheetController.present(subViewController, animated: isAnimated)
121 | } else {
122 | mainController?.present(subViewController, animated: isAnimated)
123 | }
124 | modalController = subViewController
125 | }
126 |
127 | subViewController.presentationController?.delegate = coordinate
128 | presentDidAction(subViewController)
129 | }
130 |
131 | public func close(isAnimated: Bool, completion: () -> Void) {
132 | if let modalController {
133 | modalController.dismiss(animated: isAnimated)
134 | self.modalController = .none
135 | } else if let fullSheetController {
136 | fullSheetController.dismiss(animated: isAnimated)
137 | self.fullSheetController = .none
138 | }
139 |
140 | completion()
141 | }
142 |
143 | public func closeAll(isAnimated: Bool, completion: () -> Void) {
144 | mainController?.dismiss(animated: isAnimated)
145 | modalController = .none
146 | fullSheetController = .none
147 | completion()
148 | }
149 |
150 | public func moveTab(targetPath: String) {
151 | guard
152 | let targetController = tabRootPartialNavigators.first(where: { $0.getCurrentRootPaths().first == targetPath })?
153 | .currentTabNavigationController
154 | else { return }
155 |
156 | if mainController?.selectedViewController == targetController {
157 | targetController.popToRootViewController(animated: true)
158 | } else {
159 | mainController?.selectedViewController = targetController
160 | }
161 |
162 | NotificationCenter.default
163 | .post(name: TabbarEventNotification.onSelectedTab, object: targetPath)
164 | }
165 |
166 | public func alert(model: Alert) {
167 | let currentController = modalController ?? fullSheetController ?? mainController?.selectedViewController
168 | currentController?.present(model.build(), animated: true)
169 | }
170 |
171 | /// Sends event to navigators that match path of link item
172 | public func send(linkItem: LinkItem) {
173 | var matchPathUsables: [MatchPathUsable] = []
174 |
175 | matchPathUsables = tabRootPartialNavigators
176 | .flatMap(\.currentTabNavigationController.viewControllers)
177 | .compactMap { $0 as? MatchPathUsable }
178 |
179 | matchPathUsables
180 | .filter { linkItem.pathList.contains($0.matchPath) }
181 | .forEach {
182 | $0.eventSubscriber?.receive(encodedItemString: linkItem.encodedItemString)
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/Protocol/TabLinkNavigatorProtocol.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public protocol TabLinkNavigatorProtocol {
5 |
6 | /// Navigates to the next link item.
7 | ///
8 | /// - Parameters:
9 | /// - linkItem: The link item to navigate to.
10 | /// - isAnimated: A Boolean value that determines whether the navigation is animated.
11 | func next(linkItem: LinkItem, isAnimated: Bool)
12 |
13 | /// Navigates to the root next link item.
14 | ///
15 | /// - Parameters:
16 | /// - linkItem: The link item to navigate to at the root level.
17 | /// - isAnimated: A Boolean value that determines whether the navigation is animated.
18 | func rootNext(linkItem: LinkItem, isAnimated: Bool)
19 |
20 | /// Presents a sheet with the given link item.
21 | ///
22 | /// - Parameters:
23 | /// - linkItem: The link item to present.
24 | /// - isAnimated: A Boolean value that determines whether the presentation is animated.
25 | func sheet(linkItem: LinkItem, isAnimated: Bool)
26 |
27 | /// Presents a full sheet with the given link item.
28 | ///
29 | /// - Parameters:
30 | /// - linkItem: The link item to present.
31 | /// - isAnimated: A Boolean value that determines whether the presentation is animated.
32 | /// - prefersLargeTitles: An optional Boolean value that determines whether the navigation bar should display large titles.
33 | func fullSheet(linkItem: LinkItem, isAnimated: Bool, prefersLargeTitles: Bool?)
34 |
35 | /// Presents a custom sheet with the specified characteristics.
36 | ///
37 | /// - Parameters:
38 | /// - linkItem: The link item to present.
39 | /// - isAnimated: A Boolean value that determines whether the presentation is animated.
40 | /// - iPhonePresentationStyle: The presentation style for iPhone.
41 | /// - iPadPresentationStyle: The presentation style for iPad.
42 | /// - prefersLargeTitles: An optional Boolean value that determines whether the navigation bar should display large titles.
43 | func customSheet(
44 | linkItem: LinkItem,
45 | isAnimated: Bool,
46 | iPhonePresentationStyle: UIModalPresentationStyle,
47 | iPadPresentationStyle: UIModalPresentationStyle,
48 | prefersLargeTitles: Bool?)
49 |
50 | /// Replaces the current link item with the given link item.
51 | ///
52 | /// - Parameters:
53 | /// - linkItem: The new link item to replace the current one.
54 | /// - isAnimated: A Boolean value that determines whether the replacement is animated.
55 | func replace(linkItem: LinkItem, isAnimated: Bool)
56 |
57 | func rootReplace(linkItem: LinkItem, isAnimated: Bool, closeAll: Bool)
58 |
59 | /// Navigates either back or to the next link item based on the current state.
60 | ///
61 | /// - Parameters:
62 | /// - linkItem: The link item to navigate to or back from.
63 | /// - isAnimated: A Boolean value that determines whether the navigation is animated.
64 | func backOrNext(linkItem: LinkItem, isAnimated: Bool)
65 |
66 | /// Navigates either back or to the root next link item based on the current state.
67 | ///
68 | /// - Parameters:
69 | /// - linkItem: The link item to navigate to or back from at the root level.
70 | /// - isAnimated: A Boolean value that determines whether the navigation is animated.
71 | func rootBackOrNext(linkItem: LinkItem, isAnimated: Bool)
72 |
73 | /// Navigates back in the navigation stack.
74 | ///
75 | /// - Parameter isAnimated: A Boolean value that determines whether the navigation is animated.
76 | func back(isAnimated: Bool)
77 |
78 | /// Removes the specified paths from the navigation stack.
79 | ///
80 | /// - Parameter pathList: A list of paths to remove.
81 | func remove(pathList: [String])
82 |
83 | /// Removes the specified paths at the root level from the navigation stack.
84 | ///
85 | /// - Parameter pathList: A list of paths to remove from the root of the navigation stack.
86 | func rootRemove(pathList: [String])
87 |
88 | /// Navigates back to the last specified path.
89 | ///
90 | /// - Parameters:
91 | /// - path: The path to navigate back to.
92 | /// - isAnimated: A Boolean value indicating whether the navigation should be animated.
93 | func backToLast(path: String, isAnimated: Bool)
94 |
95 | /// Navigates back to the last specified root path.
96 | ///
97 | /// - Parameters:
98 | /// - path: The root path to navigate back to.
99 | /// - isAnimated: A Boolean value indicating whether the navigation should be animated.
100 | func rootBackToLast(path: String, isAnimated: Bool)
101 |
102 | /// Closes the navigation interface.
103 | ///
104 | /// - Parameters:
105 | /// - isAnimated: A Boolean value indicating whether the close action should be animated.
106 | /// - completeAction: A closure that gets called when the close action is complete.
107 | func close(isAnimated: Bool, completeAction: @escaping () -> Void)
108 |
109 | /// Returns the range of paths for the specified path.
110 | ///
111 | /// - Parameter path: The path to get the range for.
112 | /// - Returns: An array of paths representing the range.
113 | func range(path: String) -> [String]
114 |
115 | /// Reloads the last current item with the specified items.
116 | ///
117 | /// - Parameters:
118 | /// - items: A string representing the raw QueryString for the items to reload.
119 | /// - isAnimated: A Boolean value indicating whether the reload should be animated.
120 | func reloadLast(linkItem: LinkItem, isAnimated: Bool)
121 |
122 | /// Reloads the last root item with the specified items.
123 | ///
124 | /// - Parameters:
125 | /// - items: A string representing the raw QueryString for the items to reload.
126 | /// - isAnimated: A Boolean value indicating whether the reload should be animated.
127 | func rootReloadLast(linkItem: LinkItem, isAnimated: Bool)
128 |
129 | /// Displays an alert with the specified target and model. Depending on the target parameter,
130 | /// the alert is displayed either on the root or the sub-controller.
131 | ///
132 | /// - Parameters:
133 | /// - model: The model containing information for building and displaying the alert.
134 | func alert(model: Alert)
135 |
136 | /// Sends the specified link item to a specific subscriber ('sub') or page sheet within the current navigation stack.
137 | /// This method facilitates communication between pages, allowing data to be transferred to a specific 'sub' or page sheet as defined in the link item.
138 | ///
139 | /// - Parameter item: The link item encapsulating the data and the target path to be sent.
140 | func send(targetTabPath: String?, linkItem: LinkItem)
141 |
142 | /// Sends the specified link item to the root controller which governs the page sheets (sub). This method is mainly used for communications directly involving the root controller which has overarching control over page sheets.
143 | ///
144 | /// - Parameter item: The link item to be sent to the root controller.
145 | func currentTabSend(linkItem: LinkItem)
146 |
147 | /// Sends the main items directly to the appMain, which is a NavigationController that wraps around the link navigator. This method allows for data communication directly with the appMain, facilitating broad-reaching communications within the app.
148 | ///
149 | /// - Parameter item: The main items to be sent, often containing key-value pairs of data to be communicated to the appMain.
150 | func mainSend(linkItem: LinkItem)
151 |
152 | /// Sends the specified items to all designated receivers, including both the 'sub' page sheets and the root controllers, within the current navigation stack. This allows for a widespread dissemination of data across various levels of the navigation stack.
153 | ///
154 | /// - Parameter item: The items to be sent to all receivers, encapsulating data that may be relevant across multiple page sheets and root controllers.
155 | func allSend(linkItem: LinkItem)
156 |
157 | /// Sends the specified items to all root controllers in the navigation stack. This method is instrumental in disseminating information broadly at the root level, which governs the behaviors and states of the 'sub' page sheets.
158 | ///
159 | /// - Parameter item: The items to be sent to all root controllers, typically containing information pertinent across all root level pages.
160 | func currentTabAllSend(linkItem: LinkItem)
161 |
162 | /// Collapses overlap: keeps view controllers before the first duplicated `matchPath`
163 | /// and appends new controllers built from `linkItem`.
164 | ///
165 | /// - Parameters:
166 | /// - linkItem: The link item used to build the new sequence of view
167 | /// controllers.
168 | /// - isAnimated: A Boolean value indicating whether the replacement should
169 | /// be animated.
170 | func mergeReplace(linkItem: LinkItem, isAnimated: Bool)
171 |
172 | func moveTab(path: String)
173 | }
174 |
--------------------------------------------------------------------------------
/Sources/LinkNavigator/Core/Protocol/LinkNavigatorProtocol.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | // MARK: - LinkNavigatorURLEncodedItemProtocol
4 |
5 | /// `LinkNavigatorURLEncodedItemProtocol` defines the navigation interface for handling various link-related actions within an application.
6 | public protocol LinkNavigatorProtocol {
7 |
8 | /// Navigates to the next link item.
9 | ///
10 | /// - Parameters:
11 | /// - linkItem: The link item to navigate to.
12 | /// - isAnimated: A Boolean value that determines whether the navigation is animated.
13 | func next(linkItem: LinkItem, isAnimated: Bool)
14 |
15 | /// Navigates to the root next link item.
16 | ///
17 | /// - Parameters:
18 | /// - linkItem: The link item to navigate to at the root level.
19 | /// - isAnimated: A Boolean value that determines whether the navigation is animated.
20 | func rootNext(linkItem: LinkItem, isAnimated: Bool)
21 |
22 | /// Presents a sheet with the given link item.
23 | ///
24 | /// - Parameters:
25 | /// - linkItem: The link item to present.
26 | /// - isAnimated: A Boolean value that determines whether the presentation is animated.
27 | func sheet(linkItem: LinkItem, isAnimated: Bool)
28 |
29 | /// Presents a sheet with the given link item.
30 | ///
31 | /// - Parameters:
32 | /// - linkItem: The link item to present.
33 | /// - isAnimated: A Boolean value that determines whether the presentation is animated.
34 | /// - configuration: A `DetentConfiguration` object that defines the appearance and behavior of the sheet.
35 | ///
36 | /// - Available from iOS 15.0 and later.
37 | @available(iOS 15.0, *)
38 | func detentSheet(
39 | linkItem: LinkItem,
40 | isAnimated: Bool,
41 | configuration: DetentConfiguration)
42 |
43 | /// Presents a full sheet with the given link item.
44 | ///
45 | /// - Parameters:
46 | /// - linkItem: The link item to present.
47 | /// - isAnimated: A Boolean value that determines whether the presentation is animated.
48 | /// - prefersLargeTitles: An optional Boolean value that determines whether the navigation bar should display large titles.
49 | func fullSheet(linkItem: LinkItem, isAnimated: Bool, prefersLargeTitles: Bool?)
50 |
51 | /// Presents a custom sheet with the specified characteristics.
52 | ///
53 | /// - Parameters:
54 | /// - linkItem: The link item to present.
55 | /// - isAnimated: A Boolean value that determines whether the presentation is animated.
56 | /// - iPhonePresentationStyle: The presentation style for iPhone.
57 | /// - iPadPresentationStyle: The presentation style for iPad.
58 | /// - prefersLargeTitles: An optional Boolean value that determines whether the navigation bar should display large titles.
59 | func customSheet(
60 | linkItem: LinkItem,
61 | isAnimated: Bool,
62 | iPhonePresentationStyle: UIModalPresentationStyle,
63 | iPadPresentationStyle: UIModalPresentationStyle,
64 | prefersLargeTitles: Bool?)
65 |
66 | /// Replaces the current link item with the given link item.
67 | ///
68 | /// - Parameters:
69 | /// - linkItem: The new link item to replace the current one.
70 | /// - isAnimated: A Boolean value that determines whether the replacement is animated.
71 | func replace(linkItem: LinkItem, isAnimated: Bool)
72 |
73 | /// Collapses overlap: keeps view controllers before the first duplicated `matchPath`
74 | /// and appends new controllers built from `linkItem`.
75 | ///
76 | /// - Parameters:
77 | /// - linkItem: The link item used to build the new sequence of view
78 | /// controllers.
79 | /// - isAnimated: A Boolean value indicating whether the replacement should
80 | /// be animated.
81 | func mergeReplace(linkItem: LinkItem, isAnimated: Bool)
82 |
83 | /// Navigates either back or to the next link item based on the current state.
84 | ///
85 | /// - Parameters:
86 | /// - linkItem: The link item to navigate to or back from.
87 | /// - isAnimated: A Boolean value that determines whether the navigation is animated.
88 | func backOrNext(linkItem: LinkItem, isAnimated: Bool)
89 |
90 | /// Navigates either back or to the root next link item based on the current state.
91 | ///
92 | /// - Parameters:
93 | /// - linkItem: The link item to navigate to or back from at the root level.
94 | /// - isAnimated: A Boolean value that determines whether the navigation is animated.
95 | func rootBackOrNext(linkItem: LinkItem, isAnimated: Bool)
96 |
97 | /// Navigates back in the navigation stack.
98 | ///
99 | /// - Parameter isAnimated: A Boolean value that determines whether the navigation is animated.
100 | func back(isAnimated: Bool)
101 |
102 | /// Removes the specified paths from the navigation stack.
103 | ///
104 | /// - Parameter pathList: A list of paths to remove.
105 | func remove(pathList: [String])
106 |
107 | /// Removes the specified paths at the root level from the navigation stack.
108 | ///
109 | /// - Parameter pathList: A list of paths to remove from the root of the navigation stack.
110 | func rootRemove(pathList: [String])
111 |
112 | /// Navigates back to the last specified path.
113 | ///
114 | /// - Parameters:
115 | /// - path: The path to navigate back to.
116 | /// - isAnimated: A Boolean value indicating whether the navigation should be animated.
117 | func backToLast(path: String, isAnimated: Bool)
118 |
119 | /// Navigates back to the last specified root path.
120 | ///
121 | /// - Parameters:
122 | /// - path: The root path to navigate back to.
123 | /// - isAnimated: A Boolean value indicating whether the navigation should be animated.
124 | func rootBackToLast(path: String, isAnimated: Bool)
125 |
126 | /// Closes the navigation interface.
127 | ///
128 | /// - Parameters:
129 | /// - isAnimated: A Boolean value indicating whether the close action should be animated.
130 | /// - completeAction: A closure that gets called when the close action is complete.
131 | func close(isAnimated: Bool, completeAction: @escaping () -> Void)
132 |
133 | /// Returns the range of paths for the specified path.
134 | ///
135 | /// - Parameter path: The path to get the range for.
136 | /// - Returns: An array of paths representing the range.
137 | func range(path: String) -> [String]
138 |
139 | /// Reloads the last root item with the specified items.
140 | ///
141 | /// - Parameters:
142 | /// - items: A string representing the raw QueryString for the items to reload.
143 | /// - isAnimated: A Boolean value indicating whether the reload should be animated.
144 | func rootReloadLast(items: LinkItem, isAnimated: Bool)
145 |
146 | /// Displays an alert with the specified target and model. Depending on the target parameter,
147 | /// the alert is displayed either on the root or the sub-controller.
148 | ///
149 | /// - Parameters:
150 | /// - target: The target specifying where to display the alert. It can be root, sub, or default.
151 | /// If the target is default, it determines whether to present the alert on sub or root based
152 | /// on the `isSubNavigatorActive` property.
153 | /// - model: The model containing information for building and displaying the alert.
154 | func alert(target: NavigationTarget, model: Alert)
155 |
156 | /// Sends the specified link item to a specific subscriber ('sub') or page sheet within the current navigation stack.
157 | /// This method facilitates communication between pages, allowing data to be transferred to a specific 'sub' or page sheet as defined in the link item.
158 | ///
159 | /// - Parameter item: The link item encapsulating the data and the target path to be sent.
160 | func send(item: LinkItem)
161 |
162 | /// Sends the specified link item to the root controller which governs the page sheets (sub). This method is mainly used for communications directly involving the root controller which has overarching control over page sheets.
163 | ///
164 | /// - Parameter item: The link item to be sent to the root controller.
165 | func rootSend(item: LinkItem)
166 |
167 | /// Sends the main items directly to the appMain, which is a NavigationController that wraps around the link navigator. This method allows for data communication directly with the appMain, facilitating broad-reaching communications within the app.
168 | ///
169 | /// - Parameter item: The main items to be sent, often containing key-value pairs of data to be communicated to the appMain.
170 | func mainSend(item: LinkItem)
171 |
172 | /// Sends the specified items to all designated receivers, including both the 'sub' page sheets and the root controllers, within the current navigation stack. This allows for a widespread dissemination of data across various levels of the navigation stack.
173 | ///
174 | /// - Parameter item: The items to be sent to all receivers, encapsulating data that may be relevant across multiple page sheets and root controllers.
175 | func allSend(item: LinkItem)
176 |
177 | /// Sends the specified items to all root controllers in the navigation stack. This method is instrumental in disseminating information broadly at the root level, which governs the behaviors and states of the 'sub' page sheets.
178 | ///
179 | /// - Parameter item: The items to be sent to all root controllers, typically containing information pertinent across all root level pages.
180 | func allRootSend(item: LinkItem)
181 | }
182 |
--------------------------------------------------------------------------------