ios – How to upload an image to Firebase with other data through a special form with a single button

0
181


First of all ofc before posting I searched in Google for information related to this issue, but in my particular situation I did not find anything, except for a simple upload of images to Storage.

I have a simple app to search some Items from database, every user can upload some data to database (Firebase) via special form on home screen.

I need the user to be able to add a picture along in addition to text and keep it as one, so that when searching for this item, the name and description are displayed along with the image.
I guess need another field in collection, like “pictureId” (of course, first I need to upload this image along with the text to the database through a form on the click of a button) to call it from Storage everywhere I want?
(in this case, when searching).

In Firebase database I have only one collection “Items” with fields “title“, “userId“, “createdTime“, “description“, “author“.

Form to upload data:

enum Mode {
  case new
  case edit
}

enum Action {
  case delete
  case done
  case cancel
}

struct AddItemView: View {
@Environment(\.presentationMode) private var presentationMode
@State var presentActionSheet = false

// MARK: - State (Initialiser-modifiable)

@ObservedObject var viewModel = NewItemView()
var mode: Mode = .new
var completionHandler: ((Result<Action, Error>) -> Void)?

// MARK: - UI Components

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("New Item")) {
        TextField("Title", text: $viewModel.signleitem.title)
        TextField("Description", text: $viewModel.signleitem.description)
      }
      
      Section(header: Text("Author")) {
        TextField("Author", text: $viewModel.signleitem.author)
      }
      
      if mode == .edit {
        Section {
          Button("Delete item") { self.presentActionSheet.toggle() }
            .foregroundColor(.red)
        }
      }
    }
    .navigationTitle(mode == .new ? "Your Item" : viewModel.signleitem.title)
    .navigationBarTitleDisplayMode(mode == .new ? .inline : .large)
    .navigationBarItems(
      leading: cancelButton,
      trailing: saveButton
    )
    .actionSheet(isPresented: $presentActionSheet) {
      ActionSheet(title: Text("Are you sure?"),
                  buttons: [
                    .destructive(Text("Delete item"),
                                 action: { self.handleDeleteTapped() }),
                    .cancel()
                  ])
    }
  }
}

// MARK: - Action Handlers

func handleCancelTapped() {
  self.dismiss()
}

func handleDoneTapped() {
  self.viewModel.handleDoneTapped()
  self.dismiss()
}

func handleDeleteTapped() {
  viewModel.handleDeleteTapped()
  self.dismiss()
  self.completionHandler?(.success(.delete))
}

func dismiss() {
  self.presentationMode.wrappedValue.dismiss()
}
}


struct AddItemView_Previews: PreviewProvider {
  static var previews: some View {
      _ = SingleItem(title: "", author: "", description: "")
    return
      NavigationView {
        AddItemView() 
      }
  }
}

extension View {
  func endTextEditing() {
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                    to: nil, from: nil, for: nil)
  }
}

Firebase stuff:

class NewItemView: ObservableObject {
  // MARK: - Public properties
  
  @Published var signleitem: SingleItem
  @Published var modified = false
  
  // MARK: - Internal properties
  
  private var cancellables = Set<AnyCancellable>()
  
  // MARK: - Constructors
  
  init(signleitem: SingleItem = SingleItem(title: "", author: "", description: "")) {
    self.signleitem = signleitem
    
      self.$signleitem
      .dropFirst()
      .sink { [weak self] signleitem in
        self?.modified = true
      }
      .store(in: &self.cancellables)
  }
  
  // MARK: - Firestore
  
  private var db = Firestore.firestore()
  
  private func addItem(_ signleitem: SingleItem) {
    do {
        var addedItem = signleitem
        addedItem.userId = Auth.auth().currentUser?.uid
        _ = try db.collection("items").addDocument(from: addedItem)
    }
    catch {
      print(error)
    }
  }
  
  private func updateItem(_ signleitem: SingleItem) {
    if let documentID = signleitem.id {
      do {
          try db.collection("items").document(documentID).setData(from: signleitem)
      }
      catch {
        print(error)
      }
    }
  }
  
  private func updateOrAddItem() {
      if signleitem.id != nil {
      self.updateItem(self.signleitem)
    }
    else {
      addItem(signleitem)
    }
  }
  
  private func removeItem() {
    if let documentID = signleitem.id {
      db.collection("items").document(documentID).delete { error in
        if let error = error {
          print(error.localizedDescription)
        }
      }
    }
  }
  
  // MARK: - UI handlers
  
  func handleDoneTapped() {
    self.updateOrAddItem()
  }
  
  func handleDeleteTapped() {
    self.removeItem()
  }
  
}

class SettingsView: ObservableObject {
  @Published var signleitems = [SingleItem]()
  
  private var db = Firestore.firestore()
  private var listenerRegistration: ListenerRegistration?
  
  deinit {
    unsubscribe()
  }
  
  func unsubscribe() {
    if listenerRegistration != nil {
      listenerRegistration?.remove()
      listenerRegistration = nil
    }
  }
  
  func subscribe() {
      let userId = Auth.auth().currentUser?.uid
    if listenerRegistration == nil {
      listenerRegistration = db.collection("items")
            .order(by: "createdTime")
            .whereField("userId", isEqualTo: userId)
            .addSnapshotListener { (querySnapshot, error) in
        guard let documents = querySnapshot?.documents else {
          print("No documents")
          return
        }
        
        self.signleitems = documents.compactMap { queryDocumentSnapshot in
          try? queryDocumentSnapshot.data(as: SingleItem.self)
        }
      }
    }
  }
  
  func removeItems(atOffsets indexSet: IndexSet) {
    let signleitems = indexSet.lazy.map { self.signleitems[$0] }
      signleitems.forEach { signleitem in
      if let documentID = signleitem.id {
        db.collection("items").document(documentID).delete { error in
          if let error = error {
            print("Unable to remove document: \(error.localizedDescription)")
          }
        }
      }
    }
  }

  
}


struct SingleItem: Identifiable, Codable {
  @DocumentID var id: String?
    var title : String
    var author : String
    var description : String
    @ServerTimestamp var createdTime: Timestamp?
    var userId : String?
}
  
  enum CodingKeys: String, CodingKey {
    case id
    case title
    case author
    case description = ""
  }