✨ LinkNavigator 是一个帮助你在 SwiftUI 中轻松进行页面导航的库。
以下 README 的翻译由社区成员贡献:
如果你想贡献翻译,请打开一个 PR,并附上指向 Gist 的链接!
推送一个或多个页面。
navigator.next(paths: ["page1", "page2"], items: [:], isAnimated: true)
弹出(pop)一个或多个页面。
navigator.remove(paths: ["pageToRemove"])
返回上一页或简单地关闭模态视图。
navigator.back(isAnimated: true)
跳转到你想要的页面。如果该页面已经在导航堆栈中,则返回到该页面。否则,如果该页面不在堆栈中,则推送一个新页面。
navigator.backOrNext(path: "targetPage", items: [:], isAnimated: true)
用新的导航堆栈替换当前的导航堆栈。
navigator.replace(paths: ["main", "depth1", "depth2"], items: [:], isAnimated: true)
以 sheet 或全屏覆盖的方式打开页面。
navigator.sheet(paths: ["sheetPage"], items: [:], isAnimated: true)
navigator.fullSheet(paths: ["page1", "page2"], items: [:], isAnimated: true, prefersLargeTitles: false)
关闭模态视图并调用完成闭包 (completion closure)。
navigator.close(isAnimated: true) { print("modal dismissed!") }
显示系统警报。
let alertModel = Alert(
title: "Title",
message: "message",
buttons: [.init(title: "OK", style: .default, action: { print("OK tapped") })],
flagType: .default)
navigator.alert(target: .default, model: alertModel)
编辑复杂的路径并使用它。
// current navigation stack == ["home", "depth1", "depth2", "depth3"]
// target stack == ["home", "depth1", "newDepth"]
var new = navigator.range(path: "depth1") + ["newDepth"]
navigator.replace(paths: new, items: [:], isAnimated: true)
控制模态视图后面的页面。
navigator.rootNext(paths: ["targetPage"], items: [:], isAnimated: true)
navigator.rootBackOrNext(path: "targetPage", items: [:], isAnimated: true)
你可以分别为 iPhone 和 iPad 选择模态呈现样式。
navigator.customSheet(
paths: ["sheetPage"],
items: [:],
isAnimated: true,
iPhonePresentationStyle: .fullScreen,
iPadPresentationStyle: .pageSheet,
prefersLargeTitles: .none)
强制重新加载模态视图后面的最后一个页面。 当你需要再次调用 onAppear(perform:) 时,这非常有用。
navigator.rootReloadLast(items: [:], isAnimated: false)
LinkNavigator 提供了 2 个示例应用程序。
要在你的 SwiftUI 项目中安装 LinkNavigator,你需要实现 4 个文件。
你可以自由编辑类型名称。 在以下示例中,为了清楚起见,使用了简单的名称。
按顺序描述:AppDependency -> AppRouterGroup -> AppDelegate -> AppMain
// AppDependency.swift
// A type that manages external dependencies.
import LinkNavigator
struct AppDependency: DependencyType { } // you need to adopt DependencyType protocol here.
// AppRouterGroup.swift
// A type that manages the pages you want to go with LinkNavigator.
import LinkNavigator
struct AppRouterGroup {
var routers: [RouteBuilder] {
[
HomeRouteBuilder(), // to be implemented in Step 3
Page1RouteBuilder(),
Page2RouteBuilder(),
Page3RouteBuilder(),
Page4RouteBuilder(),
]
}
}
// AppDelegate.swift
// A type that manages the navigator injected with external dependencies and pages.
import SwiftUI
import LinkNavigator
final class AppDelegate: NSObject {
var navigator: LinkNavigator {
LinkNavigator(dependency: AppDependency(), builders: AppRouterGroup().routers)
}
}
extension AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
true
}
}
// AppMain.swift
// A type that sets the starting page of the Application.
import SwiftUI
import LinkNavigator
@main
struct AppMain: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
var navigator: LinkNavigator {
appDelegate.navigator
}
var body: some Scene {
WindowGroup {
navigator
.launch(paths: ["home"], items: [:]) // the argument of 'paths' becomes starting pages.
.onOpenURL { url in
// in case you need deep link navigation,
// deep links should be processed here.
}
}
}
}
在页面 struct 类型中添加一个 navigator
属性,以便在初始化时注入。
根据架构的特性,自由更改 navigator 属性的位置并使用它。 例如,你可以将其放在 ViewModel
或 Environment
中。
struct HomePage: View {
let navigator: LinkNavigatorType
var body: some View {
...
}
}
为每个页面创建一个采用 RouteBuilder
协议的 struct 类型。
以这种方式创建的 RouteBuilder struct 在 AppRouterGroup 类型中收集和管理。
import LinkNavigator
import SwiftUI
struct HomeRouteBuilder: RouteBuilder {
var matchPath: String { "home" }
var build: (LinkNavigatorType, [String: String], DependencyType) -> MatchingViewController? {
{ navigator, items, dependency in
return WrappingController(matchPath: matchPath) {
HomePage(navigator: navigator)
}
}
}
}
LinkNavigator 支持 Swift Package Manager。
File
菜单 -> 选择 Add Packages...
。Package.swift
中添加以下内容。let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
dependencies: [
.package(url: "https://github.com/interactord/LinkNavigator.git", .upToNextMajor(from: "0.6.1"))
],
targets: [
.target(
name: "MyPackage",
dependencies: ["LinkNavigator"])
]
)
/// in AppMain.swift (MVI)
/// To use for route navigation, set the prefersLargeTitles parameter to true in the launch method.
navigator
.launch(paths: ["home"], items: [:], prefersLargeTitles: true)
/// in HomeView.swift (MVI)
/// To specify the display mode of the navigation bar title, use the navigationBarTitleDisplayMode (.line, .large, .automatic) in the SwiftUI screen of each screen.
ScrollView {
....
}
.navigationBarTitleDisplayMode(.large)
.navigationTitle("Home")
/// If you want to use it in fullSheet or customSheet,
/// Home.intent (MVI)
/// To enable large titles, set the prefersLargeTitles variable to true. To maintain the current settings, use .none.
navigator.fullSheet(paths: ["page1", "page2"], items: [:], isAnimated: true, prefersLargeTitles: true)
问:我想知道如果我想将 IgnoringSafeArea 应用于特定部分或整个屏幕,该怎么做?
navigator
.launch(paths: ["home"], items: [:], prefersLargeTitles: true)
/// - Note:
/// If you are using the ignoresSafeArea property to ignore the safe area on an internal screen,
/// please add the corresponding code to the part where you first execute the LinkNavigator.
.ignoresSafeArea()
问:在视图控制器中,我需要在调用屏幕时处理各种任务,例如导航或调用 Firebase 事件。我应该如何处理?
import SwiftUI
public final class DebugWrappingViewController<Content: View>: UIHostingController<Content>, MatchPathUsable {
// MARK: Lifecycle
public init(
matchPath: String,
trackEventUseCase: TrackEventUseCase,
@ViewBuilder content: () -> Content)
{
self.matchPath = matchPath
self.eventSubscriber = eventSubscriber
self.trackEventUseCase = trackEventUseCase
super.init(rootView: content())
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("✂️ \(matchPath) deinit...")
}
// MARK: Public
public let matchPath: String
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = SystemColor.Background.Default.Base.getColor()
print("🚗 \(matchPath)")
trackEventUseCase.sendEvent(.screen(matchPath))
}
// MARK: Private
private let trackEventUseCase: TrackEventUseCase
}
该库是在 MIT 许可证下发布的。 有关详细信息,请参阅 LICENSE。