文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

SwiftUI怎么自定义导航

2023-06-30 18:58

关注

这篇文章主要介绍“SwiftUI怎么自定义导航”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SwiftUI怎么自定义导航”文章能帮助大家解决问题。

前言

默认情况下,SwiftUI提供的各种导航API在很大程度上是以用户直接输入为中心的——也就是说,导航是在系统响应例如按钮的点击和标签切换等事件时由系统本身处理的。

然而,有时我们可能想更直接地控制应用程序的导航执行方式,尽管SwiftUI在这方面仍然不如UIKit或AppKit灵活,但它确实提供了相当多的方法,让我们在构建的视图中执行完全自定义的导航。

切换标签(tabs)

让我们先来看看我们如何能控制当前在TabView中显示的标签。通常情况下,当用户手动点击每个标签栏中的一个项目时,标签就会被切换,但是通过在一个给定的TabView中注入一个选择(selection)绑定,我们可以观察并控制当前显示的标签。在这里,我们要做的就是在两个标签之间切换,这两个标签是用整数0和1标记的:复制

struct RootView: View {    @State private var activeTabIndex = 0    var body: some View {        TabView(selection: $activeTabIndex) {            Button("Switch to tab B") {                activeTabIndex = 1            }            .tag(0)            .tabItem { Label("Tab A", systemImage: "a.circle") }            Button("Switch to tab A") {                activeTabIndex = 0            }            .tag(1)            .tabItem { Label("Tab B", systemImage: "b.circle") }        }    }}

但真正好的地方是,在识别和切换标签时,我们并不仅仅局限于使用整数。相反,我们可以自由地使用任何Hashable值来表示每个标签——例如通过使用一个枚举,其中包含我们想要显示的每个标签的情况。然后我们可以将这部分状态封装在一个ObservableObject中,这样我们就可以很容易地注入到我们的视图层次环境中:

enum Tab {    case home    case search    case settings}class TabController: ObservableObject {    @Published var activeTab = Tab.home    func open(_ tab: Tab) {        activeTab = tab    }}

有了上述内容,我们现在可以用新的Tab类型来标记TabView中的每个视图,如果我们再把TabController注入到视图层次结构的环境中,那么其中的任何视图都可以随时切换显示的Tab。

struct RootView: View {    @StateObject private var tabController = TabController()    var body: some View {        TabView(selection: $tabController.activeTab) {            HomeView()                .tag(Tab.home)                .tabItem { Label("Home", systemImage: "house") }            SearchView()                .tag(Tab.search)                .tabItem { Label("Search", systemImage: "magnifyingglass") }            SettingsView()                .tag(Tab.settings)                .tabItem { Label("Settings", systemImage: "gearshape") }        }        .environmentObject(tabController)    }}

例如,现在我们的HomeView可以使用一个完全自定义的按钮切换到设置标签——它只需要从环境中获取我们的TabController,然后它可以调用open方法来执行标签切换,像这样:

struct HomeView: View {    @EnvironmentObject private var tabController: TabController    var body: some View {        ScrollView {            ...            Button("Open settings") {                tabController.open(.settings)            }        }    }}

很好! 另外,由于TabController是一个完全由我们控制的对象,我们也可以用它来切换主视图层次结构以外的标签。例如,我们可能想根据推送通知或其他类型的服务器事件来切换标签,现在可以通过调用上述视图代码中的相同的open方法来完成。

要了解更多关于环境对象以及SwiftUI状态管理系统的其余部分,请查看本指南。

控制导航堆栈

就像标签视图一样,SwiftUI的NavigationView也可以被编程自定义控制。例如,假设我们正在开发一个应用程序,在其主导航堆栈中显示一个日历视图作为根视图,然后用户可以通过点击位于该应用程序导航栏中的编辑按钮来打开一个日历编辑视图。为了连接这两个视图,我们使用了一个NavigationLink,每当点击一个给定的视图时,它就会自动将其压入到导航栈中:

struct RootView: View {    @ObservedObject var calendarController: CalendarController    var body: some View {        NavigationView {            CalendarView(                calendar: calendarController.calendar            )            .toolbar {                ToolbarItem(placement: .navigationBarTrailing) {                    NavigationLink("Edit") {              CalendarEditView(                  calendar: $calendarController.calendar              )              .navigationTitle("Edit your calendar")          }                }            }            .navigationTitle("Your calendar")        }        .navigationViewStyle(.stack)    }}

