Update an existing item in a ChatModel dictionary from a Firestore snapshot - swift

I want to show an overview in a TableView of all existing chats (Name, LastMessage) for a specific user.
Right now, I append a new item in my TableView, which is obviously wrong, instead I want to update the existing item by it's key "ChatId"
This is my Model:
class ChatModel {
var chatOwnerId: String?
var chatPartnerId: String?
var createdDate: Timestamp?
var chatId: String?
var lastMessage: String?
var lastMessageDate: Timestamp?
init(dictionary: [String: Any]) {
chatOwnerId = dictionary["chatOwnerId"] as? String
chatPartnerId = dictionary["chatPartnerId"] as? String
createdDate = dictionary["createdDate"] as? Timestamp
chatId = dictionary["chatId"] as? String
lastMessage = dictionary["lastMessage"] as? String
lastMessageDate = dictionary["lastMessageDate"] as? Timestamp
}
}
How I add the data to my model:
func loadChatOverviewNew(currentUser: String) {
ChatApi.shared.observeChatOverviewNew(currentUser: currentUser) { (chatOverview) in
self.chats.insert(chatOverview, at: 0)
self.chatTableView.reloadData()
}
}
How can I update my "chats" when I receive a new snapshot, instead of appending / inserting it?

To update an existing item, find that item in the array by calling ``, and then update it.
Something like:
ChatApi.shared.observeChatOverviewNew(currentUser: currentUser) { (chatOverview) in
if let index = self.chats.firstIndex(where: { $0.chatId == chatOverview.chatId }) {
self.chats[index] = chatOverview
}
else {
self.chats.insert(chatOverview, at: 0)
}
self.chatTableView.reloadData()
}

Related

How do you pass data dynamically is a Swift array?

