如何在 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 
    

如果您希望在此之后更新您的视图,只需将此函数包装在一个名为 SomeViewModelstruct 中(当然,“Some”被替换为您想要的任何内容)。之后让它向集合添加一个快照侦听器并更新@Published 属性。现在,这将使您的视图对新创建的集合做出反应。

如果这不是您想要的,请在 cmets 中告诉我,我会更好地回答您的问题。

【讨论】:

@rayaataneja 我的回复有点晚了,但这正是我所需要的。非常感谢!

以上是关于如何在 SwiftUI 中使用 Firestore 创建子集合的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI+Firestore 中的多选列表

如何使用 SwiftUI 中的 documentID 删除特定的 Cloud Firestore 文档?

如何从firestore中检索swiftUI中的预定义项目列表?

SwiftUI:如何阅读 Firestore 中的特定文档?

无法使用 SwiftUI 从 Firebase / Firestore 中删除文档

如何通过 Desc SwiftUI 对 Cloud Firestore 数据进行排序