形状
我们使用过的所有视图都是容器或设计用于在屏幕上展示预定义内容,但SwiftUI还内置了图形视图可创建自定义控件或用作闭包。这些视图与之前介绍过的类似,可使用大部分修饰符,但经过特别设计在屏幕上画自定义图形。
通用形状
SwiftUI允许我们创建预定义或自定义开关。以下是用于创建标准开关的视图。
- Rectangle() :这个初始化方法创建一个
Rectangle视图。矩形的尺寸由视图的框架决定。 - RoundedRectangle (cornerRadius : CGFloat, style : RoundedCornerStyle):此初始化方法创建一个
RoundedRectangle视图。cornerRadius参数指定圆角的半径,style参数是RoundedCornerStyle类型的枚举,指定所用曲率的类型。值有circular和continuous。该视图还包包通过CGSize的值定义半径的初始化方法:RoundedRectangle(cornerSize: CGSize, style: RoundedCornerStyle)。 - Circle() :些初始化方法创建一个
Circle视图。圆的直径由视图的边框决定。 - Ellipse() :此初始化方法创建一个
Ellipse视图。椭圆的大小由视图框架的宽和高决定。 - Capsule (style : RoundedCornerStyle):此初始化方法创建一个
Capsule视图。style参数是指定应用于边角曲率类型的枚举。值有circular和continuous。
和其它视图一样,图形视图在未指定大小时采用其容器的大小,但可以通过frame()修饰符声明具体的大小。以下示例展示了所有的标准形状。我们将视图放到了水平的ScrollView视图中,以例可以滚动列表。
示例11-1:绘制标准图形
scss
struct ContentView: View {
var body: some View {
VStack {
ScrollView(.horizontal, showsIndicators: true) {
HStack {
Rectangle()
.frame(width: 100, height: 100)
RoundedRectangle(cornerRadius: 25, style: .continuous)
.frame(width: 100, height: 100)
Circle()
.frame(width: 100, height: 100)
Ellipse()
.frame(width: 100, height: 50)
Capsule()
.frame(width: 100, height: 50)
}.padding()
}
Spacer()
}
}
}

图11-1:标准形状
✍️跟我一起做:创建一个多平台项目。使用示例11-1 中的代码更新ContentView视图。我们在屏幕上看不全所有形状,将视图向左滚动。使用这个项目测试本章后续的示例。
默认,视图通过依赖于外观模式的颜色进行渲染(浅色模式为黑色,深色模式为白色),但我们可以通过如下的修饰符对形状进行填充和边框设置。
- fill(View):此修饰符通过参数指定的视图填充形状。参数是一个表示颜色、渐变或图片的视图。
- stroke (View, lineWidth : CGFloat):此修饰符定义形状的边框。第一个参数是表示颜色、渐变或图片的视图,
lineWidth参数定义边框的宽度。 - stroke (View, style : StrokeStyle):此修饰符定义形状的边框。第一个参数是表示颜色、渐变或图片的视图,
style参数为StrokeStyle类型的结构体,定义边框的宽度、末端、连接、转角限量、虚线和虚线相位。 - strokeBorder (View, lineWidth : CGFloat):这一修饰符定义开关的内边框。第一个参数是表示颜色、渐变或图片的视图,
lineWidth参数定义边框的宽度。 - strokeBorder (View, style : StrokeStyle):此修饰符定义形状的内边框。第一个参数是表示颜色、渐变或图片的视图,
style参数为StrokeStyle类型的结构体,定义边框的宽度、末端、连接、转角限量、虚线和虚线相位。
通过这些修饰符我们可以改变的有两类:填充和边框。填充由fill()修饰符以及表示内容的视图定义,如Color视图。
示例11-2:使用颜色填充形状
css
struct ContentView: View {
var body: some View {
RoundedRectangle(cornerRadius: 25)
.fill(Color.red)
.frame(width: 100, height: 100)
}
}
注意fill()修饰符由RoundedRectangle视图实现,但frame()返回另一个视图。因此 任意定义形状的修饰符,比如fill(),必须在其它修饰符之前,如frame()。本例中,我们使用这些修饰符创建一个带圆角的红色方框。

