[本文翻译自hackingwithswift,点击链接阅读原文]
我们将从简单开始,然后向上努力。一路上,你会开始看到SwiftUI让一些变得简单的事情,以及一些更难的事情。
在ContentView.swift中,是一个基本结构,代表我们应用程序中唯一的屏幕:ContentView
。看起来像这样:
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.accentColor)
Text("Hello, world!")
}
.padding()
}
}
这不是很多代码,但它已经告诉我们很多:
视图是SwiftUI中的结构。
所有视图都必须符合
View
协议。该协议需要一个名为
body
的计算属性,其中包含视图的实际布局。它返回
some View
,这是一个名为不透明返回类型的Swift功能——它的意思是“一种特定类型的视图,但我们真的不在乎是哪种视图。”在我们的内容视图中是少量的用户界面:显示地球图标的图像和一些写着“你好,世界”的文字,两者都被包裹在
VStack
中,使其垂直对齐。随着我们的进展,我们将更详细地查看这些观点。有一些方法调用到位:
.imageScale().foregroundStyle()
和padding()
在SwiftUI中,我们调用这些修饰符,因为它们修改了文本视图的外观或行为方式。
您还应该在Xcode的右侧看到一个预览面板。这会随着您键入而更新,这使得它成为在您工作时查看更改的好方法。如果您没有看到右侧的预览面板,请转到“编辑器”菜单并选取“画布”。
如果Xcode的预览区域停止——这种情况经常发生——您可以按Opt-Cmd-P使其恢复显示您的布局。
这真的很重要,所以我重复一遍:按Opt-Cmd-P将使您的SwiftUI预览更新。
在我们的应用程序中,此屏幕将向我们显示菜单中的项目列表,因此我们将使用
List
视图,而不是Xcode的默认模板代码。
因此,用这个替换当前文本视图:
List {
Text("Hello World")
Text("Hello World")
Text("Hello World")
}
当预览更新时,您现在将看到相当于UIKit的UITableView
的三段文本,都写着“Hello World”。这是一个静态列表视图——我们正在发送三段固定数据,因此它将它们解释为表格中的三行。
在我们的应用程序中,菜单将包含可以订购的项目列表,点击其中一个将显示一个新屏幕,其中包含有关该订单项目的详细信息。这就像在UIKit中一样:我们在导航控件中包装表格。
在SwiftUI中,这个导航控件是一个NavigationStack
,它结合了UINavigationBar
的显示样式和UINavigationController
的视图控制器堆栈行为。要带一个,只需在您的列表周围添加NavigationStack
,如下:
NavigationStack {
List {
Text("Hello World")
Text("Hello World")
Text("Hello World")
}
}
当预览更新时,你会看到事情看起来一样,但这只是因为我们还没有给它起一个标题。
早些时候,我简要地提到了padding()
修饰符,说修饰符之所以得名,是因为它们修改了视图的外观或行为方式。SwiftUI有许多修饰符——数百个,很容易——每个修饰符都允许您以一种非常具体的方式自定义视图的行为。
是的,修饰符看起来像普通的Swift方法,但它们更复杂,因为它们实际上改变了它们所应用的内容。简单来说,如果您有一些文本并应用padding()
修饰符,您不仅会返回一些文本,而且周围有一些空间——您实际上会返回另一种类型。
在这种情况下,我们想将navigationTitle()
修饰符应用于我们的列表视图,该视图接受在导航栏中显示的某种文本。所以,我们会这样写:
NavigationStack {
List {
Text("Hello World")
Text("Hello World")
Text("Hello World")
}
.navigationTitle("Menu")
}
是的,修饰符附加到列表而不是导航堆栈上——想想我们如何设置UIViewController
的标题,而不是尝试设置UINavigationController
的标题。
如果您现在尝试运行该应用程序,您会看到一切都完全按照我们预期的工作——表格滚动,导航栏随着您滚动而缩小,等等。SwiftUI做的一件伟大的事情是默认为我们提供现代系统行为,因此我们获得大导航栏标题作为标准。
当您有固定的表格单元格时,静态文本工作正常,但就我们而言,我们有很多菜单项目可以加载到多个部分——早餐、主菜、甜点和饮料。我们真正想做的是从JSON中加载菜单数据,然后将其用于我们的列表项,这实际上并不难完成。
首先,我们需要加载我们的数据。您已导入的Helper.swift文件包含从应用程序捆绑包中加载Codable
JSON的代码,非常适合加载我们的menu.json文件。因此,现在将此属性添加到ContentView
结构中:
let menu = Bundle.main.decode([MenuSection].self, from: "menu.json")
接下来,我们需要将我们的列表放在菜单上的各个部分。这是通过使用ForEach
块来完成的,该块循环数组中的项目,并重复其中的任何东西:
List {
ForEach(menu) {
Text("Hello World")
Text("Hello World")
Text("Hello World")
}
}
List
和ForEach
之后的开头大括号实际上表示闭包的开始,在ForEach
的情况下,SwiftUI将把数组中的每个部分传递到闭包中,以便我们可以配置它。
因此,我们需要通过将代码修改为以下内容来接受该部分:
ForEach(menu) { section in
这几乎有用,但还有一件事我们需要做。SwiftUI需要知道如何识别我们表格中的每个单元格——它需要确切地知道哪个是哪个,这样如果我们要求的话,它可以为我们添加和删除一些东西。当我们有一个静态列表时,这不是问题,因为它可以看到有三个,但现在我们有一个动态列表,我们需要告诉它关于每个部分的东西,使其独一无二。
如果您打开Menu.swift,您将看到定义MenuSection
和MenuItem
的结构,两者都有包含UUID
的id
属性——一个通用唯一标识符。这非常适合我们使用,因为每个部分的每个菜单项都有一个唯一的标识符,因此SwiftUI可以知道哪个是哪个。
我们可以通过使两种类型符合Identifiable
来告诉SwiftUI使用这些标识符。该协议只有一个要求,即符合类型必须有一个名为id
的属性,可以唯一地识别它们。我们已经有了,所以只需将Identifiable
添加到这两种类型中就足够了:
struct MenuSection: Codable, Identifiable {
并且:
struct MenuItem: Codable, Equatable, Identifiable {
如果您现在运行代码,您将看到十二行包含“Hello World”——这是您可能没有想到的。
改变的是,我们现在有一个动态列表,我们的ForEach
将为菜单部分中的每个项目执行其闭包的正文——三个文本视图——一次。我们有四个部分,每个部分都有三个文本视图,所以我们总共有12个。
为了解决这个问题,我们将要求每个部分提供一个文本视图,并给它我们要显示的部分名称:
List {
ForEach(menu) { section in
Text(section.name)
}
}
接下来,让我们在每个部分中添加项目。这是ForEach
部分中的另一个ForEach
,像这样:
List {
ForEach(menu) { section in
Text(section.name)
ForEach(section.items) { item in
Text(item.name)
}
}
}
现在,您将看到许多表格行,其中一些包含部分名称(“早餐”、“主菜”等),一些包含菜单项名称(“全英文”、“超级食品沙拉”等)。
虽然这有效,但它并不理想——它不会在我们的表格中创建任何视觉结构,所以我们将把它拆散。标准的UIKit方法是使用表格视图部分,而SwiftUI为此为我们提供了Section
视图。我们可以用Section
名称作为标题替换Text(section.name)
,该部分将用作部分开头的文本。内部的ForEach
——包含我们的菜单项的那个——就在该部分内,因此SwiftUI将了解我们如何将内容分组在一起。
最终结果如下所示:
List {
ForEach(menu) { section in
Section(section.name) {
ForEach(section.items) { item in
Text(item.name)
}
}
}
}
默认情况下,SwiftUI的列表使用UITableView
的“嵌套分组”样式,但我们可以通过在navigationTitle()
后添加另一个修饰符来更改它:
.listStyle(.grouped)
评论区