SwiftUI 自定义组件样式

32 阅读1分钟

我们给 Badge 添加自定义样式, 效果图

image.png

1.定义协议

protocol BadgeStyle {
    associatedtype Body: View
    @ViewBuilder func makeBody(_ label: AnyView) -> Body
}

2.默认样式

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)
            )
    }
}

3.带阴影和边框的样式

struct FancyBadgeStyle: BadgeStyle {
    var background: some View {
        ZStack {
            ContainerRelativeShape()
                .fill(Color.red)
                .overlay {
                    ContainerRelativeShape()
                        .fill(LinearGradient(colors: [.white, .clear], startPoint: .top, endPoint: .center))
                }

            ContainerRelativeShape()
                .stroke(.white, lineWidth: 2.0)
                .shadow(radius: 2.0)
        }
    }

    func makeBody(_ label: AnyView) -> some View {
       label
            .font(.caption)
            .foregroundStyle(.white)
            .padding(.horizontal, 5)
            .padding(.vertical, 2)
            .background(background)
            .containerShape(Capsule(style: .continuous))
    }
}

4.通过自定义环境变量去修改 badgeStyle

enum BadgeStyleKey: EnvironmentKey {
    static var defaultValue: any BadgeStyle = DefaultBadgeStyle()
}

extension EnvironmentValues {
    var badgeStyle: any BadgeStyle {
        get {
            self[BadgeStyleKey.self]
        }
        set {
            self[BadgeStyleKey.self] = newValue
        }
    }
}

5.用 modifier 使用对齐方式和样式

struct OverlayBadge<BadgeLable: View>: ViewModifier {
    var alignment: Alignment = .topTrailing
    var label: BadgeLable

    @Environment(\.badgeStyle) var badgeStyle

    func body(content: Content) -> some View {
        content
            .overlay(alignment: alignment) {
               AnyView(  badgeStyle.makeBody(AnyView(label)))
                    .alignmentGuide(alignment.horizontal, computeValue: { dimension in
                        dimension[HorizontalAlignment.center]
                    })
                    .alignmentGuide(alignment.vertical, computeValue: { dimension in
                        dimension[VerticalAlignment.center]
                    })
            }
    }
}

6. 方便 View 使用, 添加扩展

extension View {
    func badgeStyle(_ style: any BadgeStyle) -> some View {
        environment(\.badgeStyle, style)
    }

    func badge<V: View>(alignment: Alignment = .topTrailing, @ViewBuilder _ content: () -> V) -> some View {
        modifier(OverlayBadge(alignment: alignment, label: content()))
    }
}

7. 方便设置 style

extension BadgeStyle where Self == FancyBadgeStyle {
    static var fancy: FancyBadgeStyle { FancyBadgeStyle()
    }
}

8.使用

ZStack {
    Color.teal
    VStack {
        Text("SwiftUI")
            .badge {
                Text(100, format: .number)
            }
    }
    .badgeStyle(.fancy)
}