图11-2:矩形
添加边框的流程相似,但两种类型的修饰符结果稍有不同。stroke()修饰符向内及向外扩展边框,而strokeBorder()修饰符创建一个内边框。
示例11-3:定义边框
css
struct ContentView: View {
var body: some View {
HStack {
RoundedRectangle(cornerRadius: 25)
.stroke(Color.red, lineWidth: 20)
.frame(width: 100, height: 100)
.padding()
RoundedRectangle(cornerRadius: 25)
.strokeBorder(Color.red, lineWidth: 20)
.frame(width: 100, height: 100)
.padding()
}
}
}
以上视图中包含两个边框为20点的RoundedRectangle视图,但因为使用的是不同的修饰符,边框不同。第一个方框的边框有一差别在图形外绘制,而另一半在视图边框内绘制,但第二个方框的边框在边框内,如下图所示。

图11-3:带不同边框的矩形
这两个修饰符也可以接收StrokeStyle结构体对边框进行调优。该结构体提供了如下的初始化方法。
- StrokeStyle (lineWidth : CGFloat, lineCap : CGLineCap, lineJoin : CGLineJoin, miterLimit : CGFloat, dash : [CGFloat], dashPhase : CGFloat):此初始化方法创建一个配置笔触的
StrokeStyle结构体。lineWidth参数指定宽度。lineCap参数指定线末端的样式。它是一个枚举,值有butt(平直方形)、round(圆形)和square(方形)。lineJoin参数设置两个连接线结点的样式。它是一个枚举,值有miter(锐利末端)、round(圆形末端)和bevel(方形末端)。miterLimit参数在lineJoin参数为miter时指定线扩展多长。dash参数指定虚线笔触每段的长度。dashPhase参数指定虚线的起点。
注:butt和square都是方形末端,但前者没有扩展段,后者有扩展段。
下例创建了一个RoundedRectangle视图,边界以15点宽和圆形末端配置虚线。
示例11-4:定义自定义边框
less
struct ContentView: View {
let lineStyle = StrokeStyle(lineWidth: 15, lineCap: .round, lineJoin: .round, miterLimit: 0, dash: [20], dashPhase: 0)
var body: some View {
RoundedRectangle(cornerRadius: 25)
.stroke(Color.red, style: lineStyle)
.frame(width: 100, height: 100)
}
}

图11-4:带自定义笔触的矩形
形状也是视图,因此可以结合SwiftUI的视图和控件使用。比如下例中将Capsule形状赋值为按钮的背景。
示例11-5:结合形状和其它视图
css
struct ContentView: View {
@State private var setActive: Bool = true
var body: some View {
VStack {
Button(action: {
setActive.toggle()
}, label: {
Text(setActive ? "Active" : "Inactive")
.font(.title)
.foregroundColor(Color.white)
.padding(.horizontal, 30)
.padding(.vertical, 10)
})
.background(
Capsule()
.fill(setActive ? Color.green : Color.red)
)
Spacer()
}.padding()
}
}
事实上,background()修饰符有一个版本专门用于形状。
- background (Color, in: Shape):这一修饰符将形状赋值给视图的背景。第一个参数指定颜色,
in参数指定形状。
使用该修饰符,我们可以通过一行代码声明上例中的按钮。
示例11-6:将形状赋值为按钮的背景
less
.background(setActive ? Color.green : Color.red, in: Capsule())
该按钮切换@State属性的值。若值为true,展示标签Active并将绿色胶囊赋值为按钮的背景,否则显示标签Inactive并将胶囊变为红色。

