Nick
Nick

Reputation: 219

SwiftUI: How to iterate over Documents in Cloud Firestore Collection and get Data?

I have this small project where a user can post an Image together with a quote, I would then like to display the Image and the quote togehter in their profile, as well as somewhere else so other users can see the post.

If I have this Cloud Firestore setup where all of the Image Docs have the same 3 fields, but with different values.

How can I then iterate over all of the Image Docs and get the the Url and the quote? So I later can display the url together with the correct Quote? enter image description here

And if this is for some reason not possible, is it then possible to get the number of Documents in a Collection?

BTW, I am not very experienced so I would appreciate a "kid friendly" answer if possible

Upvotes: 0

Views: 1565

Answers (1)

jnpdx
jnpdx

Reputation: 52347

Firestore
  .firestore()
  .collection("Images")
  .getDocuments { (snapshot, error) in
     guard let snapshot = snapshot, error == nil else {
      //handle error
      return
    }
    print("Number of documents: \(snapshot.documents.count ?? -1)")
    snapshot.documents.forEach({ (documentSnapshot) in
      let documentData = documentSnapshot.data()
      let quote = documentData["Quote"] as? String
      let url = documentData["Url"] as? String
      print("Quote: \(quote ?? "(unknown)")")
      print("Url: \(url ?? "(unknown)")")
    })
  }

You can get all of the documents in a collection by calling getDocuments.

Inside that, snapshot will be an optional -- it'll return data if the query succeeds. You can see I upwrap snapshot and check for error in the guard statement.

Once you have the snapshot, you can iterate over the documents with documents.forEach. On each document, calling data() will get you a Dictionary of type [String:Any].

Then, you can ask for keys from the dictionary and try casting them to String.

You can wee that right now, I'm printing all the data to the console.

Keep in mind that getDocuments is an asynchronous function. That means that it runs and then returns at an unspecified time in the future. This means you can just return values out of this function and expect them to be available right after the calls. Instead, you'll have to rely on things like setting properties and maybe using callback functions or Combine to tell other parts of your program that this data has been received.

If this were in SwiftUI, you might do this by having a view model and then displaying the data that is fetched:

struct ImageModel {
  var id = UUID()
  var quote : String
  var url: String
} 

class ViewModel {
  @Published var images : [ImageModel] = []

  func fetchData() { 
    
    Firestore
   .firestore()
   .collection("Images")
   .getDocuments { (snapshot, error) in
      guard let snapshot = snapshot, error == nil else {
      //handle error
      return
    }
    print("Number of documents: \(snapshot.documents.count ?? -1)")
    self.images = snapshot.documents.compactMap { documentSnapshot -> ImageModel? in
      let documentData = documentSnapshot.data()
      if let quote = documentData["Quote"] as? String, let url = documentData["Url"] as? String {
         return ImageModel(quote: quote, url: url)
      } else {
         return nil
      }
    }
   }
  }
}

struct ContentView {
  @ObservedObject var viewModel = ViewModel()

  var body : some View {
    VStack {
      ForEach(viewModel.images, id: \.id) { item in 
        Text("URL: \(item.url)")
        Text("Quote: \(item.quote)")
      }
    }.onAppear { viewModel.fetchData() }
  }
} 

Note: there are now fancier ways to get objects decoded out of Firestore using FirebaseFirestoreSwift and Combine, but that's a little outside the scope of this answer, which shows the basics

Upvotes: 1

Related Questions