第 14 章:构建一个小型应用
联系人管理 (Contact Manager)
项目概述
在本实战项目中,我们将使用 SwiftData 构建一个完整的联系人管理应用。该应用将实现以下核心功能:
- 添加/编辑联系人信息(姓名、电话、邮箱、地址等)
- 按姓名或分组筛选联系人
- 收藏常用联系人
- 支持联系人照片存储
- 数据持久化与自动同步(若启用 CloudKit)
1. 数据模型设计
import SwiftData
import SwiftUI
@Model
final class Contact {
var id: UUID
var name: String
var phone: String?
var email: String?
var address: String?
var isFavorite: Bool
var createdAt: Date
var group: ContactGroup?
var photoData: Data?
init(name: String, phone: String? = nil, email: String? = nil) {
self.id = UUID()
self.name = name
self.phone = phone
self.email = email
self.isFavorite = false
self.createdAt = Date()
}
}
@Model
final class ContactGroup {
var name: String
@Relationship(deleteRule: .nullify) var contacts: [Contact]
init(name: String) {
self.name = name
self.contacts = []
}
}
2. 核心功能实现
2.1 联系人列表视图
struct ContactListView: View {
@Query(sort: \Contact.name) private var contacts: [Contact]
@Environment(\.modelContext) private var context
var body: some View {
List {
ForEach(contacts) { contact in
NavigationLink {
ContactDetailView(contact: contact)
} label: {
ContactRowView(contact: contact)
}
}
.onDelete(perform: deleteContacts)
}
.toolbar {
Button("Add", systemImage: "plus") {
context.insert(Contact(name: "New Contact"))
}
}
}
private func deleteContacts(_ indexSet: IndexSet) {
for index in indexSet {
context.delete(contacts[index])
}
}
}
2.2 联系人详情与编辑
struct ContactDetailView: View {
@Bindable var contact: Contact
@State private var isEditing = false
var body: some View {
Form {
if let photoData = contact.photoData, let image = UIImage(data: photoData) {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(height: 150)
}
Section("Basic Info") {
TextField("Name", text: $contact.name)
TextField("Phone", text: $contact.phone ?? "")
TextField("Email", text: $contact.email ?? "")
}
Section("Additional") {
TextField("Address", text: $contact.address ?? "")
Toggle("Favorite", isOn: $contact.isFavorite)
}
}
.navigationTitle(contact.name)
}
}
3. 高级功能实现
3.1 分组管理
struct ContactGroupView: View {
@Query(sort: \ContactGroup.name) var groups: [ContactGroup]
@Environment(\.modelContext) var context
var body: some View {
List {
ForEach(groups) { group in
NavigationLink(group.name) {
GroupContactListView(group: group)
}
}
.onDelete(perform: deleteGroups)
}
.toolbar {
Button("New Group") {
context.insert(ContactGroup(name: "New Group"))
}
}
}
private func deleteGroups(_ indexSet: IndexSet) {
indexSet.forEach { index in
context.delete(groups[index])
}
}
}
3.2 搜索与筛选
struct ContactSearchView: View {
@State private var searchText = ""
@State private var showFavoritesOnly = false
var body: some View {
NavigationStack {
ContactListView(
predicate: searchPredicate,
sortOrder: [SortDescriptor(\Contact.name)]
)
.searchable(text: $searchText)
.toolbar {
Toggle("Favorites Only", isOn: $showFavoritesOnly)
}
}
}
private var searchPredicate: Predicate<Contact>? {
var predicates: [Predicate<Contact>] = []
if !searchText.isEmpty {
predicates.append(
#Predicate { contact in
contact.name.localizedStandardContains(searchText) ||
contact.phone?.contains(searchText) == true ||
contact.email?.contains(searchText) == true
}
)
}
if showFavoritesOnly {
predicates.append(#Predicate { $0.isFavorite })
}
return predicates.isEmpty ? nil : predicates.reduce(Predicate { _ in true }) { partialResult, predicate in
partialResult && predicate
}
}
}
4. 数据持久化配置
@main
struct ContactManagerApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [Contact.self, ContactGroup.self])
}
}
5. 项目扩展建议
- 照片处理:添加相机/相册集成功能
- 云同步:启用 CloudKit 实现跨设备同步
- 导入/导出:支持 vCard 格式导入导出
- 快捷操作:添加主屏幕快捷操作
- Widgets:开发常用联系人小组件
6. 调试技巧
// 在应用启动时添加示例数据
struct ExampleDataViewModifier: ViewModifier {
@Environment(\.modelContext) private var context
func body(content: Content) -> some View {
content
.task {
guard try? context.fetch(FetchDescriptor<Contact>()).isEmpty else { return }
let groups = [
ContactGroup(name: "Family"),
ContactGroup(name: "Work"),
ContactGroup(name: "Friends")
]
let contacts = [
Contact(name: "John Appleseed", phone: "555-123-4567", email: "john@example.com"),
Contact(name: "Kate Bell", phone: "555-234-5678", email: "kate@example.com"),
Contact(name: "Anna Haro", phone: "555-345-6789", email: "anna@example.com")
]
contacts[0].group = groups[0]
contacts[1].group = groups[1]
contacts[2].group = groups[2]
groups.forEach { context.insert($0) }
contacts.forEach { context.insert($0) }
}
}
}
7. 项目结构建议
ContactManager/
├── Models/
│ ├── Contact.swift
│ └── ContactGroup.swift
├── Views/
│ ├── ContactListView.swift
│ ├── ContactDetailView.swift
│ ├── ContactRowView.swift
│ ├── ContactGroupView.swift
│ └── GroupContactListView.swift
├── Utilities/
│ ├── PredicateBuilder.swift
│ └── ExampleData.swift
└── ContactManagerApp.swift
通过这个完整的联系人管理项目,您将掌握 SwiftData 在实际应用中的综合运用,包括数据模型设计、关系管理、查询优化和 UI 集成等关键技能。
