Swift Firebase Custom Object with Document ID
There is an even easier way to achieve this using the Codable support we added to Firestore recently:
Add the FirebaseFirestoreSwift
pod:
# Uncomment the next line to define a global platform for your project
platform :ios, '13.0'
target 'MyAwesomeApp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for MyAwesomeApp
pod 'Firebase/Analytics'
pod 'Firebase/Firestore'
pod 'FirebaseFirestoreSwift'
end
import FirebaseFirestoreSwift
struct City: Codable, Identifiable {
@DocumentID var id: String? = UUID().uuidString
let name: String
let state: String
let country: String
let isCapital: Bool
let population: Int64
// you only need CodingKeys if your attribute names in Firestore differ from the property names on your struct!
}
By using the @DocumentID
property wrapper, you tell Firestore's Codable support that you want it to map the document's ID to the id
field. Also note that - as the City
struct implements Identifiable
- you will be able to use it in a SwiftUI ListView
.
Then, in your view model, use queryDocumentSnapshot.data(as:)
to fetch and map data in a typesafe way:
import FirebaseFirestore
class CitiesViewModel: ObservableObject {
@Published var cities = [City]()
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
deinit {
unregister()
}
func unregister() {
if listenerRegistration != nil {
listenerRegistration?.remove()
}
}
func fetchData() {
unregister()
listenerRegistration = db.collection("cities").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.cities = documents.compactMap { queryDocumentSnapshot -> City? in
return try? queryDocumentSnapshot.data(as: City.self)
}
}
}
}
How to convert document to a custom object in Swift 5?
After contacting the firebase team, I found the solution I was looking for. It turns out I have to do import FirebaseFirestoreSwift
explicitly instead of just doing import Firebase
. The error will disappear after this. (And of course you'll need to add the pod to your podfile first:D)
Assigning firestore data to custom object
You must separate access to documents from access to data within documents (you attempt to do it together). You cannot call getDocuments()
on a document or a field within a document, only on a collection. So instead of db.collection("customers/test/specs").getDocuments()
, try:
db.collection("customers").getDocuments() { (snapshot, error) in ... }
Then to get data from the documents:
db.collection("customers").getDocuments() { (snapshot, error) in
if let snapshot = snapshot { // lead by unwrapping the snapshot instead of the error
for doc in snapshot.documents { // iterate through the documents
guard let specs = doc.get("specs") as? [[String: Any]] else {
continue // continue loop
}
for s in specs { // iterate through the array of specs
if let specNum = s["SpecNum"] as? String,
let specDesc = s["SpecDesc"] as? String,
let pltCount = s["PalletCount"] as? Int {
let spec = Spec(specNum: specNum, specDesc: specDesc, pltCount: pltCount)
self.someArray.append(spec)
}
}
self.tableView.reloadData() // loop is done, reload
}
} else {
if let error = error {
print(error)
}
}
}
This is a very simplified version of how I imagine you'd actually want to implement it, depending on how the table/collection was reloaded (on the fly, routinely, or just once). Also, each document contains an array of specs but you're fetching all documents from the collection, which would give you a ton of specs without any indication of which spec is tied to which customer. But I suspect this is just early setup and you're just trying to get a handle on the API first.
Note: Maps in Firestore are called dictionaries in Swift and they always come back from Firestore as [String: Any]
dictionaries. That's why when we originally unwrapped the specs map, we cast it as an array of dictionaries:
let specs = doc.get("specs") as? [[String: Any]]
Add a Document's Document ID to Its Own Firestore Document - Swift 4
While there is a perfectly fine answer, FireStore has the functionality you need built in, and it doesn't require two calls to the database. In fact, it doesn't require any calls to the database.
Here's an example
let testRef = self.db.collection("test_node")
let someData = [
"child_key": "child_value"
]
let aDoc = testRef.document() //this creates a document with a documentID
print(aDoc.documentID) //prints the documentID, no database interaction
//you could add the documentID to an object etc at this point
aDoc.setData(someData) //stores the data at that documentID
See the documentation Add a Document for more info.
In some cases, it can be useful to create a document reference with an
auto-generated ID, then use the reference later. For this use case,
you can call doc():
You may want to consider a slightly different approach. You can obtain the document ID in the closure following the write as well. So let's give you a cool Ride (class)
class RideClass {
var availableSeats: Int
var carType: String
var dateCreated: String
var ID: String
init(seats: Int, car: String, createdDate: String) {
self.availableSeats = seats
self.carType = car
self.dateCreated = createdDate
self.ID = ""
}
func getRideDict() -> [String: Any] {
let dict:[String: Any] = [
"availableSeats": self.availableSeats,
"carType": self.carType,
"dateCreated": self.dateCreated
]
return dict
}
}
and then some code to create a ride, write it out and leverage it's auto-created documentID
var aRide = RideClass(seats: 3, car: "Lincoln", createdDate: "20190122")
var ref: DocumentReference? = nil
ref = db.collection("rides").addDocument(data: aRide.getRideDict() ) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
aRide.ID = ref!.documentID
print(aRide.ID) //now you can work with the ride and know it's ID
}
}
How can I get the document ID when using Codable in Firestore?
You can use Firestore's Codable support to map document IDs. No need to implement a custom decoder - we've done all the hard work for you.
Here is how.
1. Create a model for your data
You already did this. Looking at the attributes in your MoodRecord
struct, I assume you want to use date
and time
to track timestamps, and mood
to capture the value of an enum. I've updated the struct accordingly:
struct MoodRecord: Codable, Hashable, Identifiable {
@DocumentID var id: String?
var user: String
var date: Date
var time: Date
var mood: Mood
}
enum Mood: String, Codable {
case great
case ok
case good
case bad
case terrible
}
2. Map data using Codable
Fetching Firestore documents and mapping them to Swift structs becomes a one-liner thanks to Codable:
docRef.getDocument(as: MoodRecord.self) { result in
// handle result
}
Here is a complete code snippet for fetching a single document:
private func fetchMoodRecord(documentId: String) {
let docRef = db.collection("moodrecords").document(documentId)
docRef.getDocument(as: MoodRecord.self) { result in
switch result {
case .success(let moodRecord):
// A MoodRecord value was successfully initialized from the DocumentSnapshot.
self.moodRecord = moodRecord
self.errorMessage = nil
case .failure(let error):
// A MoodRecord value could not be initialized from the DocumentSnapshot.
self.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
}
}
3. Updating a document
To update a document using Codable, use the following code snippet:
func updateMoodRecord(moodRecord: MoodRecord) {
if let id = moodRecord.id {
let docRef = db.collection("moodRecords").document(id)
do {
try docRef.setData(from: moodRecord)
}
catch {
print(error)
}
}
}
4.Adding new documents
Adding new documents is even easier:
func addMoodRecord(moodRecord: MoodRecord) {
let collectionRef = db.collection("moodRecords")
do {
let newDocReference = try collectionRef.addDocument(from: moodRecord)
print("Mood record stored with new document reference: \(newDocReference)")
}
catch {
print(error)
}
}
More
To learn more about how to map Firestore documents using Swift's Codable protocol, including how to map advanced data types such as date, time, colors, enums, how to fetch data using snapshot listeners, and how to handle any errors that might occur during the mapping process, check out Mapping Firestore Data in Swift - The Comprehensive Guide and the accompanying sample project on GitHub
Related Topics
Instagram Explorer Searchbar and Tableview
Callback Url Not Approved Despite Being Provided Twitter API
Create Spotlight-Like Window in Swift 4
Calling Nsexception.Raise() in Swift
Programmatically Create an Nsviewcontroller Without an Xib in Swift 3
Nswindow with Round Corners in Swift
How to Trim a String in Swift Based on a Character
How to Cast an @Binding in Swift
Swiftui Hierarchical Picker with Dynamic Data
Scenekit Won't Scale a Dynamic Body
Workarounds for Generic Variable in Swift
How to Get a Double Value Up to 2 Decimal Places
Swfitui List Make Scrolling Disabled
Refreshing Auth Token with Moya
How to Retrieve All Contacts Using Cncontact.Predicateforcontacts
How to Suppress Warnings in Swift 3
How to Make High Score of Game to Be Saved on Leaderboard, with Swift