SwiftUI:同时渲染和填充视图的教程

503 阅读4分钟

SwiftUI的整个API的一个真正强大的方面是,它使我们能够以渲染视图和UI控件的同样方式来绘制诸如形状、梯度和颜色等东西。例如,如果我们想渲染一个圆角矩形的按钮,那么我们可以通过指定一个RoundedRectangle 形状作为我们视图的背景来实现--像这样:

struct CreateAccountButton: View {
    var action: () -> Void

    var body: some View {
        Button("Create account", action: action)
            .padding()
            .background(RoundedRectangle(cornerRadius: 20))
    }
}

默认情况下,上述背景形状将使用当前的foregroundColor ,但如果我们想用一个特定的颜色来填充它,那么我们可以使用fill 修改器来实现:

struct CreateAccountButton: View {
    var action: () -> Void

    var body: some View {
        Button("Create account", action: action)
            .padding()
            .foregroundColor(.white)
            .background(RoundedRectangle(cornerRadius: 20).fill(Color.blue))
    }
}

请注意,我们现在也给我们的按钮应用了白色的前景色。这是因为按钮标签默认是蓝色的,所以如果我们让它保持这样,它现在就会变得不可见。

到目前为止,我们实际上可以通过使用cornerRadius 修改器来直接在我们的视图上应用圆角,而不是使用形状来实现完全相同的结果。然而,使用形状可能会给我们带来一些好处,这取决于我们要做什么样的样式设计。例如,假设我们还想给我们的按钮添加一个边框,并且我们想让这个边框跟随我们按钮的圆角。关于如何做到这一点,一个初步的想法可能是将stroke 修改器也应用到我们的RoundedRectangle - 像这样:

struct CreateAccountButton: View {
    var action: () -> Void

    var body: some View {
        Button("Create account", action: action)
            .padding()
            .foregroundColor(.white)
            .background(RoundedRectangle(cornerRadius: 20)
                .fill(Color.blue)
                .stroke(Color.primary, lineWidth: 2)
            )
    }
}

不幸的是,上面的代码不能编译,因为fillstroke 只在Shape 协议上可用,而这两个修饰符都使用标准的some View 返回类型。值得庆幸的是,还有另一种方法,那就是让我们的填充矩形成为另一个描边矩形的背景,然后我们把它作为按钮的背景:

struct CreateAccountButton: View {
    var action: () -> Void

    var body: some View {
        Button("Create account", action: action)
            .padding()
            .foregroundColor(.white)
            .background(
                RoundedRectangle(cornerRadius: 20)
                    .stroke(Color.primary, lineWidth: 2)
                    .background(
                        RoundedRectangle(cornerRadius: 20)
                            .fill(Color.blue)
                    )
            )
    }
}

最初,上述解决方案可能看起来有点奇怪,但我们必须记住,在SwiftUI的世界里,视图只是对我们想要渲染的东西的描述,任何视图都可以被用作任何其他视图的背景。所以,使用两个独立的矩形来描边和填充,有点像SwiftUI版本的执行两个渲染命令。

也就是说,像我们上面做的那样堆叠多个形状,往往会导致代码有点难以阅读。解决这个问题的一个方法是将我们的背景声明移到它自己的专用属性中--像这样:

struct CreateAccountButton: View {
    var action: () -> Void

    var body: some View {
        Button("Create account", action: action)
            .padding()
            .foregroundColor(.white)
            .background(background)
    }

    private var background: some View {
        let rectangle = RoundedRectangle(cornerRadius: 20)

        return rectangle
            .stroke(Color.primary, lineWidth: 2)
            .background(rectangle.fill(Color.blue))
    }
}

如果我们想创建一个更可重复使用的解决方案,那么另一个选择是创建一个自定义的修改器,让我们通过描边和填充来设计任何Shape

extension Shape {
    func style<S: ShapeStyle, F: ShapeStyle>(
        withStroke strokeContent: S,
        lineWidth: CGFloat = 1,
        fill fillContent: F
    ) -> some View {
        self.stroke(strokeContent, lineWidth: lineWidth)
            .background(fill(fillContent))
    }
}

struct CreateAccountButton: View {
    var action: () -> Void

    var body: some View {
        Button("Create account", action: action)
            .padding()
            .foregroundColor(.white)
            .background(RoundedRectangle(cornerRadius: 20).style(
                withStroke: Color.primary,
                lineWidth: 2,
                fill: Color.blue
            ))
    }
}

模仿 SwiftUI 内置的形状造型 API 的一大好处是,通过使用受限于ShapeStyle 协议的通用类型来实现我们的填充和描边内容,这样做可以让我们使用更多的颜色来造型我们的形状。例如,如果我们想用梯度来填充我们的按钮,那么我们现在可以很容易地这样做:

struct CreateAccountButton: View {
    var action: () -> Void

    var body: some View {
        Button("Create account", action: action)
            .padding()
            .foregroundColor(.white)
            .background(RoundedRectangle(cornerRadius: 20).style(
                withStroke: Color.primary,
                lineWidth: 2,
                fill: LinearGradient(
                    gradient: Gradient(colors: [.blue, .black]),
                    startPoint: .top,
                    endPoint: .bottom
                )
            ))
    }
}

虽然可以肯定地说,SwiftUI的Shape API应该使我们能够同时描边和填充一个给定的形状,而不需要任何自定义代码,但我们可以组成多个形状来建立我们自己的那种功能的事实绝对是一个非常强大的东西。

谢谢你的阅读!