我目前正在为位于Cloud Firestore中的服务文档集合添加更新功能。然而,该功能目前正在失败。
来自控制台的错误消息:
"更新服务项失败,错误:No document to update: projects/myservices-ec91e/databases/(默认)/documents/services/5231A3BD-63A4-4AA4-A4B3-81364A2DACDF">
这是我的数据模型:
import Foundation
import FirebaseFirestoreSwift
struct Service: Identifiable {
@DocumentID var id = UUID().uuidString
var name: String
var price: Double
}
这是我的ObservableObject上的函数实现:
// Variables for displaying Service objects
@Published var name = ""
@Published var price: Double = 0
// Properties for error handling
@Published var failedToUpdate = false
@MainActor func updateService(id: DocumentReference) async throws {
let db = Firestore.firestore()
// Get the document ID string from the DocumentReference object
let documentID = id.documentID
// Update the document with the given ID and fields
try await db.collection("services").document(documentID).updateData([
"name": name,
"price": price
])
}
这是函数实现位置的完整视图:
struct EditServiceView: View {
@EnvironmentObject var serviceModel: ServiceModel
@Environment(.dismiss) var dismiss
let service: Service
var body: some View {
NavigationStack {
Form {
Section("Name") {
TextField("", text: $serviceModel.name)
}
Section("Price") {
VStack {
Text("$(String(format: "%.0f", serviceModel.price))")
.fontWeight(.bold)
Slider(value: $serviceModel.price,
in: 0...500, step: 1)
}
}
}
.toolbar {
// Update button
ToolbarItem(placement: .navigationBarTrailing) {
Button("Update") {
Task {
if let serviceID = service.id {
let serviceRef = Firestore.firestore().collection("services").document(serviceID)
do {
// Update the service item in the database
try await serviceModel.updateService(id: serviceRef)
} catch {
print("Failed to update service item with error: (error.localizedDescription)")
// Reset fields anf show alert
serviceModel.resetFields()
serviceModel.failedToUpdate = true
}
// Dismiss the view
dismiss()
}
}
}
.alert("Failed to Update", isPresented: $serviceModel.failedToUpdate) {
Button("Ok") { }
} message: {
Text("Unable to update. Please try again later.")
}
.disabled(serviceModel.disableSaveButton)
}
}
.navigationTitle("Edit Service")
}
}
}
最后,使用ForEach将视图传递给List:
struct ServicesMainView: View {
@EnvironmentObject var serviceModel: ServiceModel
var body: some View {
NavigationStack {
List {
ForEach(serviceModel.services) { service in
NavigationLink(
destination: EditServiceView(service: service),
label: {
HStack {
Text(service.name)
Spacer()
Text("$(String(format: "%.0f", service.price))")
}
})
}
}
.navigationTitle("Services")
.task {
do {
try await serviceModel.listenForAllServices()
} catch {
serviceModel.failedToListenToServices = true
}
}
// Stop listening for data from ("services") collection.
.onDisappear {
serviceModel.listener?.remove()
print("Has stopped listening for data from database.")
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
serviceModel.showingAddView.toggle()
}, label: {
Text("Add")
})
}
}
.sheet(isPresented: $serviceModel.showingAddView) {
AddServicesView()
}
}
.alert("Unable to Fetch Data", isPresented: $serviceModel.failedToListenToServices) {
Button("Ok") { }
} message: {
Text("Failed to reach server. Please try again later.")
}
}
}
给定控制台上的错误消息,我认为我没有正确引用数据库上的对象。我点击的对象在数据库中确实存在。
更详细地说明:这是数据库上的对象被引用的方式,以及函数被调用的位置:
.toolbar {
// Update button
ToolbarItem(placement: .navigationBarTrailing) {
Button("Update") {
Task {
if let serviceID = service.id {
let serviceRef = Firestore.firestore().collection("services").document(serviceID)
do {
// Update the service item in the database
try await serviceModel.updateService(id: serviceRef)
} catch {
print("Failed to update service item with error: (error.localizedDescription)")
// Reset fields anf show alert
serviceModel.resetFields()
serviceModel.failedToUpdate = true
}
// Dismiss the view
dismiss()
}
}
}
.alert("Failed to Update", isPresented: $serviceModel.failedToUpdate) {
Button("Ok") { }
} message: {
Text("Unable to update. Please try again later.")
}
.disabled(serviceModel.disableSaveButton)
}
}
我是这样在DB上创建Service对象的:
// This function creates a new service document in the "services" collection in Firestore.
func createNewService() async throws {
// Create an instance of the Firestore database object.
let db = Firestore.firestore()
// Add a new document to the "services" collection with the given name and price values.
// Use the "await" keyword to wait for the completion of the addDocument method call.
_ = try await db.collection("services").addDocument(data: [
"name": name, // The name property of the new service.
"price": price // The price property of the new service.
])
}
我的另一个假设是,我使用UUID字符串作为Service模型的本地id。我在本地使用UUID的原因是因为forEach对我大喊大叫,我没有。
你可以使用swift的Codable
可编码和可解码协议的类型别名。通过使Swift类型符合此协议,编译器将从序列化格式(如JSON)合成编码/解码该类型实例所需的代码。
使用codable你的结构将变成:
import FirebaseFirestoreSwift
struct Service: Codable {
@DocumentID var id: String?
var name: String
var price: Double
}
使用codable将帮助您将您的firestore文档转换为严格类型的结构体,并且在实现此更改之后,您还需要更新您的updateService方法,如下所示:
@MainActor func updateService(service: Service) async throws {
let db = Firestore.firestore()
guard let documentID = service.id else {
throw FirestoreError.invalidDocumentID
}
try await db.collection("services").document(documentID).setData(from: service)
}
我们将触发updateService方法,就像你所做的那样,但现在也传递了服务结构体:
Button("Update") {
Task {
do {
if let serviceID = service.id {
// Update the service item in the database
let updatedService = Service(id: serviceID, name: serviceModel.name, price: serviceModel.price)
try await serviceModel.updateService(service: updatedService)
}
// Dismiss the view
dismiss()
} catch {
print("Failed to update service item with error: (error.localizedDescription)")
// Reset fields and show alert
serviceModel.resetFields()
serviceModel.failedToUpdate = true
}
}
}
通过这种方式,您不必使用UUID包,因为我们的文档id是由firestore id生成自动处理的。
虽然codable是新添加到firebase文档,你可以在这里探索更多,它也很好地解释了著名的firebase Frank van Puffelen在youtube视频和详细信息在这里
FirebaseFirestoreSwift
的一个好处是你可以在模型本身中使用引用
import FirebaseFirestoreSwift
struct Service: Identifiable {
var id: String{
ref!.documentID //If you get a nil here it is because the reference it missing, aka you haven't gone through Firebase yet
}
@DocumentID private var ref: DocumentReference?
var name: String
var price: Double
}
为了避免使用nil
引用的问题,最好在创建时直接从Firebase获取新文档。
let newService = try await db.collection("services").addDocument(from: Service(name: "", price: 0)).getDocument().data(as: Service.self) //Valid reference
而不是直接使用
let newService = Service(name: "", price: 0) //nil reference
如果设置正确,你可以添加一个save
方法到对象
struct Service: Identifiable {
var id: String{
ref!.documentID //This will cause a crash if you don't use `getDocument` before SwiftUI Views use it.
}
@DocumentID private var ref: DocumentReference?
var name: String
var price: Double
func save() throws{
if let ref = ref{
try ref.setData(from: self)
}else{
print("Error: Must `addDocument` and/or `getDocument()` to get a valid reference")
}
}
}
Add then call
try service.save()
任何你想保存的地方
但是为了在保存时更有效率你可以创建如果引用是nil
struct Service: Identifiable {
var id: String?{
ref?.documentID //Will be `nil` until `save` is called.
}
@DocumentID private var ref: DocumentReference?
var name: String
var price: Double
mutating func save() throws{
if let ref = ref{
try ref.setData(from: self)
}else{
self.ref = try Firestore.firestore().collection("services").addDocument(from: self)
}
}
}
那么你的添加流看起来就像
var new = Service(name: "", price: 0) //nil reference
try new.save() //add the reference
serviceModel.services.append(new) //then add to your array
如果你想保留你的updateService
方法,它看起来像。
func updateService(service: Service) throws {
try service.save()
}