Label
是SwiftUI2.0新增加的组件。Label
将文本和图像组合在一个视图中,它还根据上下文(例如,如果它放在工具栏上)和动态类型进行调整。

在本文中,让我们在基本知识之外探索这个视图。
Initializers
Label
带有六个初始化方法:
- 前四种格式提供了所有可能的文本组合,如
StringProtocol
或LocalizedStringKey
,以及图片资源或SF Symbols资源。 - 最灵活的初始化方法有两个泛型视图,没有附加字符串
- 最后一个初始化方法接受一个
LabelStyleConfiguration
参数
我们将在本文中讨论所有的初始化方法。
Label styles
除非我们在一个特殊的环境中(例如导航栏),默认情况下Label
的title和image都是显示的。
如果我们只想显示两个组件中的一个(要么只显示图片,要么只显示标题),或者用另一种方式改变Label
外观,我们可以通过labelStyle(_:)
视图修饰符来实现:这个修饰符接受一个LabelStyle
实例。
LabelStyle
告诉SwiftUI我们希望Label
如何绘制在屏幕上,默认情况下我们有三个选项:
IconOnlyLabelStyle()
TitleOnlyLabelStyle()
DefaultLabelStyle()
这些内插样式是相互排斥的:如果多个应用于同一个Label
标签,则只有最接近该Label
标签的一个才生效。
// 只有 `IconOnlyLabelStyle()` 生效:
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(IconOnlyLabelStyle())
.labelStyle(TitleOnlyLabelStyle())
.labelStyle(DefaultLabelStyle())

因为LabelStyle
是一个协议,我们可以定义自己的样式:
public protocol LabelStyle {
associatedtype Body: View
func makeBody(configuration: Self.Configuration) -> Self.Body
typealias Configuration = LabelStyleConfiguration
}
类似于ViewModifier
,LabelStyle
需要实现makeBody(configuration:)
方法,这使我们有机会定义自己的标签样式。
makeBody(configuration:)
接受一个LabelStyleConfiguration
实例,它与上面列出的最后一个Label
初始化方法接受的参数相同。
我们不能自己定义一个全新的配置,这是SwiftUI保留的,但是我们可以访问当前Label
标签的image图片(命名为icon
)和title标题:
public struct LabelStyleConfiguration {
/// A type-erased title view of a label.
public var title: LabelStyleConfiguration.Title { get }
/// A type-erased icon view of a label.
public var icon: LabelStyleConfiguration.Icon { get }
}
多亏了这个配置,我们的LabelStyle
被应用于当前样式之上。
例如,这里我们创建了一个LabelStyle
,为整个Label
添加阴影:
struct ShadowLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
Label(configuration)
.shadow(color: Color.gray.opacity(0.9), radius: 4, x: 0, y: 5)
}
}
这是我们唯一可以使用
Label
初始化的地方
由于ShadowLabelStyle
是当前LabelStyle
之上的一个样式,它将应用于当前的Label
标签。
因此,例如,如果我们把它和IconOnlyLabelStyle
一起使用,最终的结果将是一个只有图标和我们的阴影的标签:
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(ShadowLabelStyle())
.labelStyle(IconOnlyLabelStyle())

Label样式的橡皮擦
.labelStyles
声明顺序很重要,上面我们已经看到这三种回退样式是如何相互排斥的,这在它们自己的定义中真正意味着什么,它们不使用传递到makeBody(configuration:)
中的配置,而是创建一个新的配置。
换句话说,IconOnlyLabelStyle
, TitleOnlyLabelStyle
,和 DefaultLabelStyle
充当样式擦除器:一旦应用,以前的任何样式都不会继续使用。
看看我们的ShadowLabelStyle
的例子:
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(ShadowLabelStyle())
.labelStyle(IconOnlyLabelStyle())

输出Label
标签的图标是否带有阴影。
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(IconOnlyLabelStyle()) // <- the label style order has been swapped
.labelStyle(ShadowLabelStyle())

输出Label
标签的图标没有阴影。
由于我们使用的是一个样式擦除器,SwiftUI甚至不会首先使用我们的样式,这可以通过在ShadowLabelStyle
的makeBody(configuration:)
实现中添加断点来验证:SwiftUI根本不会调用我们的方法。
自定义样式橡皮擦
如上所述,只有SwiftUI可以创建新的配置,然而,有一个简单的技巧可以使任何自定义样式也成为样式橡皮擦:在makeBody(configuration:)
实现中应用一个系统样式的擦除器。
struct ShadowEraseLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
Label(configuration)
.shadow(color: Color.gray.opacity(0.9), radius: 4, x: 0, y: 5)
.labelStyle(DefaultLabelStyle()) // <- ✨
}
}
在这个例子中,我们强制我们的标签Label
同时显示文本和图标,以及我们的阴影,之前应用的任何其他样式都被忽略:
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(ShadowEraseLabelStyle())
.labelStyle(TitleOnlyLabelStyle())
.labelStyle(IconOnlyLabelStyle())

