上一章节 介绍了 SwiftUI 和 Combine 的结合使用, 本章将进一步扩展这个功能,深入探讨如何让用户与列表产生更多交互。
今天,我们将学习如何在 SwiftUI 中为列表添加左滑删除、长按触发上下文菜单和点击弹出 Action Sheet 等互动功能。
具体来说,您将学到:
- 左滑删除(Swipe-to-delete)
- 点击某一行弹出 Action Sheet
- 长按某一行呼出 Context Menu
其中,左滑删除和 Action Sheet 都是 iOS 多年来所熟悉的界面元素, 而从 iOS 13 开始,Apple 引入了类似 3D Touch “peek & pop”的 Context Menu 功能, 使得用户可以通过长按或 3D Touch(设备支持的情况下)来触发一个弹出菜单。 作为开发者,我们需要配置在这个菜单中出现的操作项。
提示:虽然本章主要演示在
List
中实现这些互动技巧,但类似的做法也可应用于其他 UI 控件(例如Button
等)。
准备 Starter Project
我们将基于一个简单的餐厅列表 App 来演示这些功能。
- 左滑删除功能
- Action Sheet
- Context Menu
基础项目代码如下:
import SwiftUI
struct ContentView: View {
var restaurants = [
Restaurant(name: "Cafe Deadend", image: "cafedeadend"),
Restaurant(name: "Homei", image: "homei"),
Restaurant(name: "Teakha", image: "teakha"),
Restaurant(name: "Cafe Loisl", image: "cafeloisl"),
Restaurant(name: "Petite Oyster", image: "petiteoyster"),
Restaurant(name: "For Kee Restaurant", image: "forkeerestaurant"),
Restaurant(name: "Po's Atelier", image: "posatelier"),
Restaurant(name: "Bourke Street Bakery", image: "bourkestreetbakery"),
Restaurant(name: "Haigh's Chocolate", image: "haighschocolate"),
Restaurant(name: "Palomino Espresso", image: "palominoespresso"),
Restaurant(name: "Homei", image: "upstate"),
Restaurant(name: "Traif", image: "traif"),
Restaurant(name: "Graham Avenue Meats And Deli", image: "grahamavenuemeats"),
Restaurant(name: "Waffle & Wolf", image: "wafflewolf"),
Restaurant(name: "Five Leaves", image: "fiveleaves"),
Restaurant(name: "Cafe Lore", image: "cafelore"),
Restaurant(name: "Confessional", image: "confessional"),
Restaurant(name: "Barrafina", image: "barrafina"),
Restaurant(name: "Donostia", image: "donostia"),
Restaurant(name: "Royal Oak", image: "royaloak"),
Restaurant(name: "CASK Pub and Kitchen", image: "caskpubkitchen")
]
var body: some View {
List {
ForEach(restaurants) { restaurant in
BasicImageRow(restaurant: restaurant)
}
}
.listStyle(.plain)
}
}
#Preview {
ContentView()
}
struct Restaurant: Identifiable {
var id = UUID()
var name: String
var image: String
}
struct BasicImageRow: View {
var restaurant: Restaurant
var body: some View {
HStack {
Image(restaurant.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(restaurant.name)
}
}
}
实现 Swipe-to-Delete
确保你已经打开了示例项目后,让我们先从左滑删除功能入手。
在 SwiftUI 中,只需要对数据源所在的 ForEach
添加 onDelete
处理器,
即可为列表所有行启用左滑删除。
示例如下:
List {
ForEach(restaurants) { restaurant in
BasicImageRow(restaurant: restaurant)
}
.onDelete { (indexSet) in
self.restaurants.remove(atOffsets: indexSet)
}
}
.listStyle(.plain)
在 onDelete
闭包中,我们通过传入的 indexSet
(它记录了要删除的行的索引)
来调用 remove(atOffsets:)
,从 restaurants
数组中移除对应的项目。
为了让删除操作后 UI 自动更新,需要借助 @State
来修饰存储列表数据的变量,
以便 SwiftUI 能监听到数据变化并自动刷新界面。
示例项目中,你可以在 ContentView
里将 restaurants
定义成这样:
@State var restaurants = [ ... ]
编译并运行后,就可以体验到左滑删除了。往左一划,就能看到 Delete
按钮。
点击后,对应那一行就会带着一个漂亮的动画消失,这个动画是 SwiftUI 自动生成的,
无需任何额外的动画代码。
如果你曾用 UIKit 来实现过这个功能,就会对 SwiftUI 的简洁深有体会: 仅需几行代码就能搞定左滑删除。
创建 Context Menu
接下来,我们来看看 Context Menu