0

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

Nick
  • 219
  • 4
  • 15

1 Answers1

1
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

jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Thanks for the answer and the super detailed answer, but is it possible to say .collection("Images") when "Images" is inside a document thats inside another Collection? Or do i have to say .collection("Users/User1/Images") @jnpdx – Nick Feb 25 '21 at 23:32
  • You can chain `document` and `collection` together. So `collection("Users").document("User1").collection("Images")` – jnpdx Feb 25 '21 at 23:34
  • Okay thanks, that makes sense. I was also wondering if the same code could be used if one of the fields is of type "GeoPoint"? @jnpdx – Nick Feb 25 '21 at 23:35
  • GeoPoint is a different type. You can't cast it to `String`. Here's an SO question about using it with Swift: https://stackoverflow.com/questions/50027187/swift-4-firebase-geopoint-decode – jnpdx Feb 25 '21 at 23:38
  • I looked at some of the posts but unfortunately I wasn’t able to understand much of it. Would it be possible for you to explain/show me how I can get the GeoPoint and display it? Would really appreciate it @jnpdx – Nick Feb 25 '21 at 23:43
  • I can certainly take a look, but would you be willing to make another question to address it? I haven't used GeoPoints, so I'd have to do a little research, and I believe that I've answered the original question sufficiently, correct? – jnpdx Feb 25 '21 at 23:44
  • Thank you, You certainly answered the question. I will create a new question as soon as I can, I have to wait 90 minutes for some reason @jnpdx – Nick Feb 25 '21 at 23:57
  • Great -- feel free to comment here when it's up. In the meantime, it looks like you can get a GeoPoint by doing: `let point = documentData["Point"] as? GeoPoint` and then `point?.longitude` or `point?.latitude` gets you the values. – jnpdx Feb 26 '21 at 00:01
  • I have created this question, hope you will take a look at it https://stackoverflow.com/questions/66378588/swiftui-how-to-fetch-geopoint-from-cloud-firestore-and-display-it @jnpdx – Nick Feb 26 '21 at 00:47