├── 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 | --------------------------------------------------------------------------------