【问题标题】:How to create subcollections with Firestore in SwiftUI如何在 SwiftUI 中使用 Firestore 创建子集合
【发布时间】: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)
    }

}



【问题讨论】:

  • 我不清楚,看完这个,具体问题是什么。
  • @jnpdx 很抱歉不够清晰。如何使用 firestore 和 swiftUI 在我的供应商上创建品牌子集?
  • 有很多代码供我们解析。请花点时间查看How to create a Minimal, Complete, and Verifiable example。就子集合而言,你为什么需要一个子集合——为什么不只是一个集合?供应商有品牌,但许多供应商都有品牌,那么为什么不让品牌成为顶级系列呢?模型听起来也很高级。雪佛兰品牌有汽车模型(文件)。但是这些模型的颜色和大小听起来像是每个模型的属性。你能缩短并澄清问题吗?
  • @Jay 我想创建一个子集合,以便能够创建一个供应商,该供应商的品牌具有颜色、尺寸,而这些品牌又与发票编号相关联。因此,通过阅读 firestore 中的子系列,我发现为供应商创建一个系列,然后为具有型号、颜色、尺寸等的品牌创建一个子系列是最好的方法。你是说收集足以涵盖所有这些吗?如果是这样,您是否有可以帮助我解决此问题的文档?对于我的问题不够明确,我深表歉意。

标签: swift firebase google-cloud-firestore swiftui


【解决方案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 我的回复有点晚了,但这正是我所需要的。非常感谢!
猜你喜欢
  • 2018-08-06
  • 2019-02-19
  • 1970-01-01
  • 1970-01-01
  • 2021-02-20
  • 2020-04-07
  • 1970-01-01
  • 2021-11-14
  • 1970-01-01
相关资源
最近更新 更多