图11-5:图形按钮
渐变
形状的填充和边框也可以使用颜色渐变定义。SwiftUI有4个显示渐变的结构体:LinearGradient、RadialGradient、AngularGradient和EllipticalGradient。这些结构体遵循ShapeStyle协议,协议定义了如下方法创建自定义实例。
- linearGradient (Gradient, startPoint : UnitPoint, endPoint : UnitPoint):此方法返回一个线性渐变。
gradient参数是供使用的颜色渐变,startPoint和endPoint参数指定渐变在形状中开始和结束的点。 - radialGradient (Gradient, center : UnitPoint, startRadius : CGFloat, endRadius : CGFloat):此方法返回一个环形渐变。
gradient参数是供使用的颜色渐变。center参数指定圆心的位置,startRadius和endRadius参数指定渐变的起始和结束位置。 - ellipticalGradient (Gradient, center : UnitPoint, startRadiusFraction : CGFloat, endRadiusFraction : CGFloat):此方法返回一个椭圆形状的径向渐变。
gradient参数是供使用的颜色渐变。center参数指定椭圆的圆心,startRadiusFraction和endRadiusFraction指定椭圆的半径。 - angularGradient (Gradient, center : UnitPoint, startAngle : Angle, endAngle : Angle):此方法返回一个角度渐变。
gradient参数是供使用的颜色渐变。center参数指定形状的中心,startAngle参数指定渐变的起始角度,endAngle指定结束角度。 - conicGradient (Gradient, center : UnitPoint, angle : Angle):此方法返回一个锥形渐变。
gradient参数是供使用的颜色渐变。center参数指定圆锥顶点的位置,angle参数指定渐变起始的角度。
这些返回前面介绍的渐变结构体中一种的实例,但颜色的渐变通过Gradient结构体来定义。
- Gradient (colors : [Color]):此初始化方法通过参数指定的颜色创建一个渐变。
colors参数是一个Color视图数组。 - Gradient (stops : [Gradient.Stop]):此初始化方法通过参数指定的颜色创建一个渐变。
stops参数为指定颜色的Stop结构体的数组,以及结束的时机。
另一个展示渐变所需的值是UnitPoint结构体。它类似CGPoint结构体,但专门设计用于处理图形结构体。
- UnitPoint (x : CGFloat, y : CGFloat):此初始化方法创建一个
UnitPoint结构体。x和y参数指定该点的x和y坐标。对于渐变,这些参数值在0.0到1.0之间。
UnitPoint结构体包含定义通用点的类型属性bottom、bottomLeading、bottomTrailing、center、leading、top、topLeading、topTrailing、trailing和zero。例如,我们对线性渐变应用bottom和top来从形状的底部到顶部绘制渐变。
示例11-7:定义线性渐变
less
struct ContentView: View {
let gradient = Gradient(colors: [Color.red, Color.green])
var body: some View {
RoundedRectangle(cornerRadius: 25)
.fill(.linearGradient(gradient, startPoint: .bottom, endPoint: .top))
.frame(width: 100, height: 100)
}
}
示例11-7 中的代码定义了两种颜色,红色和绿色的渐变,然后使用linearGradient()方法返回的结构体将渐变应用于RoundedRectangle视图。因为使用bottom声明了起始点,并用top声明了结束点,按从底到顶的顺序显示Gradient结构体中声明的颜色。

图11-6:线性渐变
创建渐变时若未指定结束颜色,颜色会在渐变占有的整个区域中均匀分布。如果希望调整分布,必须通过Stop结构体定义颜色。
- Stop (color : Color, location : CGFloat):此初始化方法通过结束值创建一个颜色。
color参数指定颜色,location参数指定颜色在渐变中开始的位置(值为0.0到1.0)。
下例重现了前面的案例,但这次绿色从0.4处开始(渐变占据区域的40%)。
示例11-8:通过自定义了结束点定义线性渐变
less
struct ContentView: View {
let gradient = Gradient(stops: [
Gradient.Stop(color: Color.red, location: 0.0),
Gradient.Stop(color: Color.green, location: 0.4)
])
var body: some View {
RoundedRectangle(cornerRadius: 25)
.fill(.linearGradient(gradient, startPoint: .bottom, endPoint: .top))
.frame(width: 100, height: 100)
}
}

