Update Array [[String: [Dictionary]]] when valueChanged in Firebase - ios

var chatMessages = [[String: ChatMessage]]()
Firebase Chat Messages structure is like this.
-Kjws99ol6qjFt7ET9C
content: "Hehd"
displayName: "John Doe"
fileLength: 0
fileUrl: ""
fromID: "5904ee8cfa"
isRead: false
messageStatus: 2
messageType: "normal"
timestamp: 1494596232
Now on childAdded I'm appending the new message like this
weakSelf.chatMessages.append(newMessage)
//Kjws99ol6qjFt7ET9C - This is the threadID which is stored in String and below value is stored in ChatMessage
But after message isRead by user it value changes and that is identified by childChanged so in childChanged change how to update my Array correctly?

On the childChanged event, the app is passed a snapshot of the updated child, with the (in this case) key being Kjws99ol6qjFt7ET9C and the value being the child node data.
To update the array, find which index in the chatMessages array corresponds to that key and update the value accordingly.
To find it in the array you've set up, which is an array of [String: ChatMessage] dictionaries do the following
let searchKey = "Kjws99ol6qjFt7ET9C"
let index = chatMessages.map( {$0.keys.first!} ).index(of: searchKey)
Once you have the index, you can then update the element in the array with the new data.
{$0.keys.first!} - compiles all of the keys in the chatMessage array into an array
index(of: searchKey) - finds the index of the searchKey we are looking for
Then you can
chatMessage[index] = updated data
If you need any additional code, let me know.
However, I would strongly encourage changing the model to store a ChatMessage class (or struct) in the array
class ChatMessage {
var fbKey = "" // the key like Kjws99ol6qjFt7ET9C
var content = "" // like "Hehd"
var displayName "" // "John Doe"
}
var chatMessages = [ChatMessage]()
it will be easier to maintain and the array search is simplified and faster.
With this use case, to find a specific index do this
let searchKey = "Kjws99ol6qjFt7ET9C"
let index = chatMessages.index(where: { $0.fbKey == searchKey} )

I'm confused as to why you are storing the dictionary inside an array. It looks to me like
var chatMessages = [String: ChatMessage]()
would satisfy your model as each message push id (e.g. -Kjws99ol6qjFt7ET9C) would be unique from the database, so you could have a valid Dictionary by storing this push id as the key.
When you receive a childChanged event, you can then quickly locate the message that has changed by looking up the snapshot.key in the dictionary and updating it.

Related

Sorting Array returns a Dictionary

I have the following object:
private var datasource: [Int: [MyCustomModel]] = [:]
At a later stage, I need to sort the datasource by the key property (ascending).
I do it as follows:
let sorted = datasource.sorted(by: {$0.key < $1.key})
This does sort the content by the key (Int) value perfectly, though I cannot set the datasource to the sorted property.
Xcode presents the following error
Cannot assign value of type '[Dictionary<Int, [MyCustomModel]>.Element]' (aka 'Array<(key: Int, value: Array)>') to type '[Int : [MyCustomModel]]'
I get that the returned type, after sorting is a Dictionary and not an Array.
How can I overcome this issue?
Thanks.
I didn't get any solution for the same to sort the dictionary, but you can do something like below.
Its works for me.
make structure of key and value.
struct STRUCT_TEMP
{
var key : Int
var value : [MyCustomModel]
}
create structure variable.
var structTemp : [STRUCT_TEMP] = []
convert your dictionary in structure like below:
self.structTemp = datasource.map({ STRUCT_TEMP(key: $0.key, value: $0.value) })
now you can do anything with the structTemp array. Like sorting, filter and you can also use this array everywhere you want instead of dictionary.
let datasource: [Int: [MyCustomModel]] = [:]
Dictionaries cannot be sorted!. If you use sorted(by: { $0.key < $1.key}) to sort the datasource, the result is an array, not a dictionary.
datasource.sorted(by: { $0.key < $1.key})
Swift 5.5
You can use OrderedDictionary. It keeps insertion order.
https://www.advancedswift.com/ordereddictionary/

same value in dictionary in swift

