Adds a viewmodifier for SwiftUI for routing
This commit is contained in:
parent
4471d4d946
commit
eeee6fb04d
|
@ -4,6 +4,9 @@ import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "RouteKit",
|
name: "RouteKit",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v14),
|
||||||
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "RouteKit",
|
name: "RouteKit",
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
29984C392763E400006C359A /* ViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29984C382763E400006C359A /* ViewModifier.swift */; };
|
||||||
|
29984C3A2763E400006C359A /* ViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29984C382763E400006C359A /* ViewModifier.swift */; };
|
||||||
430EC1EA20598F7600F84EA2 /* RouteKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 430EC1E120598F7600F84EA2 /* RouteKit.framework */; };
|
430EC1EA20598F7600F84EA2 /* RouteKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 430EC1E120598F7600F84EA2 /* RouteKit.framework */; };
|
||||||
430EC1F820598FDB00F84EA2 /* AnyRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435264D82048705600F23D74 /* AnyRoute.swift */; };
|
430EC1F820598FDB00F84EA2 /* AnyRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435264D82048705600F23D74 /* AnyRoute.swift */; };
|
||||||
430EC1F920598FDB00F84EA2 /* Navigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435BFF8320583A9000346484 /* Navigator.swift */; };
|
430EC1F920598FDB00F84EA2 /* Navigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435BFF8320583A9000346484 /* Navigator.swift */; };
|
||||||
|
@ -57,6 +59,7 @@
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
29984C382763E400006C359A /* ViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifier.swift; sourceTree = "<group>"; };
|
||||||
430EC1DA20598E2C00F84EA2 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
430EC1DA20598E2C00F84EA2 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
||||||
430EC1E120598F7600F84EA2 /* RouteKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RouteKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
430EC1E120598F7600F84EA2 /* RouteKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RouteKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
430EC1E920598F7600F84EA2 /* RouteKit-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RouteKit-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
430EC1E920598F7600F84EA2 /* RouteKit-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RouteKit-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -147,6 +150,7 @@
|
||||||
436CD12B2050589800BC228D /* PayloadDecoder.swift */,
|
436CD12B2050589800BC228D /* PayloadDecoder.swift */,
|
||||||
435264D92048705600F23D74 /* Route.swift */,
|
435264D92048705600F23D74 /* Route.swift */,
|
||||||
435BFF7D2058332700346484 /* Router.swift */,
|
435BFF7D2058332700346484 /* Router.swift */,
|
||||||
|
29984C382763E400006C359A /* ViewModifier.swift */,
|
||||||
436CD126204F02F400BC228D /* Extensions */,
|
436CD126204F02F400BC228D /* Extensions */,
|
||||||
);
|
);
|
||||||
path = RouteKit;
|
path = RouteKit;
|
||||||
|
@ -398,7 +402,7 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
|
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||||
};
|
};
|
||||||
439B5875231280D500BA9D21 /* SwiftFormat */ = {
|
439B5875231280D500BA9D21 /* SwiftFormat */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
@ -444,6 +448,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
430EC1FA20598FDB00F84EA2 /* Pattern.swift in Sources */,
|
430EC1FA20598FDB00F84EA2 /* Pattern.swift in Sources */,
|
||||||
|
29984C392763E400006C359A /* ViewModifier.swift in Sources */,
|
||||||
430EC1FF20598FDB00F84EA2 /* URL+Extension.swift in Sources */,
|
430EC1FF20598FDB00F84EA2 /* URL+Extension.swift in Sources */,
|
||||||
430EC1F820598FDB00F84EA2 /* AnyRoute.swift in Sources */,
|
430EC1F820598FDB00F84EA2 /* AnyRoute.swift in Sources */,
|
||||||
430EC1F920598FDB00F84EA2 /* Navigator.swift in Sources */,
|
430EC1F920598FDB00F84EA2 /* Navigator.swift in Sources */,
|
||||||
|
@ -472,6 +477,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
430EC2232059932F00F84EA2 /* Pattern.swift in Sources */,
|
430EC2232059932F00F84EA2 /* Pattern.swift in Sources */,
|
||||||
|
29984C3A2763E400006C359A /* ViewModifier.swift in Sources */,
|
||||||
430EC2282059933200F84EA2 /* URL+Extension.swift in Sources */,
|
430EC2282059933200F84EA2 /* URL+Extension.swift in Sources */,
|
||||||
430EC2212059932F00F84EA2 /* AnyRoute.swift in Sources */,
|
430EC2212059932F00F84EA2 /* AnyRoute.swift in Sources */,
|
||||||
430EC2222059932F00F84EA2 /* Navigator.swift in Sources */,
|
430EC2222059932F00F84EA2 /* Navigator.swift in Sources */,
|
||||||
|
@ -522,7 +528,7 @@
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
INFOPLIST_FILE = "${SRCROOT}/Sources/${PROJECT}/Info.plist";
|
INFOPLIST_FILE = "${SRCROOT}/Sources/${PROJECT}/Info.plist";
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = woxtu.RouteKit;
|
PRODUCT_BUNDLE_IDENTIFIER = woxtu.RouteKit;
|
||||||
PRODUCT_NAME = "${PROJECT}";
|
PRODUCT_NAME = "${PROJECT}";
|
||||||
|
@ -543,7 +549,7 @@
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
INFOPLIST_FILE = "${SRCROOT}/Sources/${PROJECT}/Info.plist";
|
INFOPLIST_FILE = "${SRCROOT}/Sources/${PROJECT}/Info.plist";
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = woxtu.RouteKit;
|
PRODUCT_BUNDLE_IDENTIFIER = woxtu.RouteKit;
|
||||||
PRODUCT_NAME = "${PROJECT}";
|
PRODUCT_NAME = "${PROJECT}";
|
||||||
|
@ -712,6 +718,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -767,6 +774,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
|
|
@ -27,6 +27,15 @@
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "430EC1E020598F7600F84EA2"
|
||||||
|
BuildableName = "RouteKit.framework"
|
||||||
|
BlueprintName = "RouteKit-iOS"
|
||||||
|
ReferencedContainer = "container:RouteKit.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
|
@ -39,17 +48,6 @@
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "430EC1E020598F7600F84EA2"
|
|
||||||
BuildableName = "RouteKit.framework"
|
|
||||||
BlueprintName = "RouteKit-iOS"
|
|
||||||
ReferencedContainer = "container:RouteKit.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
|
@ -70,8 +68,6 @@
|
||||||
ReferencedContainer = "container:RouteKit.xcodeproj">
|
ReferencedContainer = "container:RouteKit.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
|
|
@ -6,46 +6,46 @@
|
||||||
// Copyright (c) 2018 woxtu. All rights reserved.
|
// Copyright (c) 2018 woxtu. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#if os(iOS) || os(tvOS)
|
import Foundation
|
||||||
import Foundation
|
import UIKit
|
||||||
import UIKit
|
|
||||||
|
|
||||||
open class Navigator {
|
open class Navigator {
|
||||||
private static var router = Router<UIViewController>()
|
var router = Router<UIViewController>()
|
||||||
|
|
||||||
private static var rootViewController: UIViewController? {
|
let rootViewController: UIViewController
|
||||||
return UIApplication.shared.keyWindow?.rootViewController
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func append<R>(route: R) where R: Route, R.Response == UIViewController {
|
public init(rootViewController: UIViewController) {
|
||||||
router.append(route: route)
|
self.rootViewController = rootViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
public func append<R>(route: R) where R: Route, R.Response == UIViewController {
|
||||||
public static func push(url: URL, animated: Bool) -> UIViewController? {
|
router.append(route: route)
|
||||||
if
|
}
|
||||||
let viewController = self.router.push(url: url),
|
|
||||||
let navigationController = self.rootViewController as? UINavigationController {
|
|
||||||
navigationController.pushViewController(viewController, animated: animated)
|
|
||||||
return viewController
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public static func present(url: URL, animated: Bool, transform: ((UIViewController) -> UIViewController) = { $0 }) -> UIViewController? {
|
public func push(url: URL, animated: Bool) -> UIViewController? {
|
||||||
return present(url: url, animated: animated, transform: transform, completion: nil)
|
if
|
||||||
}
|
let viewController = self.router.push(url: url),
|
||||||
|
let navigationController = self.rootViewController as? UINavigationController {
|
||||||
|
navigationController.pushViewController(viewController, animated: animated)
|
||||||
|
return viewController
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public static func present(url: URL, animated: Bool, transform: ((UIViewController) -> UIViewController) = { $0 }, completion: (() -> Void)? = nil) -> UIViewController? {
|
public func present(url: URL, animated: Bool, transform: ((UIViewController) -> UIViewController) = { $0 }) -> UIViewController? {
|
||||||
if let viewController = self.router.push(url: url) {
|
return present(url: url, animated: animated, transform: transform, completion: nil)
|
||||||
rootViewController?.present(transform(viewController), animated: animated, completion: completion)
|
}
|
||||||
return viewController
|
|
||||||
} else {
|
@discardableResult
|
||||||
return nil
|
public func present(url: URL, animated: Bool, transform: ((UIViewController) -> UIViewController) = { $0 }, completion: (() -> Void)? = nil) -> UIViewController? {
|
||||||
}
|
if let viewController = self.router.push(url: url) {
|
||||||
}
|
rootViewController.present(transform(viewController), animated: animated, completion: completion)
|
||||||
}
|
return viewController
|
||||||
#endif
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// ViewModifier.swift
|
||||||
|
// RouteKit
|
||||||
|
//
|
||||||
|
// Created by Larry Tran on 12/10/21.
|
||||||
|
// Copyright © 2021 woxtu. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RouterViewModifier: ViewModifier {
|
||||||
|
|
||||||
|
@Environment(\.navigator) var navigator
|
||||||
|
|
||||||
|
let url: URL
|
||||||
|
let presentation: Presentation
|
||||||
|
let animated: Bool
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.onTapGesture {
|
||||||
|
switch presentation {
|
||||||
|
case .present:
|
||||||
|
navigator.present(url: url, animated: animated)
|
||||||
|
case .push:
|
||||||
|
navigator.push(url: url, animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func routeOnTap(url: URL, presentation: Presentation, animated: Bool) -> some View {
|
||||||
|
self.modifier(RouterViewModifier(url: url, presentation: presentation, animated: animated))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Presentation {
|
||||||
|
case present
|
||||||
|
case push
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct NavigatorKey: EnvironmentKey {
|
||||||
|
static let defaultValue = Navigator()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
var navigator: Navigator {
|
||||||
|
get { self[NavigatorKey.self] }
|
||||||
|
set { self[NavigatorKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,14 +12,16 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class NavigatorTests: XCTestCase {
|
class NavigatorTests: XCTestCase {
|
||||||
|
let navigtor = Navigator()
|
||||||
|
|
||||||
func test() {
|
func test() {
|
||||||
let transform = { (vc: UIViewController) -> UIViewController in vc }
|
let transform = { (vc: UIViewController) -> UIViewController in vc }
|
||||||
let completion = { () -> Void in }
|
let completion = { () -> Void in }
|
||||||
|
|
||||||
Navigator.present(url: URL(string: "/")!, animated: true)
|
navigtor.present(url: URL(string: "/")!, animated: true)
|
||||||
Navigator.present(url: URL(string: "/")!, animated: true, transform: transform)
|
navigtor.present(url: URL(string: "/")!, animated: true, transform: transform)
|
||||||
Navigator.present(url: URL(string: "/")!, animated: true, completion: completion)
|
navigtor.present(url: URL(string: "/")!, animated: true, completion: completion)
|
||||||
Navigator.present(url: URL(string: "/")!, animated: true, transform: transform, completion: completion)
|
navigtor.present(url: URL(string: "/")!, animated: true, transform: transform, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue