SwiftUI通过子视图修改SwiftData对象
SwiftUI通过子视图修改SwiftData对象

SwiftUI通过子视图修改SwiftData对象

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)")
    }
}

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注