同样,由于我们的样式现在扮演了一个样式橡皮擦的角色,所以它不会被应用到当前样式之上,而是从一个干净的Label
开始。
LabelStyleConfiguration的icon和title的样式
我们可能还试图通过将两个配置视图传递给一个新Label
标签来删除样式:
struct ShadowLabelTryStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
Label(
title: { configuration.icon },
icon: { configuration.title }
)
.shadow(color: Color.gray.opacity(0.9), radius: 4, x: 0, y: 5)
}
}
使用我们的视图Body:
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(ShadowLabelTryStyle())
.labelStyle(IconOnlyLabelStyle())

有趣的是,这是行不通的,结果是configuration.icon
和configuration.title
延续了整个配置风格。
在上面的例子中,title
视图将被隐藏,尽管我们创建了一个新的Label
而没有直接传递配置本身。
为了进一步证明这一点,让我们定义一个新的样式,它所做的只是将标签Label
的标题title
与图标icon
交换:
struct SwapLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
Label(
title: { configuration.icon },
icon: { configuration.title }
)
}
}
新标签以原始标题作为其图标,原始图标作为其标题。
现在想象一下这个视图主体:
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(SwapLabelStyle())
.labelStyle(IconOnlyLabelStyle())
我们期望的最终结果是什么?
我们首先应用IconOnlyLabelStyle
,因此标题title
是隐藏的,而图像"moon.circle.fill"
显示。
我们在SwapLabelStyle
中交换它们并没有达到效果,用户看到的还是原来的图标。

自定义icon和title的样式
为了完整起见,我必须指出LabelStyle
的makeBody(configuration:)
只需要返回some View
,而不是Label
(或带有几个修饰符的Label
标签)。
这意味着我们真的可以用它做任何我们想做的事情,把我们的标签变成HStack
怎么样?
struct HStackLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.icon
Spacer()
configuration.title
}
}
}
这里我们用它作为其他Label
:
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(HStackLabelStyle())

虽然这可以工作,但这是一个绝佳的机会来指出.labelStyle
修饰符只有在应用于标签时才能工作,
由于HStackLabelStyle
不返回Label
标签,任何进一步应用的标签样式(包括擦除标签)都将被忽略。
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(HStackLabelStyle())
.labelStyle(ShadowLabelStyle())
.labelStyle(IconOnlyLabelStyle())

在HStackLabelStyle
之前应用它们就可以了:
Label("Title", systemImage: "moon.circle.fill")
.labelStyle(ShadowLabelStyle())
.labelStyle(IconOnlyLabelStyle())
.labelStyle(HStackLabelStyle())

然而,如果我们这样做,我们可能一开始就不应该使用Label。
可访问的Label
虽然LabelStyle
主要用于添加新的样式,但我们也可以使用它使我们的Label
标签更容易访问。
例如,当系统内容大小属于可访问性大小时,我们可能希望去掉任何标签效果并隐藏图标,只留下用户继续执行任务所需的最低限度例如,当系统内容大小属于可访问性大小时,我们可能希望去掉任何标签效果并隐藏图标,只留下用户继续执行任务所需的最低限度。
这是一个很好的例子,LabelStyle
和我们的条件修饰符扩展:
struct AccessibleLabelStyle: LabelStyle {
@Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory
func makeBody(configuration: Configuration) -> some View {
Label(configuration)
.if(sizeCategory.isAccessibilityCategory) { $0.labelStyle(TitleOnlyLabelStyle()) }
}
}

Label Extensions
尽管标签样式是自定义和标准化标签的主要方式,但有时我们可以通过创建一个标签扩展来代替。
例如,在今年的《SF Symbols 2》更新中,我们已经获得了其中一些颜色变体:
不幸的是,开箱即用,标签默认显示单色变体,没有办法改变它。
这可以通过标签扩展来解决:
extension Label where Title == Text, Icon == Image {
init(_ title: LocalizedStringKey, colorfulSystemImage systemImage: String) {
self.init {
Text(title)
} icon: {
Image(systemName: systemImage)
.renderingMode(.original)
}
}
}
我们可以用colorfulSystemImage
来替换systemImage
参数名,例如:
Label("Title", colorfulSystemImage: "moon.circle.fill")
.labelStyle(ShadowLabelStyle())

总结
Label是SwiftUI的另一个例子,表面上看起来非常简单,但实际上隐藏了大量的复杂性和灵活性。
网友评论