在此界面中选择项目需要保存的位置,点击创建按钮,项目就生成了。
Xcode 的界面这里就不赘述了,读者可以自己摸索摸索。
提醒一下,记得将图片中序号1的渲染机型修改为 iPhone 13 Pro 或者你熟悉的手机机型。
Hello World让我们仔细观察 ContentView.swift 文件,此文件大体上分为三块:
- import SwiftUI 导入了 Swift 的核心框架,暂时先不管它。
- 尾部的结构体 ContentView_Previews 定义了模拟器中需要渲染的内容,也不管它。
- 中间的结构体 ContentView ,定义了手机页面的外观,它就是核心了。
观察 ContentView ,现在我们不用去关心什么是 var body 、 什么是 some View ,只需要知道 var body 后面花括号里的东西,直接代表了手机上界面的样子,就足够了。
所以那花括号里有什么?来看看:
Text("Hello, world!")
.padding()
就算你完全没学过编程,看单词也能猜到个大概的意思:
- Text("Hello, world!"):页面中有一行文字,内容是 Hello, world! 。
- .padding():文字周围用空白进行了某个宽度的填充。
是这样的吗?点击上图中模拟器面板的 Resume 按钮,渲染效果试试看:
其实这里就能看出 SwiftUI 框架有意思的地方了:它描述了“UI 应该是什么样子”,而不是用一句句代码指导“应该怎样去构建 UI”。
举个例子,如果用传统的命令式开发模式,我们在代码中需要创建文本类、设置它的文字内容、将其添加到 UI 上、并且进行布局:
// 页面启动完成的生命周期函数
func viewDidLoad() {
super.viewDidLoad()
// 创建文本对象
let label = UILabel()
// 给文本赋值
label.text = "Hello, World!"
// 将文本对象添加到主 UI 中
view.addSubview(label)
// 下面省略了大一串布局代码
// ...
}
相对的,用本文中聊到的声明式开发模式,你只要告诉程序“我需要一行文本”就可以了:
var body: some View {
Text("Hello, world!")
}
是不是清爽多了?
状态和列表欣赏完项目自动生成的代码后,接下来就要改动 ContentView 里的内容,让其变成一个可以展示待办清单列表的迷你项目了:
// struct 表示结构体,如果以前没接触过,你暂时把它理解成 class 类就好了
// (实际上结构体和类有本质区别,这里不展开讲)
struct ContentView: View {
// @State 修饰的变量表示本地状态
// todoList 的任何改变,都会自动触发 UI 的重新渲染
@State private var todoList = ["Apple", "Pear", "Tomato"]
var body: some View {
// 列表控件
List {
// 循环渲染元素
ForEach(todoList, id: \.self) { item in
Text(item)
}
}
}
}
被 @State 修饰的变量,代表了它是被管理起来的本地状态,用于 UI 中数据的双向绑定。
再看看 var body 中的改动:
- List 表示这里有一个列表。
- ForEach 表示列表的内容将依靠本地状态 todoList 动态生成。里面的 id: \.self 暂时不用去深究它,你只需要知道它用于识别列表中每个元素的身份就可以了。
- item 是列表的每个元素,用 Text(item) 展示了其文本内容。
如果你学习过 Vue ,那么理解起来就更加容易了:@State 就类似于 Vue 中的 data ; ForEach(..., id: ...) 和 Vue 中的 v-for 语法类似,需要一个 id 值作为列表元素的识别凭证。
再一次提醒,作为新手了解向的文章,你真的不需要逐句去斟酌每个对象、关键字、变量的具体含义,只需要知道它们大概的作用就足够了。
代码就这么多了,来看看渲染效果:
用有限的几行代码,就创造出了一个简单而美观的列表,并且具有了良好的布局效果。
自定义状态试试在模拟器中,将手机横向放置的布局效果,同样做到了很好的自适应,尽管你没写哪怕一行跟布局有关的代码。
上面的代码中,我们用了一个字符串数组 todoList ,定义了一个本地状态。但是如此简单的数据结构,对一个待办清单来说显然差了点意思。最起码的,如何表现此条项目完成与否?
这时候就需要复杂一点的数据结构了,比如将自定义对象作为本地状态的列表元素。
首先定义一个结构体 ToDoItem :
struct ToDoItem: Identifiable {
let id = UUID()
var isOn = true
let name: String
}
- 它满足 Identifiable 协议,因此后面代码的 ForEach 中就不用写难看的 id: \.self 了,程序会根据协议自动将 ToDoItem 里的 let id = UUID() 作为身份识别符。
- isOn 是一个可更改(var)的布尔值,表征了此对象是否已完成。
- name 是一个不可更改(let)的字符串,表征了对象的具体文本内容。
很好,接下来将 ContentView 修改成下面这样:
struct ContentView: View {
// 装饰器 @State 表示 todoList 是一个受监控的本地状态
@State private var todoList = [
ToDoItem(name: "Apple"),
ToDoItem(name: "Pear"),
ToDoItem(name: "Tomato")
]
var body: some View {
// 列表元素
List {
// 动态遍历渲染
ForEach(todoList) { item in
Text(item.name)
}
}
}
}
- 将本地状态的列表元素替换为了 ToDoItem 结构体。
- 注意观察 ForEach 中的变化。
渲染界面如下: