SwiftUI通过子视图完成对SwiftData对象的内容修改。
具体表现为:在Swift中实现在主视图中点击NavigationLink链接,跳转并传递SwiftData对象到内容编辑视图,内容编辑视图修改SwiftData对象后,保存并完成对SwiftData对象的修改。
主视图部分
在List中遍历每一个SwiftData对象,List中设置NavigationLink跳转到EditView视图:
@Environment(\.modelContext) var modelContext
@Query(sort: \Prospect.name) var prospects: [Prospect]
List(prospects, selection: $selectedProspects) { prospect in
NavigationLink(destination: EditView(prosect:prospect)) {
VStack(alignment: .leading) {
Text(prospect.name)
.font(.headline)
Text(prospect.emailAddress)
.foregroundStyle(.secondary)
}
}
}
当点击List中的SwiftData对象信息时,就会将点击的SwiftData对象传递给EditView视图,同时跳转到EditView视图。
子视图部分
EditView视图需要引入SwiftData和modelContext:
import SwiftData
struct EditView: View {
@Environment(\.modelContext) private var modelContext // 获取模型上下文
...
}
设置接收的SwiftData对象:
var prospect: Prospect
因此需要修改SwiftData对象的内容,因此需要对应的文本编辑器:
TextField("Name", text: $name)
.textContentType(.name)
.font(.title)
TextField("Email Address", text: $emailAddress)
.textContentType(.emailAddress)
.font(.title)
TextField需要绑定@State或@Binding属性包装器,因为传入的SwiftData无法使用这类属性包装器进行包装,所以创建SwiftData对象对应的属性变量,通过@State属性包装器可以实现对属性变量的修改。
因为Prospect对象只有name和eamilAddress属性,因此添加两个变量绑定到TextField中:
@State private var name: String
@State private var emailAddress: String
这两个属性的初始化工作则由EditView视图完成:
init(prosect: Prospect) {
self.prospect = prosect
_name = State(initialValue: prosect.name)
_emailAddress = State(initialValue: prosect.emailAddress)
}
现在,从主视图传递过来的SwiftData对象会保存到EditView视图的prospect变量上,同时SwiftData对象的属性也会赋值给EditView的name和emailAddress变量。
在视图中展示编辑框:
var body: some View {
Form {
TextField("Name", text: $name)
.textContentType(.name)
.font(.title)
TextField("Email Address", text: $emailAddress)
.textContentType(.emailAddress)
.font(.title)
}
.navigationTitle("Edit Prospect")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
saveChanges()
}
}
}
}
当修改对应的name和emailAddress字段后,将name和emailAddress赋值给prospect,然后调用modelContext进行保存:
private func saveChanges() {
prospect.name = name
prospect.emailAddress = emailAddress
try? modelContext.save() // 保存到 SwiftData 的上下文中
}
最后,实现效果为:通过子视图修改SwiftData对象。
完整代码
主视图代码
//
// ProspectsView.swift
// hackingwithswift
//
// Created by 方君宇 on 2024/12/10.
//
import SwiftUI
import SwiftData
import CodeScanner
import UserNotifications
struct ProspectsView: View {
@Query(sort: \Prospect.name) var prospects: [Prospect]
@Environment(\.modelContext) var modelContext
@State private var isShowingScanner = false
@State private var selectedProspects = Set<Prospect>()
@State private var hideTips = false
let filter: FilterType
init(filter: FilterType) {
self.filter = filter
if filter != .none {
let showContactedOnly = filter == .contacted
_prospects = Query(filter: #Predicate {
$0.isContacted == showContactedOnly
}, sort: [SortDescriptor(\Prospect.name)])
}
}
func handleScan(result: Result<ScanResult, ScanError>) {
isShowingScanner = false
switch result {
case .success(let result):
let details = result.string.components(separatedBy: "\n")
guard details.count == 2 else { return }
let person = Prospect(name: details[0], emailAddress: details[1], isContacted: false)
modelContext.insert(person)
case .failure(let error):
print("Scanning failed: \(error.localizedDescription)")
}
}
func delete() {
for prospect in selectedProspects {
modelContext.delete(prospect)
}
selectedProspects=[]
}
func addNotification(for prospect: Prospect) {
let center = UNUserNotificationCenter.current()
let addRequest = {
let content = UNMutableNotificationContent()
content.title = "Contact \(prospect.name)"
content.subtitle = prospect.emailAddress
content.sound = UNNotificationSound.default
var dateComponents = DateComponents()
dateComponents.hour = 9
// let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
center.add(request)
}
center.getNotificationSettings { settings in
if settings.authorizationStatus == .authorized {
addRequest()
} else {
center.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
addRequest()
} else if let error {
print(error.localizedDescription)
}
}
}
}
}
var title: String {
switch filter {
case .none:
"Everyone"
case .contacted:
"Contacted people"
case .uncontacted:
"Uncontacted people"
}
}
var body: some View {
NavigationStack {
if filter == .none,hideTips == false {
VStack {
HStack {
Spacer()
Image(systemName: "minus.square.fill")
.onTapGesture {
hideTips = true
}
}
Image(systemName: "lasso.badge.sparkles")
.font(.title)
Spacer().frame(height: 10)
Text("是否联系了潜在客户")
}
.foregroundColor(Color.white)
.frame(width: 200,height: 100)
.background(Color.blue)
.cornerRadius(10)
}
List(prospects, selection: $selectedProspects) { prospect in
NavigationLink(destination: EditView(prosect:prospect)) {
VStack(alignment: .leading) {
Text(prospect.name)
.font(.headline)
Text(prospect.emailAddress)
.foregroundStyle(.secondary)
}
}
.tag(prospect)
.swipeActions {
if prospect.isContacted {
Button("Mark Uncontacted", systemImage: "person.crop.circle.badge.xmark") {
prospect.isContacted.toggle()
}
.tint(.blue)
} else {
Button("Mark Contacted", systemImage: "person.crop.circle.fill.badge.checkmark") {
prospect.isContacted.toggle()
}
.tint(.green)
}
Button("Delete", systemImage: "trash", role: .destructive) {
modelContext.delete(prospect)
}
Button("Remind Me", systemImage: "bell") {
addNotification(for: prospect)
}
.tint(.orange)
}
}
.navigationTitle(title)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Scan", systemImage: "qrcode.viewfinder") {
isShowingScanner = true
}
}
ToolbarItem(placement: .topBarLeading) {
EditButton()
}
if selectedProspects.isEmpty == false {
ToolbarItem(placement: .bottomBar) {
Button("Delete Selected", action: delete)
}
}
}
}
.sheet(isPresented: $isShowingScanner) {
CodeScannerView(codeTypes: [.qr], simulatedData: "Paul Hudson\npaul@hackingwithswift.com", completion: handleScan)
}
}
}
#Preview {
ProspectsView(filter: .none)
.modelContainer(for: Prospect.self)
}
子视图代码
//
// EditView.swift
// hackingwithswift
//
// Created by 方君宇 on 2024/12/11.
//
import SwiftUI
import SwiftData
struct EditView: View {
@Environment(\.modelContext) private var modelContext // 获取模型上下文
@State private var name: String
@State private var emailAddress: String
var prospect: Prospect
init(prosect: Prospect) {
self.prospect = prosect
_name = State(initialValue: prosect.name)
_emailAddress = State(initialValue: prosect.emailAddress)
}
var body: some View {
Form {
TextField("Name", text: $name)
.textContentType(.name)
.font(.title)
TextField("Email Address", text: $emailAddress)
.textContentType(.emailAddress)
.font(.title)
}
.navigationTitle("Edit Prospect")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
saveChanges()
}
}
}
}
private func saveChanges() {
prospect.name = name
prospect.emailAddress = emailAddress
try? modelContext.save() // 保存到 SwiftData 的上下文中
}
}
#Preview {
do {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: Prospect.self, configurations: config)
let prosect = Prospect(name: "a", emailAddress: "a.com", isContacted: true)
return EditView(prosect: prosect)
.modelContainer(container)
} catch {
return Text("Failed to create container: \(error.localizedDescription)")
}
}