零是必须发明的吗?对于许多古代文明来说,数字仅指有形的事物。根本没必要数任何东西。最终,学者们认识到,“零”不仅仅是缺席——它是一种价值。它是占位符,是起点,是正面与负面之间的关键桥梁。
在 SwiftUI 中, 空状态就是零点。总会有没有数据可显示、没有修改器可应用或内容可渲染的情况。然而,软件仍然必须向用户——或编译器——传达这种“无”的状态。SwiftUI 为我们提供了三种不同的工具来处理空虚,各自有不同的用途:
EmptyView:用于视图层级(布局)EmptyModifier:用于类型系统(编译器)ContentUnavailableView:面向用户(体验)EmptyView 是 SwiftUI 中看似最简单的类型之一。表面上看,它像是你不想渲染时随便插入的占位符。但它的作用更深层:
EmptyView 是一个零尺寸、非渲染视图。它不绘制任何东西,不占用空间,没有布局影响,且对视图树的参与极少。如果 SwiftUI 是一种标准编程语言,EmptyView 就是虚无—— 意思是“这里有一个视图的概念,但它是空的。”
和其他隐藏修饰符的区别:
EmptyView 会完全移除视图,完全不占用空间,从布局路径中移除.hidden 或 .opacity(0) 并不会移除视图,它更接近设置可见性。视图仍然保持在布局计算中,保留其帧大小,并影响邻居的位置。EmptyView 的主要使用场景包括:
当你需要在某些条件下不显示任何内容时:
var body: some View {
VStack {
if showContent {
Text("有内容")
} else {
EmptyView() // 什么都不显示
}
}
}
在使用 @ViewBuilder 时,不能返回“零”。它必须返回一个视图,所有分支需要返回相同的视图类型。
在复杂的通用上下文或手动 ViewBuilder 实现中,EmptyView 是你需要退出渲染时的正确返回类型。
@ViewBuilder
var conditionalView: some View {
if isLoggedIn {
ProfileView()
} else {
EmptyView() // 未登录时不显示任何内容
}
}
struct CustomView<Content: View>: View {
let content: Content
var body: some View {
VStack {
Text("标题")
content // 可能是实际内容,也可能是 EmptyView
}
}
}
// 使用
CustomView(content: EmptyView()) // 只显示标题
NavigationLink(destination: EmptyView()) {
Text("暂未开放")
}
.disabled(true)
struct CardView<Header: View, Footer: View>: View {
let header: Header
let footer: Footer
var body: some View {
VStack {
header
Text("主要内容")
footer
}
}
}
// 只需要显示内容,不需要 header 和 footer
CardView(
header: EmptyView(),
footer: EmptyView()
)
如果 EmptyView 代表“无视图”,那么 EmptyModifier 代表 “无变换”。
官方文档指出,该修饰符在“编译时”非常有用。这是一个关键的区别。EmptyModifier 通常不用于运行时逻辑(比如开关不透明度);它是修饰符世界的身份元素。它的存在是为了满足类型检查器的需求,当需要提供修饰符,但你实际上并不想做任何操作时使用。
EmptyModifier 最强大的用例是将其与条件编译结合。
想象你想要一个特定的调试覆盖层:一个红色边框,但你希望那段代码完全从发布版本中剔除。你不想把每个调用点都包裹在 #if Debug 中。相反,你可以定义一个根据构建配置变化的修正值:
#if DEBUG
struct DebugBorder: ViewModifier {
func body(content: Content) -> some View {
content.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(.red, lineWidth: 1)
)
}
}
#else
// 否则在 Release 构建中, 这个修饰结构会变成 "Identity" modifier
typealias DebugBorder = EmptyModifier
#endif
现在,您的代码可以保持干净且一致:
Text("Hello World")
.modifier(DebugBorder())
你可能会想写这样的代码:view.modifier(isEnabled ? MyEffect() : EmptyModifier())
因为在编译过程中,通常会生成 _ConditionalContent 封装器。对于简单的运行时切换,通常将逻辑放入自定义修改器或使用标准视图扩展更为简洁。
EmptyModifier 在需要为通用系统提供默认值时发挥出色。如果你构建了一个可重用组件,并且接受泛型的修改器 ViewModifier,就可以将默认设置为 EmptyModifier 。
struct MyContainer<VM: ViewModifier>: View {
var modifier: VM = EmptyModifier() // Default is "do nothing"
// ...
}
EmptyModifier 是你 UI 中隐形的绑定,在发布版本中看不到,但在调校时至关重要。用它大胆调试,干净部署。
iOS 17 中的新功能。
EmptyView 解决了渲染不存在的技术问题,但它在用户体验问题上却没有明确的作用。空白的屏幕需要意图、清晰度,且通常带有明显的行动邀请。
当您的应用程序没有任何内容可显示时,SwiftUI 的 ContentUnavailableView 可以显示标准的用户界面。它非常适合您的应用依赖尚未提供的用户信息的情况,例如当您的用户尚未创建任何数据时,或者他们正在搜索某些内容但没有结果时。
举个例子,如果您正在制作一个应用程序,让用户写下他们想要记住的 Swift 代码片段,那么默认情况下它可能会以没有代码片段的方式开始。因此,您可以像这样使用:
// 这将显示 SF Symbols 中的一个大 Swift 图标,以及下面的标题文本“无片段”
ContentUnavailableView("No snippets", systemImage: "swift")
// 这将显示 SF Symbols 中的一个大 signature 图标,以及下面的标题文本“无片段”
ContentUnavailableView("No snippets", systemImage: "signature.th")
// 还可以在下面添加额外的描述文本行,指定为 Text 视图,以便您可以添加额外的样式,例如自定义字体或自定义颜色:
ContentUnavailableView(
"No snippets",
systemImage: "swift",
description: Text("You don't have any saved snippets yet.")
)
// 如果想要完全控制,可以提供标题和描述的单独视图,以及一些要显示的按钮以帮助用户开始使用:
ContentUnavailableView {
Label("No snippets", systemImage: "swift")
} description: {
Text("You don't have any saved snippets yet.")
} actions: {
Button("Create Snippet") {
// create a snippet
}
.buttonStyle(.borderedProminent)
}
SwiftUI 还内置了一些 ContentUnavailableView 的实例,方便使用。
// 例如:显示失败的搜索结果屏幕
ContentUnavailableView.search
// 可以自定义其内容
ContentUnavailableView.search(text: "Life, the Universe, and Everything")