与 UIKit 使用多种多样的控件布局不同,SwiftUI 的布局主要是使用各种 View(视图) 和 Modifier(修饰符) 来设置页面布局。在 UIKit 中,我们使用控件的各种属性来构建 UI,而 SwiftUI 中,我们用 Modifier 来修饰 View 来达到同样的目的。实际上,大部分 SwiftUI 中的 View 和 UIKit 中的控件是有对应关系的,我们在学习的时候只要掌握好这种对应关系即可。本文开始就梳理一下 SwiftUI 中常见的 View 和 Modifier。
Text
Text 即为显示文字的 View,对应 UIKit 中的 UILabel。
struct ContentView: View { var body: some View { Text("Hello SwiftUI") .font(.title) //字体大小 .foregroundColor(.orange) //字体颜色 .bold() //加粗 .italic() //斜体 .underline() //下划线 .lineLimit(2) //行数,若为nil则不限行数 } }
如果说我们有多行文本,可以通过 .multilineTextAlignment 修饰符来设置多行文本之间的对齐方式。
struct ContentView: View { var body: some View { Text("Hello\nSwiftUI") .multilineTextAlignment(.center) } }
在以往的 UIKit 中,我们若想要实现将不同样式的文本拼接在一起,就必须用到 NSAttributedString。但在 SwiftUI 中就非常容易,只要把两个 Text 用“+”拼接起来即可。
struct ContentView: View { var body: some View { Text("Hello ") .foregroundColor(.red) + Text("SwiftUI") .bold() .italic() } }
需要注意的是,第四行的加号不能省略,否则就违反了上一篇文章所讨论的 “返回 some修饰的协议时返回值类型必须确定”。
当 Text 中的文本特别长时,我们可以设置一些截断方式。
struct ContentView: View { var name: String = "SwiftUI是一门非常新的技术,我们一定要好好学习。" var body: some View { //长文本 Text(self.name) .frame(width:160, height: 50) //.truncationMode(.head) //头部截断 //.truncationMode(.middle) //中部截断 //.truncationMode(.tail) //默认尾部截断 } }
同时,我们可以通过设置 .minimumScaleFactor 修饰符,在当 Text 文本显示不下的时候通过缩小字号来使得文字显示全。
struct ContentView: View { var name: String = "SwiftUI是一门非常新的技术,我们一定要好好学习。" var body: some View { //长文本 Text(self.name) .frame(width:160, height: 50) .minimumScaleFactor(0.5) //括号里的参数指定该View允许的最小文本缩放比例,会根据情况自动调整 } }
通过使用 lineSpacing 来控制多行文本之间的行间距。
struct ContentView: View { var name: String = "SwiftUI是一门非常新的技术,我们一定要好好学习。" var body: some View { Text(self.name) .frame(width:160, height: 70) .lineSpacing(20) } }
若我们要控制文本中的字符间距,可以使用 tracking 或 kerning 修饰符。tracking 会拉开连字符,而 kerning 不会;kerning 会留下一些尾随空白,而 tracking 不会。
struct ContentView: View { var body: some View { VStack { //显示为 f fi Text("ffi") .font(.custom("AmericanTypewriter", size: 70)) .kerning(50) //显示为 f f i Text("ffi") .font(.custom("AmericanTypewriter", size: 70)) .tracking(50) } } }
padding()
padding 用于设置一个 View 周围的填充。在不设置参数的情况下,可以获得所有方向上系统提供的默认填充。
struct ContentView: View { var body: some View { Text("Hello SwiftUI") .padding() } }
当然我们也可以自定义填充的方向和大小。
struct ContentView: View { var body: some View { Text("Hello SwiftUI") .padding(.bottom, 100) } }
frame()
和 UIKit 类似,frame 修饰符用于显式指定一个 View 的大小(但是不能指定 View 的坐标了)。我们可以直接在构造函数中传入 width 和 height 指定 View 的宽高,也可以传入 minWidth、idealWidth、maxWidth、minHeight、idealHeight、maxHeight 来进行更加个性化的设置(若不指定就采用系统默认值)。还可以根据需要设置对齐方式 alignment。
struct ContentView: View { var body: some View { Text("666") .frame(width: 100, height: 50, alignment: .bottomLeading) //文字设在100x50区域的左下角 } }
Shape
Shape 是 SwiftUI 中新增的一个 View,用来表示形状。
struct ContentView: View { var body: some View { VStack { //矩形 Rectangle() .foregroundColor(Color.red) .padding() //圆角矩形 RoundedRectangle(cornerSize: CGSize(width: 10,height: 10)) .foregroundColor(Color.blue) .frame(height:200) //圆形 Circle() .foregroundColor(Color.yellow) //椭圆 Ellipse() .foregroundColor(Color.pink) .frame(height:200) //胶囊型 Capsule() .fill(Color.green) .frame(width: 200, height: 50) } } }
还记得当初做红家面试题的时候,题目要求是将一个正方形切圆。在 SwiftUI 中,我们可以使用 .clipShape 修饰符轻松实现:
struct ContentView: View { var body: some View { Rectangle() .foregroundColor(Color.red) .frame(width: 100, height: 100) .clipShape(Circle()) } }
Color
颜色在 SwiftUI 中也是一种 View。
struct ContentView: View { var body: some View { Color.pink .frame(width: 200, height: 100) .padding() } }
HStack
HStack 代表水平容器,用于处理若干个元素的横向摆放。在 HStack 的构造函数中我们可以传入两个属性:alignment 表示对齐方式、spacing 表示两两间距。
默认情况下,SwiftUI 会针对不同的 View设置默认间距,所以如果不需要,必须显式设为 0。
struct ContentView: View { var body: some View { HStack(alignment: .center, spacing: 20) { Color.red Color.blue } } }
VStack
VStack 代表垂直容器,用于处理若干个元素的纵向摆放。和 HStack 相同,我们也可以在构造函数中传入 alignment 和 spacing 属性。
struct ContentView: View { var body: some View { VStack(alignment: .center, spacing: 20) { Color.red Color.blue } } }
ZStack
ZStack 代表 Z 轴容器,可以理解为与屏幕垂直方向的容器。由于与屏幕垂直的方向只有前后之分,故 ZStack 没有 spacing 属性但是仍有 alignment 属性。
struct ContentView: View { var body: some View { ZStack (alignment: .center){ Circle() .foregroundColor(Color.yellow) .frame(width: 50, height: 50, alignment: .center) Text("1") } } }
HStack、VStack、ZStack 三种容器可以互相嵌套使用,例如下面这个例子。如果一切顺利,你可以在屏幕上看到一个用图形构建的人形。
struct ContentView: View { var body: some View { VStack (alignment: .center, spacing: 20){ ZStack { Circle() HStack { Rectangle() .foregroundColor(Color.red) .frame(width: 50, height: 50) .padding() Rectangle() .foregroundColor(Color.red) .frame(width: 50, height: 50) .padding() } } HStack { Capsule() .frame(width: 80) Rectangle() Capsule() .frame(width: 80) } HStack { RoundedRectangle(cornerRadius: 10) .frame(width: 100) RoundedRectangle(cornerRadius: 10) .frame(width: 100) } } } }
zIndex()
zIndex 为 ZStack 的 Z 轴索引。ZStack 中的 View 是叠加显示的,写在后面的 View 会遮住前面的,可以使用 zIndex 设置。View 的索引,索引大的就会显示在前面。
struct ContentView: View { var body: some View { ZStack { RoundedRectangle(cornerRadius: 30, style: .continuous) .frame(width: 300, height: 200) .foregroundColor(.red) .zIndex(2) RoundedRectangle(cornerRadius: 30, style: .continuous) .frame(width: 300, height: 200) .foregroundColor(.blue) .zIndex(1) } //此时红色圆角矩形在蓝色圆角矩形的上面 } }
Spacer
Spacer 是一个灵活的空间,可大可小,可以智能根据上下文自动设置大小完成空间的填充。
struct ContentView: View { var body: some View { VStack { Color.red //空隙(灵活的空间) Spacer() Color.blue }.frame(height: 300) } }
可以通过 frame 修饰符来指定 Spacer 的大小。
struct ContentView: View { var body: some View { VStack { Color.red //指定Spacer的大小 Spacer() .frame(height:100) Color.blue }.frame(height: 300) } }
Divider
Divider 会显示一条分隔线用于分隔内容,其中 VStack 里面的分隔线是水平的、HStack 里面的分隔线是垂直的。
struct ContentView: View { var body: some View { VStack { Color.red //水平分隔线 Divider() Color.blue }.frame(height: 300) } }
struct ContentView: View { var body: some View { HStack { Color.red //垂直分隔线 Divider() Color.blue }.frame(height: 300) } }
Image
Image 即为显示图片的 View,对应 UIKit 中的 UIImageView。
struct ContentView: View { var body: some View { VStack { Image("666") //资源图片,需要将图片预先放在工程的Assets.xcassets文件夹中 Image(systemName: "clock.fill") //系统图片 } } }
关于系统图片,我们可以前往 Apple 开发者网站 https://developer.apple.com/sf-symbols 下载 sf-symbols APP 来查询某个系统图片的名称。
设置 forgroundColor 修饰符,我们可以改变渲染系统图片的颜色。有的时候系统图片可能默认被渲染成蓝色,我们可用 renderingMode(.original) 修饰符渲染成原图。
struct ContentView: View { var body: some View { VStack { Image(systemName: "clock") .foregroundColor(.red) Image(systemName: "clock") .renderingMode(.original) } } }
设置 frame、font 或 imageScale 修饰符,我们可以控制 Image 的大小。其中 font 既可以选择系统预置的大小(比如 .largeTitle、.footnote 等),也可以通过传入 .system(size: ) 的方法传入字号来设置图片大小;而 imageScale 只有三种大小——large、medium、small。
struct ContentView: View { var body: some View { VStack { Image(systemName: "clock") .foregroundColor(.red) .font(.system(size: 30)) //直接输入字号 // .font(.largeTitle) //使用系统预置大小 .padding() Image(systemName: "clock") .foregroundColor(.red) .imageScale(.large) } } }
特别地,若我们要用 frame 修饰符来改变图片的大小,我们必须先用 .resizable 修饰符来修饰 Image。
struct ContentView: View { var body: some View { Image(systemName: "clock") .resizable() //一定要有resizable,否则指定frame是无效的 .foregroundColor(.red) .frame(width: 20, height: 20) //指定frame .padding() } }
默认情况下,SwiftUI中图片绘制与 frame是无关的,而只会遵从图片本身的大小。如果我们想要图片可以按照所在的 frame缩放,需要添加 resizable()修饰符。
Image 有三种设置显示模式的修饰符——aspectRatio、scaledToFill、scaledToFit。其中 aspectRatio 的第一个参数用于控制图片显示的比例,第二个参数 contentMode 用于控制图片显示是否铺满屏幕(.fill 是充满屏幕、.fit 是根据屏幕大小自适应)。若我们不需要控制图片显示的比例,我们也可以直接使用 scaledToFill、scaledToFit 来设置是否需要铺满屏幕。
struct ContentView: View { var body: some View { Image(systemName: "clock") .resizable() //这边同样要先加resizable修饰符 //图片充满整个屏幕,下面两种写法是等价的 // .aspectRatio(contentMode: .fill) // .scaledToFill() //图片大小自适应,下面两种写法是等价的 // .aspectRatio(contentMode: .fit) // .scaledToFit() .aspectRatio(2, contentMode: .fit) //启用第一个参数,设置长宽比 } }
我们可以设置一个 Image 为其它 View 的背景。
struct ContentView: View { var body: some View { Text("Hello SwiftUI") .font(.largeTitle) .background( Image(systemName: "clock") .resizable() .foregroundColor(.red) .frame(width: 100, height: 100)) } }
Image 同样支持 .clipShape 修饰符来切割图片的形状。
struct ContentView: View { var body: some View { Image(systemName: "exclamationmark.square.fill") .resizable() .frame(width: 100, height: 100) .clipShape(Circle()) //切圆 } }
下面的代码综合运用了本文的一些知识,设置了一张图片的大小并将其居中屏幕显示。
struct ContentView: View { var body: some View { VStack(alignment:.leading,spacing: 0){ Image(systemName: "exclamationmark.square.fill") .resizable() .scaledToFit() .frame( //最大宽度 maxWidth: UIScreen.main.bounds.width, //最大高度 maxHeight: 300, alignment: .center ) } } }