Im creating a tinder like swipe app and I need a new CardData(name: "", age: "") to be created depending on how many profiles I pass through from my database. The number of cards will change. I need the number of cards created to match the the value of the results default. I have looked for the solution for a while and can't find it anywhere.
import UIKit
var nameArrayDefault = UserDefaults.standard.string(forKey: "nameArray")!
var ageArrayDefault = UserDefaults.standard.string(forKey: "ageArray")!
var nameArray = nameArrayDefault.components(separatedBy: ",")
var ageArray = ageArrayDefault.components(separatedBy: ",")
var results = UserDefaults.standard.string(forKey: "results")!
struct CardData: Identifiable {
let id = UUID()
let name: String
let age: String
static var data: [CardData] {[
CardData(name: “\(nameArray[0])”, age: “\(ageArray[0])”),
CardData(name: “\(nameArray[1])”, age: “\(ageArray[1])"),
CardData(name: “\(nameArray[2])”, age: “\(ageArray[2])”)
]}
}
You should initiate the array of CardData objects only the first time and update it after that. You can do the following
var _data = [CardData]()
var data: [CardData] {
if _data.isEmpty() {
self.initiateData()
}
return _data
}
// Initiate data for the first time only
func initiateData() -> [CardData] {
// Check that nameArray has the same size as ageArray
// If the size is different then the data are not valid.
guard nameArray.count == ageArray.count else {
return []
}
// For each of names inside nameArray create the corresponding CardData object
self.nameArray.forEach({ (index, item)
self._data.append(CardData(name: item, age: ageArray[index]))
})
}

Need acces from document to collection Firestore

I'm trying to do an iOS app and i've binded it with firebase, so I'm trying to get some posts ad fetch them, and this works fine, however this posts got 2 collections (likes and replies) and i'm trying to fetch likes, the thing is that I can't get the likes because for some reasons I can't a class for document forEach neither I can't access it, someone got any idea?
Code:
import Foundation
import Firebase
struct Post : Hashable {
var id : String
var dateAdded : String
var posterEmail : String
var posterUsername : String
var posterIcon : String
var postTitle : String
var postBody : String
var likes : [String]
var userLikedPost : Bool
}
struct Like {
var likeId : String
var likerEmail : String
}
class Likes {
var likes : [Like] = []
func fetchLikes() {
//Firestore.firestore()
}
}
class Posts : ObservableObject {
#Published var posts : [Post] = []
func fetchPosts() {
Firestore.firestore().collection("posts").getDocuments(completion: { (docPosts, error) in
if (error != nil) {
print("error fetching posts")
} else {
docPosts?.documents.forEach { (post) in
let id = post.documentID
let email = post.get("posterEmail") as! String
let username = post.get("posterUsername") as! String
let icon = post.get("posterIcon") as! String
let title = post.get("title") as! String
let body = post.get("body") as! String
// Here i want to insert the code that gets the likes class and access the likes variable
self.posts.append(Post(id: id, dateAdded:String(id.split(separator: "_").joined(separator: "/").prefix(10)) ,posterEmail: email, posterUsername: username, posterIcon: icon, postTitle: title, postBody: body,
likes: [],userLikedPost: false))
}
}
})
}
}
The Firestore structure was not included in the question so I will present one for use
user_wines
uid_0
name: "Jay"
favorite_wines:
0: "Insignia"
1: "Scavino Bricco Barolo"
2: "Lynch Bages"
uid_1
name: "Cindy"
favorite_wines
0: "Palermo"
1: "Mercury Head"
2: "Scarecrow"
And then the code to read all of the user documents, get the name, the wine list (as an array as Strings) and output it to console
func readArrayOfStrings() {
let usersCollection = self.db.collection("user_wines")
usersCollection.getDocuments(completion: { snapshot, error in
guard let allDocs = snapshot?.documents else { return }
for doc in allDocs {
let name = doc.get("name") as? String ?? "No Name"
let wines = doc.get("favorite_wines") as? [String] ?? []
wines.forEach { print(" ", $0) }
}
})
}
and the output
Jay
Insignia
Scavino Bricco Barolo
Lynch Bages
Cindy
Palermo
Mercury Head
Scarecrow
EDIT
Here's the same code using Codable
class UserWineClass: Codable {
#DocumentID var id: String?
var name: String
var favorite_wines: [String]
}
and the code to read data into the class
for doc in allDocs {
do {
let userWine = try doc.data(as: UserWineClass.self)
print(userWine.name)
userWine.favorite_wines.forEach { print(" ", $0) }
} catch {
print(error)
}
}

Why converting a Firestore querySnapshot into custom objects with compactMap returns empty although the querySnapshot contains documents?

Screenshot of a Firestore Document
I am using Swift, Xcode and a Firestore database.
I created a TableView and a Custom Object Class (MediumSample) with a dictionary and want to load my Firestore documents and show them in the TableView.
The documents (example in the screenshot) are loaded from Firestore correctly but the conversion into the object did not work. The list of objects returned from compactMap is always empty.
Here is my code snippets. It would be great, if someone has a hint on what is going wrong.
Custom Object Class (simplified):
import Foundation
import FirebaseFirestore
protocol MediumSampleDocumentSerializable {
init?(dictionary: [String:Any])
}
struct MediumSample {
var id: String
var field_t: String
var field_i: Int64
var field_b1: Bool
var field_b2: Bool
var field_g: FirebaseFirestore.GeoPoint
var field_d: Date
var field_a: [String]
var usecase: String
var dictionary: [String:Any] {
return [
"id": id,
"field_t": field_t,
"field_i": field_i,
"field_b1": field_b1,
"field_b2": field_b2,
"field_g": field_g,
"field_d": field_d,
"field_a": field_a,
"usecase": usecase
]
}
}
extension MediumSample: MediumSampleDocumentSerializable {
init?(dictionary: [String:Any]) {
guard let id = dictionary ["id"] as? String,
let field_t = dictionary ["field_t"] as? String,
let field_i = dictionary ["field_i"] as? Int64,
let field_b1 = dictionary ["field_b1"] as? Bool,
let field_b2 = dictionary ["field_b2"] as? Bool,
let field_g = dictionary ["field_g"] as? FirebaseFirestore.GeoPoint,
let field_d = dictionary ["field_d"] as? Date,
let field_a = dictionary ["field_a"] as? [String],
let usecase = dictionary ["usecase"] as? String else {return nil}
self.init (id: id, field_t: field_t, field_i: field_i, field_b1: field_b1, field_b2: field_b2, field_g: field_g, field_d: field_d, field_a: field_a, usecase: usecase)
}
}
Declaration of the database and array and calling the loading function:
import UIKit
import FirebaseFirestore
class MediumTableViewController: UITableViewController {
//MARK: Properties
var db: Firestore!
var mediumsamples = [MediumSample]()
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
loadMediumSamples()
}
Function for loading the Firestore documents to fill the Array:
private func loadMediumSamples() {
//run the Firestore query
db.collection(Constants.MEDIUMS).whereField("usecase", isEqualTo: Constants.USECASE)
.getDocuments() { querySnapshot, err in
if let err = err {
print("Error getting documents: \(err)")
} else {
//initialise an array of medium objects with Firestore data snapshots
self.mediumsamples = querySnapshot!.documents.compactMap({MediumSample(dictionary: $0.data())})
//fill the tableView
DispatchQueue.main.async {
self.tableView.reloadData()
print(self.mediumsamples)
}
print("Mediums List", self.mediumsamples) // This line returns: Mediums List []
print("Mediums List size", (self.mediumsamples.count)) // This line returns: Mediums List size 0
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())") // This line returns the snapshot documents correctly!
}
}
}
}
Here is how the screenshot object object is added:
func addMediumSamples() {
let currentDateTime = Date()
let location = FirebaseFirestore.GeoPoint(latitude: 0, longitude: 0)
let mediumsample = MediumSample(id: "an id", field_t: "field_t", field_i: 10, field_b1: true, field_b2: false, field_g: location, field_d: currentDateTime, field_a: [Constants.SAMPLE_DEV], usecase: Constants.SAMPLE_DEV)
var ref: DocumentReference? = nil
ref = self.db.collection(Constants.MEDIUMS).addDocument(data: mediumsample.dictionary) {
error in
if let error = error {
print("Error writing city to Firestore: \(error)")
} else {
print("Document added with id : \(ref!.documentID)")
}
}
}
The problem is in the MediumSample struct, in the field_d type (Date).
The type of that field in your Cloud Firestore database is Timestamp.
The field "field_d" in the MediumSample struct expects a value of type Date.
You can change the type to the FirebaseFirestore.Timestamp, or you can convert it to Date when mapping and before passing to the MediumSample.
eg. for converting Timestamp to Date in Swift
let date = timestamp.dateValue()

