Eul 是一款 SwiftUI & Combine 教程 App(iOS、macOS),以文章(文字、图片、代码)配合真机示例(Xcode 12+、iOS 14+,macOS 11+)的形式呈现给读者。笔者意在尽可能使用简洁明了的语言阐述 SwiftUI & Combine 相关的知识,使读者能快速掌握并在 iOS 开发中实践。
List
构建
基础构建方法
List 最基本的构建方法:
List {
Text("Sun")
Text("Cloud")
Text("Snow")
}
数据源
通常我们的列表并非静态,而是与动态的数据源绑定的。List 可以通过传入 data
来实现,不过这里的 data 需要遵循 Identifiable 协议,这样才能保证数据的唯一性。
我们先对天气作如下定义:
struct Weather: Identifiable {
let id = UUID()
let name: String
let icon: String
}
id
是协议要求实现的属性(遵循 Hashable 协议),我们也可以在实例初始化时传入 0, 1, 2, 3...只要是没有冲突的哈希值都可以。这里我们有个简单的处理方法,就是通过 UUID()
生成惟一值,在初始化时可以省去传参。
然后我们添加数据源:
@State private var weathers = [
Weather(name: "Sunshine", icon: "sun.max.fill"),
Weather(name: "Cloud", icon: "cloud"),
Weather(name: "Snow", icon: "snow"),
Weather(name: "Rain", icon: "cloud.rain.fill")
]
生成 List:
List(weathers) { v in
Label(v.name, systemImage: v.icon)
}
可展开列表
List 可以通过树形结构的数据源直接构建可展开的列表,比如我们定义如下可展开的天气对象:
struct ExpandWeather: Identifiable {
let id = UUID()
var name: String
var icon: String
var weathers: [ExpandWeather]?
}
该结构体内嵌的 weathers 的元素类型就是它本身,而且是可选类型。我们构造如下数据:
let expandWeather: [ExpandWeather] = [
ExpandWeather(name: "Weather", icon: "", weathers: [
ExpandWeather(name: "Sunshine", icon: "sun.max.fill"),
ExpandWeather(name: "Cloud", icon: "cloud"),
ExpandWeather(name: "Snow", icon: "snow"),
ExpandWeather(name: "Rain", icon: "cloud.rain.fill")
])
]
然后通过如下方法构建视图:
List(expandWeather, children: \.weathers) { weather in
Label(weather.name, systemImage: weather.icon)
}
最后得到的就是一个可以展开的列表,当然我们可以构造更加复杂的多级展开列表,其原理是一样的,都是在子数据中嵌套更多数据类型相同的子数据。
样式
List 有多种样式,系统为我们提供了多种 ListStyle。其中,iOS 比较常用的除了系统默认样式外,还有 GroupedListStyle 和 InsetGroupedListStyle。
ForEach
基本使用
ForEach 能通过循环从集合数据中快速构建视图。集合数据 data 可以是 Range<Int>
类型,也可以是元素遵循 Identifiable
协议的数组。
比如:
ForEach(0..<2) { idx in
Text("\(idx)")
}
或者,我们仍然以上文的 weathers 作为 data ,
ForEach(weathers) { weather in
Text(weather.name)
}
ForEach 里有个参数 id,它用来保证数据的唯一性。上例中我们并没有显示地调用它,因为对 Range 类型的 data 而言,其 id 默认值就是 Int 元素的值,而 weathers 中的 Weather 实例,我们已经给 id 默认赋值为 UUID() 了,ForEach 会通过 keyPath 取到其中的 id,所以我们也不必显示传参。
如果我们只是想通过一个普通的字符串数组去实现循环创建视图,那么 id 可以显示指定为 \.self
,即使用字符串本身作为 id 来保证数据的唯一性。
ForEach(["A", "B"], id: \.self) { str in
Text(str)
}
编辑操作
还记得我们前面提到过得 EditButton 吗?之前我们并没有具体讲解它的用法,现在我们可以见到它的具体使用了。
ForEach 和 List 配合使用,可以轻松地对列表进行编辑操作:delete、move。还是以 weathers 作为数据源,我们构建列表视图:
var body: some View {
List {
ForEach(weathers) { v in
Label(v.name, systemImage: v.icon)
}
.onDelete(perform: onDelete)
.onMove(perform: onMove)
}
.navigationBarItems(trailing: EditButton())
}
func onDelete(offsets: IndexSet) {
weathers.remove(atOffsets: offsets)
}
func onMove(fromOffsets: IndexSet, toOffset: Int) {
weathers.move(fromOffsets: fromOffsets, toOffset: toOffset)
}
EditButton 终于登场了,试试对列表进行编辑操作。
这里的编辑操作只是单选操作,如果我们要多选呢?可以在 List 中绑定 selection 数据,通过它对多条数据同时操作。这里就不举例了,读者可以按照这个思路去实现。
ScrollView
ScrollView 只有一个构建方法:
init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, content: () -> Content)
使用也简单,这里不再赘述。
ScrollViewReader
ScrollViewReader 的回调闭包返回的是一个 ScrollViewProxy 实例,它只有一个实例方法 scrollTo()
,该 方法能够使 ScrollView 滑动到指定的位置,对 List 也同样适用。
scrollTo()
还可以传入 anchor
参数控制更精确的滑动,比如示例所示为:sr.scrollTo(70, anchor: .center)
。
如果需要给滑动添加动画效果,只需要在 withAnimation 的闭包中调用 scrollTo()
即可。
iOS 开发,独立作品: ① Eul:SwiftUI & Combine 简明教程 ② FontsX:在任意 app 输入特殊字体