Skip to content

第45天 项目 9 第三部分

今天,我们将通过了解可用的自定义选项来完善你对SwiftUI导航的知识,这些选项包括:自定义导航栏外观、将工具栏按钮放置在精确位置,以及允许用户编辑导航标题。

在这三者中,中间那项(将工具栏按钮放置在精确位置)最为重要,因为它有两种形式:我们既可以强制按钮处于精确位置,也可以使用“语义化”位置——这种位置允许SwiftUI根据当前平台将按钮放置在合理的位置,并且还会根据需要应用额外的样式。

我很喜欢史蒂夫·乔布斯的一句名言:“有些人认为设计只是外观问题。但如果你深入研究就会发现,设计实际上关乎功能运作。”这正是SwiftUI这类声明式布局系统的强大之处:通过向系统提供额外信息、描述某个元素的作用,SwiftUI会自动处理好一切,确保其以正确的方式运作——这比我们自己手动处理所有事情要容易得多。

今天你需要学习三个主题,从中你将了解自定义导航栏外观、隐藏返回按钮、工具栏位置等相关知识。

  • 自定义导航栏外观
  • 将工具栏按钮放置在精确位置
  • 让导航标题可编辑

自定义导航栏外观

作者:Paul Hudson 2023年11月2日

iOS的导航栏有其特定的默认外观,但我们对其样式拥有一定的控制权限。

首先,你已经了解了如何使用大号或行内(inline)导航标题样式,这两种样式分别会在顶部显示大号或小号文本。例如,以下代码会在顶部显示小号标题:

swift
NavigationStack {
    List(0..<100) { i in
        Text("Row \(i)")
    }
    .navigationTitle("Title goes here")
    .navigationBarTitleDisplayMode(.inline)
}

默认情况下,你会看到顶部的导航栏是不可见的,但只要你向上滚动一点,导航栏就会出现实心灰色背景,这样标题就能与列表内容清晰地区分开来。

SwiftUI允许我们对导航栏进行一些自定义:我们可以指定一种替代颜色作为导航栏的背景色。需要注意的是,这种背景色只有在列表滚动到导航栏下方时才会显示,因此一开始你是看不到它的。

要尝试自定义导航栏背景色,请在navigationBarTitleDisplayMode()下方添加以下代码:

swift
.toolbarBackground(.blue)

当你运行应用并向上滚动一点时,会看到导航栏变成实心蓝色。不过你可能还会发现标题难以阅读——因为在浅色模式下,标题文本会是黑色的。

你可以通过在之前添加的修饰符下方再添加一个修饰符来解决这个问题,强制导航栏始终使用深色模式,这样标题文本就会变成白色:

swift
.toolbarColorScheme(.dark)

提示: 之后你还会接触到其他类型的工具栏。上述两个修饰符会影响“所有”工具栏,但如果只想修改导航栏,你应该在这两个修饰符中都添加第二个参数for: .navigationBar

自定义导航栏还有最后一种方式:你可以隐藏导航栏,既可以始终隐藏,也可以根据应用的当前状态来隐藏。

要隐藏导航栏,请添加toolbar()修饰符并将其设置为.hidden,既可以隐藏所有工具栏,也可以只隐藏导航栏:

swift
.toolbar(.hidden, for: .navigationBar)

隐藏工具栏并不会阻止你导航到新视图,但它“可能”会导致可滚动视图延伸到系统信息(如时钟)下方——使用时请务必注意!

将工具栏按钮放置在精确位置

作者:Paul Hudson 2023年11月2日

如果你在NavigationStack工具栏中放置按钮,SwiftUI会根据代码运行的平台自动放置这些按钮。我们此处正在开发iOS应用,因此在英语等从左到右阅读的语言环境中,按钮会被放置在导航栏的右侧。

如果你想自定义按钮位置,可以使用ToolbarItem。它会包裹你的工具栏按钮,让你能够从多个选项中选择一个,将按钮放置在精确的位置。

例如,我们可以像这样将按钮放置在左侧:

swift
NavigationStack {
    Text("Hello, world!")
    .toolbar {
        ToolbarItem(placement: .topBarLeading) {
            Button("Tap Me") {
                // 按钮动作代码
            }
        }
    }
}

虽然这种方式效果不错,但通常情况下,使用“语义化”选项会更好——这种位置具有特定的含义,而不仅仅依赖于物理位置。语义化位置包括:

  • .confirmationAction:用于需要用户确认某项操作的场景,例如同意服务条款。
  • .destructiveAction:用于用户需要做出“删除当前操作对象”这类选择的场景,例如确认删除自己创建的某些数据。
  • .cancellationAction:用于用户需要撤销已做更改的场景,例如放弃已进行的修改。
  • .navigation:用于实现用户在数据间切换的按钮,例如网页浏览器中的前进和后退按钮。

这些语义化位置有两个重要优势。首先,由于iOS了解这些按钮的用途,它可以添加额外的样式——例如,确认按钮会以粗体显示。其次,这些位置会在其他平台上自动适配,因此你的按钮在iOS、macOS、watchOS等平台上都会显示在正确的位置。

提示: 如果你需要让用户在“保存更改”和“放弃更改”之间做出选择,可能需要添加navigationBarBackButtonHidden()修饰符,这样用户就无法通过点击“返回”按钮退出,必须先做出选择。

如果你想让多个按钮使用相同的位置,可以像这样重复使用ToolbarItem

swift
.toolbar {
    ToolbarItem(placement: .topBarLeading) {
        Button("Tap Me") {
            // 按钮动作代码
        }
    }

    ToolbarItem(placement: .topBarLeading) {
        Button("Or Tap Me") {
            // 按钮动作代码
        }
    }
}

也可以使用ToolbarItemGroup,代码如下:

swift
.toolbar {
    ToolbarItemGroup(placement: .topBarLeading) {
        Button("Tap Me") {
            // 按钮动作代码
        }

        Button("Tap Me 2") {
            // 按钮动作代码
        }
    }
}

两种方式产生的效果是相同的。

让导航标题可编辑

作者:Paul Hudson 2023年11月2日

基础的navigationTitle()修饰符可以让我们在导航栏中显示一段文本,代码如下:

swift
struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, world!")
                .navigationTitle("SwiftUI")
        }
    }
}

但如果你使用的是.inline标题显示模式,还可以向navigationTitle()传递一个“绑定”(binding)。这样,标题会照常显示,但会增加一个重要功能:iOS会在标题旁边显示一个小箭头,点击该箭头会弹出“重命名”(Rename)按钮,用于修改标题。

对应的代码如下:

swift
struct ContentView: View {
    @State private var title = "SwiftUI"

    var body: some View {
        NavigationStack {
            Text("Hello, world!")
                .navigationTitle($title)
                .navigationBarTitleDisplayMode(.inline)
        }
    }
}

这种功能非常适合“标题反映用户输入内容”的场景,因为它意味着你无需在布局中额外添加文本框(textfield)。

本站使用 VitePress 制作