Adds a viewmodifier for SwiftUI for routing

This commit is contained in:
Larry 2021-12-10 11:56:57 -08:00
parent 4471d4d946
commit eeee6fb04d
6 changed files with 119 additions and 57 deletions

View File

@ -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",

View File

@ -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";

View File

@ -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"

View File

@ -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
}
}
}

View File

@ -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 }
}
}

View File

@ -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