SwiftUI 学习笔记 23:项目 3-1 自定义修饰符和自定义容器
背景填充整个屏幕
我们可以通过使用frame()修饰符来做到这一点,同时传入.infinity其最大宽度和最大高度。
1 | Text("Hello World") |
使用maxWidth并maxHeight与使用不同width和height-我们不是说文本视图必须占用所有的空间,只知道它可以。如果周围还有其他视图,SwiftUI将确保它们都获得足够的空间。
默认情况下,你的视图不会离开安全区域,但是你可以使用如下edgesIgnoringSafeArea()修饰符来更改它:
1 | Text("Hello World") |
条件修饰符
如果你拥有一个可以为true或false的属性,则可以使用该属性来控制按钮的前景色,如下所示:
1 | struct ContentView: View { |
因此,当useRedText为true时,修饰符有效读取.foregroundColor(.red),而为false时,修饰符变为.foregroundColor(.blue)。由于SwiftUI会监视我们@State属性的更改并重新调用我们的body属性,因此只要该属性更改,颜色就会立即更新。
环境修改器
如果我们在中有四个文本视图,VStack并希望为它们提供相同的字体修饰符,则可以将修饰符VStack直接应用于,并将更改应用于所有四个文本视图:
1 | VStack { |
这称为环境修改器,与应用于视图的常规修改器不同。
从编码角度来看,这些修饰符的使用方式与常规修饰符完全相同。但是,它们的行为略有不同,因为如果这些子视图中的任何一个覆盖了相同的修饰符,则子版本优先。
例如,这显示了四个带有标题字体的文本视图,但是其中一个具有较大的标题:
1 | VStack { |
例如blur()它是常规修饰符,因此,应用于子视图的所有模糊都将添加到该VStack模糊中,而不是替换它。
将视图作为属性
我们可以像这样创建两个文本视图作为属性,然后在a内使用它们VStack:
1 | struct ContentView: View { |
你甚至可以在使用这些属性时直接将修饰符应用于这些属性,如下所示:
1 | VStack { |
自定义修饰符
SwiftUI为我们提供了内置的改性剂,如一系列的font(),background()和clipShape()。但是,也可以创建执行特定操作的自定义修饰符。
例如,我们可能会说应用程序中的所有标题都应具有特定的样式,因此首先我们需要创建一个自定义ViewModifier结构来实现我们想要的功能:
1 | struct --- |
现在,我们可以将其与modifier()修饰符一起使用-是的,它是一个称为“修饰符”的修饰符,但是它允许我们将任何种类的修饰符应用于视图,如下所示:
1 | Text("Hello World") |
使用自定义修饰符时,通常在其上创建扩展View使其易于使用的明智之举。例如,我们可以将Title修饰符包装在如下扩展中:
1 | extension View { |
我们现在可以像这样使用修饰符:
1 | Text("Hello World") |
自定义修改器不仅可以应用其他现有修改器,还可以做更多的工作-它们还可以根据需要创建新的视图结构。记住,修饰符会返回新对象,而不是修改现有对象,因此我们可以创建一个将视图嵌入堆栈并添加另一个视图的对象:
1 | struct Watermark: ViewModifier { |
有了它,我们现在可以为任何视图添加水印,如下所示:
1 | Color.blue |
自定义容器
尽管你不太可能经常这样做,但我至少想向你展示,在SwiftUI应用程序中完全有可能创建自定义容器。这需要更高级的Swift知识,因为它利用了Swift的一些强大功能,因此,如果发现太多,可以跳过。
为了进行试验,我们将创建一种称为a的新型堆栈GridStack,这将使我们能够在网格内创建任意数量的视图。我们要说的是,有一个名为struct的新结构GridStack,它符合View协议并且具有一定数量的行和列,并且在网格内部将有很多内容单元格,它们本身必须符合View协议。
在Swift中,我们可以这样写:
1 | struct GridStack<Content: View>: View { |
第一行– struct GridStack<Content: View>: View使用Swift的更高级功能,称为通用(generics),在这种情况下,它意味着“你可以提供所需的任何种类的内容,但是无论它必须符合View协议的内容。”在冒号之后,我们View再次重复说它GridStack本身也符合View协议。
请特别注意这一let content行–定义了一个闭包,该闭包必须能够接受两个整数并返回我们可以显示的某种内容。
我们需要通过body组合多个垂直和水平堆栈以创建所需数量的单元格来完成该属性。我们不需要说什么是在每个单元中,因为我们可以得到通过拨打我们content用适当的行和列关闭。
因此,我们可以这样填写:
1 | var body: some View { |
现在我们有了一个自定义容器,我们可以使用它来编写一个视图,如下所示:
1 | struct ContentView: View { |
GridStack只要符合View协议,我们就能接受任何种类的细胞内容。因此,如果需要,我们可以给单元格一个堆栈:
1 | GridStack(rows: 4, columns: 4) { row, col in |
想走得更远吗?
为了获得更大的灵活性,我们可以利用SwiftUI的一种称为视图构建器的功能,该功能允许我们发送多个视图并将其形成隐式堆栈。
要使用此功能,我们需要为我们的GridStack结构创建一个自定义初始化程序,因此我们可以将content关闭标记为使用SwiftUI的视图构建器系统:
1 | init(rows: Int, columns: Int, content: @escaping (Int, Int) -> Content) { |
多数情况下,只是将参数直接复制到结构的属性中,但请注意该@ViewBuilder属性在那里。你还将看到该@escaping属性,该属性使我们可以存储闭包,以备后用。
有了适当的设置,SwiftUI现在将在我们的单元格封闭内部自动创建一个隐式水平堆栈:
1 | GridStack(rows: 4, columns: 4) { row, col in |
这两个选项均有效,所以无论你喜欢哪个都可以。
参考资料
- 感谢你赐予我前进的力量