I m mapping data that come from service with using dictionary [String: String]. I collect them dictionary array. For example, if their parent ids are the same, I want to add their values by array value.
["ParentId": "1","Value": "["Giyim","Aksesuar","Ayakkabı"]"]
It is also the reason I don't know parent id sometimes on the left sometimes on the right in photo
Here is my code and its output.
struct Categories {
let parentId: String
let values: [String]
}
for result in results {
if result?.parentCategoryId != "" {
for docId in self.docIds {
if result?.parentCategoryId == docId {
//print(result?.name)
var values = [String]()
values.append(result?.name ?? "")
self.newCat.append(Categories(parentId: docId, values: values))
}
}
}
}
Problem
As far as I understand from the description you want to map some service data structure to a dictionary where key is parentId and value is an array of some items referred to parentId.
I believe your problem comes from a misunderstanding of the concept of dictionary as a data structure.
[String: String] is dictionary where keys and their associated values are of String type. For example:
["firstKey": "firsthValue", "secondKey": "secondValue", ...]
That means you cannot store associated values of String and Array types in the same dictionary, as you already told the compiler you would like to store only strings.
It is also the reason I don't know parent id sometimes on the left sometimes on the right in photo
This is because key-value pairs are stored in the dictionary without order. This is how dictionaries work :) I'd strongly recommend reading some short official materials to get used to them.
New Swift 5.4 version has a new OrderedDictionary data structure, where keys are ordered, but there is absolutely 100500% no reason to use it for your problem*
Possible solutions
In your case i would suggest either use some struct:
struct SomeData {
let parentID: Int
var values: [String]
}
var storage: [SomeData] // alternative to emptyDic
// Filling storage with data from service
for result in inputData {
// search for SomeData with required id, add value
// OR create SomeData if there is no such id in array yet
}
OR [personally this appeals to me more]
Store data in [String: [String]] dictionary, where the key is parentID and the associated value is an array of values.
The algorithm of filling this dictionary is pretty the same:
You add new key-value pair for every new parentID
You append new values for parentIDs that are already in the dictionary.
Using the struct approach, you could do something like this (you'll need to adapt it to your code, but that should be straightforward):
struct Categories {
let parentId: String
var values: [String] //notice this needs to be a var, not a let
}
func addItem(categories : inout [Categories], docId: String, name: String) {
if let index = categories.firstIndex(where: { $0.parentId == docId }) {
categories[index].values.append(name)
} else {
categories.append(Categories(parentId: docId, values: [name]))
}
}
func addValues() {
var categories = [Categories]()
addItem(categories: &categories, docId: "4", name: "Test1")
addItem(categories: &categories, docId: "1", name: "Test")
addItem(categories: &categories, docId: "4", name: "Test2")
addItem(categories: &categories, docId: "4", name: "Test3")
print(categories)
//in your code, it'll look more like:
// addItem(categories: &self.newCat, docId: docId, name: result?.name ?? "")
}
Which yields this:
[
StackOverflowPlayground.Categories(parentId: "4", values: ["Test1", "Test2", "Test3"]),
StackOverflowPlayground.Categories(parentId: "1", values: ["Test"])
]
I still wonder whether you maybe just want a Dictionary that is keyed by the parentId, but not knowing your use case, it's hard to say.

Retrieving object properties with relation to their parent categories

I am new to realm and iOS development so I apologize in advance if something isn’t explained properly or is just incorrect.
I have 2 Realm Object classes:
class Category: Object {
#objc dynamic var name: String = ""
#objc dynamic var color: String = ""
let trackers = List<Tracker>()
}
and
class Tracker: Object {
#objc dynamic var timeSegment: Int = 0
var parentCategory = LinkingObjects(fromType: Category.self, property:
"trackers")
}
I’m able to store new timeSegment properties consistently; however, the issue is that I cannot retrieve & display a collection of timeSegment values relating to their parentCategory. setting
var entries : Results<Tracker>?
produces all results for every category, which is the only result i'm able to pull so far after testing.
Any help is appreciated, and can follow up with any additional details. Thanks
You need to call objects on your Realm object with a filter for fetching only results that match a predicate. The realm object in this code is an instance of the Realm class.
func getTrackersWithName(_ name: String) -> Results<Tracker> {
return realm.objects(Tracker.self).filter("name = \"\(name)\"")
}
This tells Realm to fetch all objects that match the filter predicate. In this case, the filter predicate matches any object where the value of the "name" property matches the string that is passed into the method.

Swift: how to retrieve data from firebase?

My structure in firebase is as follows:
app name
user ID
wins = 7
losses = 8
and my code to read the wins child node
ref = Database.database().reference().child(passUserID)
ref?.child("wins").observe(.childAdded, with: { (snapshot) in
//Convert the info of the data into a string variable
let getData = snapshot.value as? String
print(getData)
})
But it prints nothing.
To read data from Firebase you attach a listener to a path which is what creates a FIRDatabase reference. A FIRDatabaseReference represents a particular location in your Firebase Database where there is a key-value pair list of children. So in your case, you have created a Firebase reference to the key "wins" which only points to a value and not a key-value pair. Your reference was valid up to this point:
ref = Database.database().reference().child(passUserID)
//did you mean FIRDatabase and not Database??
This FIRDatabaseReference points to the key passUserID which has a key-value pair list of children ["wins":"7"] and ["losses":"8"] (NOTE: a key is always a string). So from your FIRDatabase reference, you create your observer as follows and read the value of "wins":
ref?.observe(.childAdded, with: { (snapshot) in
//Convert the info of the data into a string variable
if let getData = snapshot.value as? [String:Any] {
print(getData)
let wins = getData["wins"] as? String
print("\(wins)")
}
})
The Child added event will fire off once per existing piece of data, the snapshot value will be an individual record rather than the entire list like you would get with the value event. As more items come in, this event will fire off with each item. So if "losses" is the first record you might not get the value of "wins". Is this what you are trying to achieve? If what you really wanted to know is the value of "wins" at that particular location and to know if this value has ever changed you should use the .value observer as follows:
ref?.observe(.value, with: { (snapshot) in
//Convert the info of the data into a string variable
if let getData = snapshot.value as? [String:Any] {
let wins = getData["wins"] as? String
print("\(wins)") //check the value of wins is correct
}
})
Or if you just wanted to get the know the value of wins just once and you are not worried about knowing if there any changes to it, use the "observeSingleEvent" instead of "observe".
EDIT
I saw your image and now realize you might also have a problem with your reference. Your ref should actually be something like:
ref = FIRDatabase.database().reference().child("game-").child(passUserID)
You have obscured what "game" is but a valid reference to "wins" will include it.
SECOND EDIT
I will add the following so you can properly debug the problem. Use this pattern to observe the value and see if you get an error returned and what is says:
ref.observe(.value, with: { (snapshot) in
print(snapshot)
}, withCancel: { (error) in
print(error.localizedDescription)
})
Normally it will give you an error if you cannot access that Firebase location because of a database rule. It will also be a good idea to see if print(snapshot) returns anything as above.
You need this:
ref.child("YOUR_TOP_MOST_KEY").observe(.childAdded, with: { (snapshot) in
let keySnapshot = snapshot.key
//print(keySnapshot)
self.ref.child(keySnapshot).observe(.value, with: { (snapshot2) in
//print(snapshot2)
}) { (error) in
print("error###\(error)")
}
})

Proper way to get swift dictionary item index

I have dictionary in my app written in Swift
var cities: [String: String] = ["":"not specified", "ny":"New York", "la":"Los Angeles", "sf":"San Francisco"]
I use this dictionary to build pickerView. When user selects one of this items - city code is saving to device memory.
When user opens app next time I want pickerView to show saved city. How should i do it?
You can find the index of a key in a dictionary with higher-order functions:
let desiredKey = "ny"
let pos = x.enumerate().filter { (pair: (index: Int, element: (String, String))) -> Bool in
return pair.element.0 == desiredKey
}.map { (pair: (index: Int, element: (String, String))) -> Int in
return pair.index
}
pos is an Optional(Int) containing the position of "ny" in the current iteration order of the dictionary.
Store the value of the key somewhere (say, in user defaults), then retrieve it, get the index of the key-value pair using the code above, and pass to selectedRowInComponent: of your picker view.
Note: The problem with using a dictionary alone as a backing for your picker is that the order is not specified explicitly. Adding or removing keys may change the order of existing keys. In addition, existing keys may not end up in places that you want them to be - for example, your "" - "not specified" pair may end up in the middle of the picker.
To fix this problem, keep a separate array of city keys in the proper order that you wish to follow. Use this array to decide the picker row in which a city is to be displayed, and look up the actual city using the dictionary.
You can use NSUserDefaults to insure the persistency of your cityCode.
Copy an paste this sample in a playground.
var cities: [String: String] = ["":"not specified", "ny":"New York", "la":"Los Angeles", "sf":"San Francisco"]
let cityCodeIndexKey="cityCodeIndexKey"
/**
Selects and saves the cityCode
- parameter cityCode: a cityCode String
- returns: true if the code exists.
*/
func selectCity(cityCode:String)->Bool{
if let _ = cities.indexForKey(cityCode){
NSUserDefaults.standardUserDefaults().setObject(cityCode, forKey:cityCodeIndexKey)
NSUserDefaults.standardUserDefaults().synchronize()
return true
}else{
return false
}
}
selectCity("ny") // Returns true
selectCity("pr") // Returns false
NSUserDefaults.standardUserDefaults().valueForKey(cityCodeIndexKey) // returns "ny"
Get key,
var cityLongName <- user selected one
var key = dict.first((key,value) In{
If (value == cityLongName ){
return true
}else{
return false
}
}).key!
Get values ,
var value = dict[“city sort name”]

Resources