在这种情况下,我们在所有设备上使用堆栈式导航风格,甚至是iPad,而不是让系统选择使用哪种导航风格。

现在我们假设,我们想让我们的CalendarView以自定义方式显示其编辑视图,而不需要构建一个单独的实例。要做到这一点,我们可以在编辑按钮的NavigationLink中注入一个isActive绑定,然后将其传递给我们的CalendarView:

struct RootView: View {    @ObservedObject var calendarController: CalendarController    @State private var isEditViewShown = false    var body: some View {        NavigationView {            CalendarView(                calendar: calendarController.calendar,                isEditViewShown: $isEditViewShown            )            .toolbar {                ToolbarItem(placement: .navigationBarTrailing) {                    NavigationLink("Edit", isActive: $isEditViewShown) {                        CalendarEditView(                            calendar: $calendarController.calendar                        )                        .navigationTitle("Edit your calendar")                    }                }            }            .navigationTitle("Your calendar")        }        .navigationViewStyle(.stack)    }}

如果我们现在也更新CalendarView,使其使用@Binding绑定属性接受上述值,那么现在只要我们想显示我们的编辑视图,就可以简单地将该属性设置为true,我们的根视图的NavigationLink将自动被触发:

struct CalendarView: View {    var calendar: Calendar    @Binding var isEditViewShown: Bool    var body: some View {        ScrollView {            ...            Button("Edit calendar settings") {                isEditViewShown = true            }        }    }}

当然,我们也可以选择将isEditViewShown属性封装在某种形式的ObservableObject中,例如NavigationController,就像我们之前处理TabView时那样。

这就是我们如何以自定义编程方式触发显示在我们的用户界面中的NavigationLink——但如果我们想在不给用户任何直接控制的情况下执行这种导航呢?

例如,我们现在假设我们正在开发一个包括导出功能的视频编辑应用程序。当用户进入导出流程时,一个VideoExportView被显示为模态,一旦导出操作完成,我们想把VideoExportFinishedView推送到该模态的导航栈中。

最初,这可能看起来非常棘手,因为(由于SwiftUI是一个声明式的UI框架)没有push方法,当我们想在导航栈中添加一个新视图时,我们可以调用该方法。事实上,在NavigationView中显示一个新视图的唯一内置方法是使用NavigationLink,它需要成为我们视图层次结构本身的一部分。

也就是说,这些NavigationLink实际上不一定是可见的——所以在这种情况下,实现我们目标的一个方法是在我们的视图中添加一个隐藏的导航链接,然后我们可以在视频导出操作完成后以编程方式触发该链接。如果我们也在我们的目标视图中隐藏系统提供的返回按钮,那么我们就可以完全锁定用户能够在这两个视图之间手动导航:

struct VideoExportView: View {    @ObservedObject var exporter: VideoExporter    @State private var didFinish = false    @Environment(\.presentationMode) private var presentationMode    var body: some View {        NavigationView {            VStack {                ...                Button("Export") {                    exporter.export {    didFinish = true}                }                .disabled(exporter.isExporting)                NavigationLink("Hidden finish link", isActive: $didFinish) {                    VideoExportFinishedView(doneAction: {                        presentationMode.wrappedValue.dismiss()                    })                    .navigationTitle("Export completed")                    .navigationBarBackButtonHidden(true)                }                .hidden()            }            .navigationTitle("Export this video")        }        .navigationViewStyle(.stack)    }}struct VideoExportFinishedView: View {    var doneAction: () -> Void    var body: some View {        VStack {            Label("Your video was exported", systemImage: "checkmark.circle")            ...            Button("Done", action: doneAction)        }    }}

我们在VideoExportFinishedView中注入一个doedAction闭包,而不是让它检索当前的presentationMode本身,是因为我们希望解耦整个模态流程,而不仅仅是那个特定的视图。要了解更多信息,请查看 "解耦SwiftUI模态或详细视图"。

使用这样一个隐藏的NavigationLink绝对可以被认为是一个有点 "黑 "的解决方案,但它的效果非常好,如果我们把一个导航链接看成是导航堆栈中两个视图之间的连接(而不仅仅是一个按钮),那么上述设置可以说是有意义的。

关于“SwiftUI怎么自定义导航”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网行业资讯频道,小编每天都会为大家更新不同的知识点。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