温馨提示:本文最后更新于
2024-07-10 11:03:32
,某些文章具有时效性,若有错误或已失效,请在下方留言。本文以 badge 为例,实现自定义组件样式的具体步骤:
- 创建一个 BadgeStyle 协议为角标样式定义接口。
- 为角标样式创建一个环境变键。
- 在 badge 修饰符中使用这个自定义角标样式。
⻆标样式的协议和 ViewModi!er 协议⼗分相似:它要求⼀个 body ⽅法,来将已经存在的视图包装进⻆标⾥。
protocol BadgeStyle {
associatedtype Body: View
@ViewBuilder func makeBody(_ label: AnyView) -> Body
}
创建一个默认的角标样式
struct DefaultBadgeStyle: BadgeStyle {
var color: Color = .red
func makeBody(_ label: AnyView) -> some View {
label
.font(.caption)
.foregroundStyle(.white)
.padding(.horizontal, 5)
.padding(.vertical, 2)
.background(
Capsule(style: .continuous)
.fill(color)
)
}
}
当我们想要创建⼀个环境键时,不能简单地使⽤ DefaultBadgeStyle 来作为键所对应的值的类型。那样做的话我们就不能使⽤类型不同的⾃定义⻆标样式了。我们会⽤⼀个存在体 (existential) 来把具体的类型隐藏起来。本质上来说,它会把具体类型包装到⼀个盒⼦中 (这和 AnyView 将⼀个具体的视图类型包装到盒⼦中的做法类似)。下⾯是环境键和对应的属性:
enum BadgeStyleKey: EnvironmentKey {
static var defaultValue: any BadgeStyle = DefaultBadgeStyle()
}
extension EnvironmentValues {
var badgeStyle: any BadgeStyle {
get { self[BadgeStyleKey.self] }
set { self[BadgeStyleKey.self] = newValue }
}
}
为了使⽤⻆标样式,我们需要创建⼀个⾃定义的视图修饰器来从环境中读取样式,对标签进⾏转换,然后根据指定的对⻬确定⻆标的位置。
struct OverlayBadge<BadgeLabel: View>: ViewModifier {
var alignment: Alignment = .topTrailing
var label: BadgeLabel
@Environment(\.badgeStyle) private var badgeStyle
func body(content: Content) -> some View {
content
.overlay(alignment: alignment) {
AnyView(badgeStyle.makeBody(AnyView(label)))
.fixedSize()
.alignmentGuide(alignment.horizontal) {
$0[HorizontalAlignment.center]
}
.alignmentGuide(alignment.vertical) {
$0[VerticalAlignment.center]
}
}
}
}
这个 OverlayBadge 修饰器不需要公开。我们可以在 View 协议上编写⼀个简短的辅助⽅法,来让本节⼀开始时提出的语法成真:
extension View {
func badge<V: View>(alignment: Alignment = .topTrailing, @ViewBuilder _ content: () -> V) -> some View {
modifier(OverlayBadge(alignment: alignment, label: content()))
}
}
现在,我们可以这样使⽤ badge 修饰器了,它会⾃动使⽤环境中当前的⻆标样式:
Text(100, format: .number)
.badge { Text(100, format: .number) }

作为演示,我们来创建⼀个⾃定义带光泽的⻆标样式,并实际在环境中看看这个样式的使⽤⽅式:
struct FancyBadgeStyle: BadgeStyle {
var background: some View {
ZStack {
ContainerRelativeShape()
.fill(.red)
.overlay {
ContainerRelativeShape()
.fill(LinearGradient(colors: [.white, .clear], startPoint: .top, endPoint: .center))
}
ContainerRelativeShape()
.strokeBorder(Color.white, lineWidth: 2)
.shadow(radius: 2)
}
}
func makeBody(_ label: AnyView) -> some View {
label
.foregroundStyle(.white)
.font(.caption)
.padding(.horizontal, 7)
.padding(.vertical, 4)
.background(background)
.containerShape(Capsule(style: .continuous))
}
}
为了能更容易地在环境中设置⻆标样式,我们再⼀次向 View 添加⼀个扩展:
extension View {
func badgeStyle(_ style: any BadgeStyle) -> some View {
environment(\.badgeStyle, style)
}
}
现在,我们可以在视图层级的任何地⽅指定 .badgeStyle(FanceBadgeStyle()) 来使⽤我们的这个 fancy ⻆标样式,整棵⼦树在遇到⻆标时都将使⽤这个样式进⾏渲染。为了让语法更优雅⼀些,我们可以向 BadgeStyle 添加下⾯这样的扩展:
extension BadgeStyle where Self == FancyBadgeStyle {
static var fancy: FancyBadgeStyle {
FancyBadgeStyle()
}
}
现在我们可以把代码写成 .badgeStyle(.fancy) 了:
HStack {
Text("Inbox")
Text("Spam")
.badge {
Text(3000, format: .number)
}
}
.badgeStyle(.fancy)

© 版权声明
THE END
暂无评论内容