图11-7:带自定义结束点的线性渐变
除了线性渐变,我们还可以创建各种形状的渐变。例如,通过圆心向外绘制环形图层来创建径向和椭圆渐变,如下所示。
示例11-9:定义环形变量
less
struct ContentView: View {
let gradient = Gradient(colors: [Color.red, Color.white])
var body: some View {
RoundedRectangle(cornerRadius: 25)
.fill(.radialGradient(gradient, center: .center, startRadius: 0, endRadius: 120))
.frame(width: 100, height: 100)
}
}
本例描述了如何创建径向渐变。所需要的值有Gradient结构体、圆心、渐变在形状中开始和结束的位置。这些值指定了渐变开始和结束位置,但仅在形状内的渐变部分才进行绘制。本例中,endRadius参数指定为120,但因为形状的尺寸是100x100,仅有部分渐变可见。

图11-8:环形渐变
还可以使用另外类型的渐变,角状或锥状渐变。在这些渐变中,延着圆绘制颜色使其从上方看像是圆锥。所需要的值取决于希望定义的圆锥类型。对于简单的锥形,只需要Gradient结构体、圆心以及渐变起始的角度。
示例11-10:定义锥状渐变
less
struct ContentView: View {
let gradient = Gradient(colors: [Color.red, Color.white])
var body: some View {
RoundedRectangle(cornerRadius: 25)
.fill(.conicGradient(gradient, center: .center, angle: .degrees(180)))
.frame(width: 100, height: 100)
}
}
渐变的角度通过Angle结构体的一个实例进行声明。这个结构体包含两类按度数或弧度值进行定义的方法:degrees(Double)和radians(Double)。在示例11-10的示例中,渐变的起始角度以180角进行定义,这正是默认起始点的对面。

图11-9:锥形渐变
效果
ShapeStyle协议中定义了前面小节中实现用于创建渐变结构体的类型方法,也指定了对其它视图应用效果的一些属性和方法。以下是最常用的一些。
- shadow (ShadowStyle):此方法对视图应用阴影。参数是创建内外投影的两个类型方法:
drop(color: Color, radius: CGFloat, x: CGFloat, y: CGFloat)和inner(color: Color, radius: CGFloat, x: CGFloat, y: CGFloat)。 - opacity(Double):此方法对视图赋值参数指定的透明度级别。参数接收0.0(全透明)到1.0(不透明)之间的值。
- blendMode (BlendMode):此方法决定了视图与背景及其它视图的混合方式。参数是一个枚举,值有:
normal、darken、multiply、colorBurn、plusDarker、lighten、screen、colorDodge、plusLighter、overlay、softLight、hardLight、difference、exclusion、hue、saturation、color、luminosity、sourceAtop、destinationOver和destinationOut。
很我修饰符可以接收遵循ShapeStyle协议的结构来为视图赋值样式。在操作形状时,这些样式与foregroundStyle()修饰符配合更佳。例如,若希望对矩形应用阴影,我们可以使用这一修饰符来实现阴影,使用foregroundStyle()修饰符定义填充颜色,如下例所示。
示例11-11:对视图添加阴影
less
struct ContentView: View {
var body: some View {
RoundedRectangle(cornerRadius: 25)
.foregroundStyle(.shadow(.drop(color: .black, radius: 3, x: 4, y: 4)))
.foregroundColor(.red)
.frame(width: 100, height: 100)
}
}

图11-10:阴影
图案
除了颜色和渐变,我们还可以使用图片来填充形状。为此SwiftUI内置了ImagePaint结构体。该结构体内置了如下类型方法用于创建自定义实例。
- image (Image, sourceRect : CGRect, scale : CGFloat):此方法返回按参数指定的图片和配置的
ImagePaint结构体。第一个参数提供我们希望使用的图片的Image视图,sourceRect参数指定要绘制的图片部分(默认为整个图片),scale参数定义图片的缩放(默认为原始大小)。
默认,ImagePaint结构体以原始尺寸使用整个图片,因此大多数情况下指定图片就足以让系统创建图案了,如下例所示。
示例11-12:使用图片填充形状
css
struct ContentView: View {
var body: some View {
Rectangle()
.fill(.image(Image(.pattern)))
.frame(width: 100, height: 100)
}
}
图片无限重复直到填充整个形状。在本例中,我们定义了一个100乘100个点的方块,使用一个25乘25点的图片进行绘制。因为图片小于形状,所以进行了多次绘制覆盖了整个区域。

图11-11:图案
其它相关内容请见虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记