如何在 SwiftUI 中使用 Firestore 创建子集合
Posted
技术标签:
【中文标题】如何在 SwiftUI 中使用 Firestore 创建子集合【英文标题】:How to create subcollections with Firestore in SwiftUI 【发布时间】:2021-07-15 04:07:10 【问题描述】:尽管我了解子集合和 SwiftUI 背后的理论,但我很难将其应用于代码。这个想法是创建一个供应商,该供应商拥有品牌,而品牌又拥有型号、颜色、尺寸等。我正在创建一个患者/库存管理应用程序,我复制了患者代码(我从 @ 那里得到了很多帮助peterfriese)到库存之一,只要我需要更改几行以使子集合能够自行创建,但我完全感到困惑。我在将代码应用到视图中时遇到的困难与对 viewModel 一样。出于某种原因,我也遇到了为我的供应商保存编辑的问题,它只是添加了一个条目而不是编辑它。这是我的代码。
供应商列表视图
import SwiftUI
struct SupplierListView: View
@ObservedObject private var viewModel = SupplierViewModel()
@State private var presentAddNewSupplierScreen = false
private var addButton: some View
Button(action: self.presentAddNewSupplierScreen.toggle() )
Image(systemName: "plus")
private func supplierRowView(supplier: SupplierModel) -> some View
NavigationLink(destination: SupplierDetailView(supplier: supplier))
VStack(alignment: .leading)
Text(supplier.supplier ?? "")
.font(.headline)
var body: some View
NavigationView
List
ForEach (viewModel.suppliers) supplier in
supplierRowView(supplier: supplier)
.onDelete() indexSet in
viewModel.removeSuppliers(atOffsets: indexSet)
.navigationBarTitle("Suppliers")
.navigationBarItems(trailing: addButton)
.onAppear()
print("SupplierListView appears. Subscribing to data updates.")
self.viewModel.subscribe()
.onDisappear()
// By unsubscribing from the view model, we prevent updates coming in from
// Firestore to be reflected in the UI. Since we do want to receive updates
// when the user is on any of the child screens, we keep the subscription active!
//
// print("BooksListView disappears. Unsubscribing from data updates.")
// self.viewModel.unsubscribe()
.sheet(isPresented: self.$presentAddNewSupplierScreen)
SupplierEditView()
struct SupplierListView_Previews: PreviewProvider
static var previews: some View
SupplierListView()
供应商编辑视图
import SwiftUI
enum ModeSupplier
case new
case edit
enum ActionSupplier
case delete
case done
case cancel
extension Optional where Wrapped == String
var _boundSupplier: String?
get
return self
set
self = newValue
public var boundSupplier: String
get
return _boundSupplier ?? ""
set
_boundSupplier = newValue.isEmpty ? nil : newValue
struct SupplierEditView: View
@Environment(\.presentationMode) var presentationMode
@State var presentActionSheet = false
@ObservedObject var viewModel = SupplierDetailViewModel()
var mode: Mode = .new
var completionHandler: ((Result<Action, Error>) -> Void)?
var cancelButton: some View
Button(action: self.handleCancelTapped() )
Text("Cancel")
var saveButton: some View
Button(action: self.handleDoneTapped() )
Text(mode == .new ? "Done" : "Save")
.disabled(!viewModel.modified)
var body: some View
NavigationView
Form
Section(header: Text("Supplier Name"))
TextField("Supplier Name", text: $viewModel.supplier.supplier.bound)
if mode == .edit
Section
Button("Delete Supplier") self.presentActionSheet.toggle()
.foregroundColor(.red)
.navigationBarTitle("New Supplier", displayMode: .inline)
.navigationBarTitleDisplayMode(mode == .new ? .inline : .large)
.navigationBarItems(
leading: cancelButton,
trailing: saveButton
)
.actionSheet(isPresented: $presentActionSheet)
ActionSheet(title: Text("Are you sure?"),
buttons: [
.destructive(Text("Delete Supplier"),
action: self.handleDeleteTapped() ),
.cancel()
])
func handleCancelTapped()
dismiss()
func handleDoneTapped()
viewModel.saveSupplier()
dismiss()
func handleDeleteTapped()
viewModel.handleDeleteTapped()
self.dismiss()
self.completionHandler?(.success(.delete))
func dismiss()
presentationMode.wrappedValue.dismiss()
struct SupplierEditView_Previews: PreviewProvider
static var previews: some View
let supplier = SupplierModel(id: "", supplier: "")
let supplierViewModel = SupplierDetailViewModel(supplier: supplier)
return SupplierEditView(viewModel: supplierViewModel, mode: .edit)
供应商详情视图
import SwiftUI
struct SupplierDetailView: View
@Environment(\.presentationMode) var presentationMode
@State var presentEditSupplierSheet = false
var supplier: SupplierModel
private func editButton(action: @escaping () -> Void) -> some View
Button(action: action() )
Text("Edit")
var body: some View
Form
Section(header: Text("Supplier Name"))
Text(supplier.supplier ?? "")
.navigationBarTitle(supplier.supplier ?? "")
.navigationBarItems(trailing: editButton
self.presentEditSupplierSheet.toggle()
)
.onAppear()
print("SupplierDetailView.onAppear() for \(self.supplier.supplier)")
.onDisappear()
print("SupplierDetailView.onDisappear()")
.sheet(isPresented: self.$presentEditSupplierSheet)
SupplierEditView(viewModel: SupplierDetailViewModel(supplier: supplier), mode: .edit) result in
if case .success(let action) = result, action == .delete
self.presentationMode.wrappedValue.dismiss()
struct SupplierDetailView_Previews: PreviewProvider
static var previews: some View
let supplier = SupplierModel(id: "", supplier: "")
return
NavigationView
SupplierDetailView(supplier: supplier)
供应商模型
import Foundation
import FirebaseFirestoreSwift
struct SupplierModel: Identifiable, Codable
@DocumentID var id : String? = UUID().uuidString
var supplier: String?
供应商模型视图
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
class SupplierViewModel: ObservableObject
@Published var suppliers = [SupplierModel]()
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
deinit
unsubscribe()
func unsubscribe()
if listenerRegistration != nil
listenerRegistration?.remove()
listenerRegistration = nil
func subscribe()
if listenerRegistration == nil
listenerRegistration = db.collection("suppliers").addSnapshotListener (querySnapshot, error) in
guard let documents = querySnapshot?.documents else
print("No suppliers")
return
self.suppliers = documents.compactMap queryDocumentSnapshot -> SupplierModel? in
return try? queryDocumentSnapshot.data(as: SupplierModel.self)
func removeSuppliers(atOffsets indexSet: IndexSet)
let suppliers = indexSet.lazy.map self.suppliers[$0]
suppliers.forEach supplier in
if let documentId = supplier.id
db.collection("suppliers").document(documentId).delete error in
if let error = error
print("Unable to remove document: \(error.localizedDescription)")
SupplierDetailModelView
import Foundation
import Firebase
import Combine
class SupplierDetailViewModel: ObservableObject
@Published var supplier: SupplierModel
@Published var modified = false
private var db = Firestore.firestore()
private var cancellables = Set<AnyCancellable>()
init(supplier: SupplierModel = SupplierModel(id: "", supplier: ""))
self.supplier = supplier
self.$supplier
.dropFirst()
.sink [weak self] supplier in
self?.modified = true
.store(in: &cancellables)
func addSupplier(supplier: SupplierModel)
do
let _ = try db.collection("suppliers").addDocument(from: supplier)
catch
print(error)
private func updateSupplier(_ supplier: SupplierModel)
if let documentId = supplier.id
do
try db.collection("suppliers").document(documentId).setData(from: supplier)
catch
print(error)
private func updateOrAddSupplier()
if let _ = supplier.id
self.updateSupplier(self.supplier)
else
addSupplier(supplier: supplier)
private func removeSupplier()
if let documentId = supplier.id
db.collection("supplier").document(documentId).delete error in
if let error = error
print(error.localizedDescription)
func handleDoneTapped()
self.updateOrAddSupplier()
func handleDeleteTapped()
self.removeSupplier()
func saveSupplier()
addSupplier(supplier: supplier)
【问题讨论】:
我不清楚,看完这个,具体问题是什么。 也许对***.com/questions/63088521/…有帮助 @jnpdx 很抱歉不够清晰。如何使用 firestore 和 swiftUI 在我的供应商上创建品牌子集? 有很多代码供我们解析。请花点时间查看How to create a Minimal, Complete, and Verifiable example。就子集合而言,你为什么需要一个子集合——为什么不只是一个集合?供应商有品牌,但许多供应商都有品牌,那么为什么不让品牌成为***系列呢?模型听起来也很高级。雪佛兰品牌有汽车模型(文件)。但是这些模型的颜色和大小听起来像是每个模型的属性。你能缩短并澄清问题吗? @Jay 我想创建一个子集合,以便能够创建一个供应商,该供应商的品牌具有颜色、尺寸,而这些品牌又与发票编号相关联。因此,通过阅读 firestore 中的子系列,我发现为供应商创建一个系列,然后为具有型号、颜色、尺寸等的品牌创建一个子系列是最好的方法。你是说收集足以涵盖所有这些吗?如果是这样,您是否有可以帮助我解决此问题的文档?对于我的问题不够明确,我深表歉意。 【参考方案1】:我没有阅读您的任何代码,因为它实在是太繁琐了,但是如果您想在文档中创建一个子集合,您可以这样做:
func addBrandToSupplier(brand: Brand, supplier: Supplier)
let db = Firestore.firestore()
let newDocumentReference = db.collection("suppliers")
.document(supplier.id)
.collection("brands")
.document()
// If you just put .collection("xyz") after a document Firestore creates a new collection within that document for you.
// Now I populate that collection with a document to officially create it assuming Brand and Supplier conform to Codable
try? newDocumentReference.setData(from: brand) error in
if error != nil // Error handling code
如果您希望在此之后更新您的视图,只需将此函数包装在一个名为 SomeViewModel
的 struct
中(当然,“Some”被替换为您想要的任何内容)。之后让它向集合添加一个快照侦听器并更新@Published
属性。现在,这将使您的视图对新创建的集合做出反应。
如果这不是您想要的,请在 cmets 中告诉我,我会更好地回答您的问题。
【讨论】:
@rayaataneja 我的回复有点晚了,但这正是我所需要的。非常感谢!以上是关于如何在 SwiftUI 中使用 Firestore 创建子集合的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 SwiftUI 中的 documentID 删除特定的 Cloud Firestore 文档?
如何从firestore中检索swiftUI中的预定义项目列表?
SwiftUI:如何阅读 Firestore 中的特定文档?