swift NSPredicate Data (Struct)

In realm i saving struct data like this
class DBDialogs: Object {
#objc dynamic var userId = 0
#objc dynamic var initials = ""
#objc private dynamic var structData:Data? = nil
var Info : User? {
get {
if let data = structData {
do {
return try JSONDecoder().decode(User.self, from: data)
}catch {
return nil
}
}
return nil
}
set {
structData = try? JSONEncoder().encode(newValue)
}
}
}
struct User : Codable {
var pk : Int?
var email : String?
var brand : String?
var discs : Discs?
enum CodingKeys: String, CodingKey {
case pk = "pk"
case email = "email"
case brand = "brand"
case discs = "discs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
pk = try values.decodeIfPresent(Int.self, forKey: .pk)
email = try values.decodeIfPresent(String.self, forKey: .email)
brand = try values.decodeIfPresent(String.self, forKey: .brand)
discs = try values.decodeIfPresent(Discs.self, forKey: .discs)
}
}
how can i filter the struct data ? trying something like this, but i get the error (Unable to parse the format string "Info?.brand == %#")
var dbDialogsArray: Results<DBDialogs>?
var dbDialogsMethods = DBDialogsMethods()
....
class DBDialogsMethods { // also in this class i will write to realm, update, delete and etc
var realm: Realm!
func getArray() -> Results<DBDialogs> {
return realm.objects(DBDialogs.self)
}
}
....
let realm = try! Realm()
dbDialogsMethods.realm = realm
dbDialogsArray = dbDialogsMethods.getArray()
...
let predicate = NSPredicate(format: "Info?.brand = %#", brandField.text!)
let filteredDialogs = dbDialogsArray?.filter(predicate)
i have array DBDialogs, i need to filter the array by fields in Info (structData)
Why i do with NSPredicate :
I have several fields by which I filter data (fields for filtering are selected manually in the application), using NSPredicate I can filter multiple fields that the user chose in the app, something like this
var stringPredicate = Dictionary <String, NSObject> ()
stringPredicate ["brand"] = brand.text! as NSObject
var generalPredicates = [NSPredicate] ()
        for (key, value) in stringPredicate {
            let predicate = NSPredicate (format: "% K =% #", key, value)
            generalPredicates.append (predicate)
        }
and filter everything at once, I don’t know how to do filtering in another way with the condition that you need to filter only those fields that the user selected in the application

How should I retrieve data from firebase and put it into a dictionary?

I have an organization document with a Members collection inside of it and then a members document inside of that. Inside the members document includes a Map of a user which is a member. The Key is the UserID and 3 values (firstName, lastName, username). I am trying to load in this data into my "Event" class that holds a membersInvited Property that is a dictionary. Inside the Event class is a method to get this data called getOrgMembers(). Even though I have that data in firebase I am getting a nil value for my dictionary. I also am using Dispatch but kind of new to it.
Below is code in the Event Class:
var membersInvited: [Member: Bool]?
func getOrgMembers(dispatch: DispatchGroup?) {
let membRef = BandzDatabase.collection("Organizations").document(currentUser.currentOrgID!).collection("Members").document("members")
membRef.getDocument { (snapshot, error) in
if let error = error {
print (error.localizedDescription)
} else {
if let data = snapshot?.data() {
for (key,value) in data {
if let membArray = value as? [String: Any] {
let username = membArray["username"] as? String
let firstName = membArray["firstName"] as? String
let lastName = membArray["lastName"] as? String
let userID = key
let member = Member(username: username ?? "", firstName: firstName ?? "", lastName: lastName ?? "", userID: userID)
self.membersInvited?.updateValue(true, forKey: member)
}
}
}
}
dispatch?.leave()
}
}
struct Member: Hashable {
var username: String
var firstName: String
var lastName: String
var userID: String
init (username: String, firstName: String, lastName: String, userID: String) {
self.username = username
self.firstName = firstName
self.lastName = lastName
self.userID = userID
}
}
Below is were I call this method from another class:
func getMembers() {
showActivityIndicatory(uiView: self.view)
self.dispatchGroup.enter()
eventMade?.getOrgMembers(dispatch: self.dispatchGroup)
self.dispatchGroup.notify(queue: .main) {
//self.tableView.reloadData()
stopActivityIndicator()
print("happens")
print(self.eventMade?.membersInvited)
}
}
After some research, I discovered that since I never initilized the dictionary, whenever I was calling to append key-value paires it would not even run since it was an optional. So I changed the decleration to this:
var membersInvited: [Member: Bool] = [:]