背景填充整个屏幕

我们可以通过使用frame()修饰符来做到这一点,同时传入.infinity其最大宽度和最大高度。

1
2
3
Text("Hello World")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)

使用maxWidth并maxHeight与使用不同width和height-我们不是说文本视图必须占用所有的空间,只知道它可以。如果周围还有其他视图,SwiftUI将确保它们都获得足够的空间。

默认情况下,你的视图不会离开安全区域,但是你可以使用如下edgesIgnoringSafeArea()修饰符来更改它:

1
2
3
4
Text("Hello World")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.edgesIgnoringSafeArea(.all)

条件修饰符

如果你拥有一个可以为true或false的属性,则可以使用该属性来控制按钮的前景色,如下所示:

1
2
3
4
5
6
7
8
9
10
11
struct ContentView: View {
@State private var useRedText = false

var body: some View {
Button("Hello World") {
// flip the Boolean between true and false
self.useRedText.toggle()
}
.foregroundColor(useRedText ? .red : .blue)
}
}

因此,当useRedText为true时,修饰符有效读取.foregroundColor(.red),而为false时,修饰符变为.foregroundColor(.blue)。由于SwiftUI会监视我们@State属性的更改并重新调用我们的body属性,因此只要该属性更改,颜色就会立即更新。

环境修改器

如果我们在中有四个文本视图,VStack并希望为它们提供相同的字体修饰符,则可以将修饰符VStack直接应用于,并将更改应用于所有四个文本视图:

1
2
3
4
5
6
7
VStack {
Text("Gryffindor")
Text("Hufflepuff")
Text("Ravenclaw")
Text("Slytherin")
}
.font(.title)

这称为环境修改器,与应用于视图的常规修改器不同。

从编码角度来看,这些修饰符的使用方式与常规修饰符完全相同。但是,它们的行为略有不同,因为如果这些子视图中的任何一个覆盖了相同的修饰符,则子版本优先。

例如,这显示了四个带有标题字体的文本视图,但是其中一个具有较大的标题:

1
2
3
4
5
6
7
8
VStack {
Text("Gryffindor")
.font(.largeTitle)
Text("Hufflepuff")
Text("Ravenclaw")
Text("Slytherin")
}
.font(.title)

例如blur()它是常规修饰符,因此,应用于子视图的所有模糊都将添加到该VStack模糊中,而不是替换它。

将视图作为属性

我们可以像这样创建两个文本视图作为属性,然后在a内使用它们VStack:

1
2
3
4
5
6
7
8
9
10
11
struct ContentView: View {
let motto1 = Text("Draco dormiens")
let motto2 = Text("nunquam titillandus")

var body: some View {
VStack {
motto1
motto2
}
}
}

你甚至可以在使用这些属性时直接将修饰符应用于这些属性,如下所示:

1
2
3
4
5
6
VStack {
motto1
.foregroundColor(.red)
motto2
.foregroundColor(.blue)
}

自定义修饰符

SwiftUI为我们提供了内置的改性剂,如一系列的font(),background()和clipShape()。但是,也可以创建执行特定操作的自定义修饰符。

例如,我们可能会说应用程序中的所有标题都应具有特定的样式,因此首先我们需要创建一个自定义ViewModifier结构来实现我们想要的功能:

1
2
3
4
5
6
7
8
9
10
11
struct ---
title: ViewModifier {
func body(content: Content) -> some View {
content
.font(.largeTitle)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}

现在,我们可以将其与modifier()修饰符一起使用-是的,它是一个称为“修饰符”的修饰符,但是它允许我们将任何种类的修饰符应用于视图,如下所示:

1
2
Text("Hello World")
.modifier(Title())

使用自定义修饰符时,通常在其上创建扩展View使其易于使用的明智之举。例如,我们可以将Title修饰符包装在如下扩展中:

1
2
3
4
5
extension View {
func titleStyle() -> some View {
self.modifier(Title())
}
}

我们现在可以像这样使用修饰符:

1
2
Text("Hello World")
.titleStyle()

自定义修改器不仅可以应用其他现有修改器,还可以做更多的工作-它们还可以根据需要创建新的视图结构。记住,修饰符会返回新对象,而不是修改现有对象,因此我们可以创建一个将视图嵌入堆栈并添加另一个视图的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Watermark: ViewModifier {
var text: String

func body(content: Content) -> some View {
ZStack(alignment: .bottomTrailing) {
content
Text(text)
.font(.caption)
.foregroundColor(.white)
.padding(5)
.background(Color.black)
}
}
}

extension View {
func watermarked(with text: String) -> some View {
self.modifier(Watermark(text: text))
}
}

有了它,我们现在可以为任何视图添加水印,如下所示:

1
2
3
Color.blue
.frame(width: 300, height: 200)
.watermarked(with: "Hacking with Swift")

自定义容器

尽管你不太可能经常这样做,但我至少想向你展示,在SwiftUI应用程序中完全有可能创建自定义容器。这需要更高级的Swift知识,因为它利用了Swift的一些强大功能,因此,如果发现太多,可以跳过。

为了进行试验,我们将创建一种称为a的新型堆栈GridStack,这将使我们能够在网格内创建任意数量的视图。我们要说的是,有一个名为struct的新结构GridStack,它符合View协议并且具有一定数量的行和列,并且在网格内部将有很多内容单元格,它们本身必须符合View协议。

在Swift中,我们可以这样写:

1
2
3
4
5
6
7
8
9
struct GridStack<Content: View>: View {
let rows: Int
let columns: Int
let content: (Int, Int) -> Content

var body: some View {
// more to come
}
}

第一行– struct GridStack<Content: View>: View使用Swift的更高级功能,称为通用(generics),在这种情况下,它意味着“你可以提供所需的任何种类的内容,但是无论它必须符合View协议的内容。”在冒号之后,我们View再次重复说它GridStack本身也符合View协议。

请特别注意这一let content行–定义了一个闭包,该闭包必须能够接受两个整数并返回我们可以显示的某种内容。

我们需要通过body组合多个垂直和水平堆栈以创建所需数量的单元格来完成该属性。我们不需要说什么是在每个单元中,因为我们可以得到通过拨打我们content用适当的行和列关闭。

因此,我们可以这样填写:

1
2
3
4
5
6
7
8
9
10
11
var body: some View {
VStack {
ForEach(0 ..< rows) { row in
HStack {
ForEach(0 ..< self.columns) { column in
self.content(row, column)
}
}
}
}
}

现在我们有了一个自定义容器,我们可以使用它来编写一个视图,如下所示:

1
2
3
4
5
6
7
struct ContentView: View {
var body: some View {
GridStack(rows: 4, columns: 4) { row, col in
Text("R\(row) C\(col)")
}
}
}

GridStack只要符合View协议,我们就能接受任何种类的细胞内容。因此,如果需要,我们可以给单元格一个堆栈:

1
2
3
4
5
6
GridStack(rows: 4, columns: 4) { row, col in
HStack {
Image(systemName: "\(row * 4 + col).circle")
Text("R\(row) C\(col)")
}
}

想走得更远吗?

为了获得更大的灵活性,我们可以利用SwiftUI的一种称为视图构建器的功能,该功能允许我们发送多个视图并将其形成隐式堆栈。

要使用此功能,我们需要为我们的GridStack结构创建一个自定义初始化程序,因此我们可以将content关闭标记为使用SwiftUI的视图构建器系统:

1
2
3
4
5
init(rows: Int, columns: Int, @ViewBuilder content: @escaping (Int, Int) -> Content) {
self.rows = rows
self.columns = columns
self.content = content
}

多数情况下,只是将参数直接复制到结构的属性中,但请注意该@ViewBuilder属性在那里。你还将看到该@escaping属性,该属性使我们可以存储闭包,以备后用。

有了适当的设置,SwiftUI现在将在我们的单元格封闭内部自动创建一个隐式水平堆栈:

1
2
3
4
GridStack(rows: 4, columns: 4) { row, col in
Image(systemName: "\(row * 4 + col).circle")
Text("R\(row) C\(col)")
}

这两个选项均有效,所以无论你喜欢哪个都可以。

参考资料

查看下一天的SwiftUI学习笔记

关于100days英文课程