How to get key and value of Firestore mapped object - swift

I have an app where users can rate films they have watched, and I want to be able to put these in a tableView. The rating is stored in Firestore, and I want to put both the KEY and value into a Struct so I can access it for the tableView.
However any site/tutorial/stack question I have seen only gets the Maps value, but not the key (in this case, the title name). I can access the value, but only by using the field key, but that is what I am trying to get (see attempt 1)
Struct:
struct Rating: Codable {
var ratedTitle: String
var ratedRating: Int
}
Variable:
var ratedList = [Rating]()
Load data function (attempt 1):
let dbRef = db.collection("Users").document(userID)
dbRef.getDocument { document, error in
if let error = error {
print("There was an error \(error.localizedDescription)")
} else {
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
let midnightMass = titleRating!["Midnight Mass"]
print("Rating given to Midnight Mass: \(midnightMass!) stars")
}
}
}
//Prints: Rating given to Midnight Mass: 2 stars
Also tried (but I don't know how to get this array onto a tableView and have the first index as the Title label, and the second index a Rating label for each movie in the array) attempt 2:
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
self.userRatedList = titleRating!
print("userRatedList: \(self.userRatedList)")
}
//Prints: userRatedList: ["Midnight Mass": 2, "Bly Manor": 5]
Attempt 3:
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
self.ratedList = [Rating(ratedTitle: <#T##String#>, ratedRating: <#T##Int#>)]
//Don't know what I would put as the ratedTitle String or ratedRating Int.
self.ratedList = [Rating(ratedTitle: titleRating!.keys, ratedRating: titleRating!.values)]
//Cannot convert value of type 'Dictionary<String, Int>.Keys' to expected argument type 'String'
//Cannot convert value of type 'Dictionary<String, Int>.Values' to expected argument type 'Int'
}

Firstly, I am not sure why you need the struct to conform to Codable?
Now, based off what I see, "Title Ratings" is a dictionary with a String key and an Int value. You are overcomplicating this. If you want to access the key and value of each element individually, use a for-in loop.
//Declare your global variable
var ratedList = [Rating]()
//If you are using an if let, there is not need to force unwrap
if let docData = document.data() {
if let userRatingList = docData["Title Ratings"] as? [String: Int] {
for (key, value) in userRatingList {
let rating = Rating(ratedTitle: key, ratedRating: value)
ratedList.append(rating)
}
//reload your tableView on the main thread
DispatchQueue.main.async {
tableView.reloadData()
}
}
}

Related

Filter an (Codable) array by another array

I'm trying to filter my json data by IDs (trying mark some favourites and filter using it)
struct workoutList : Codable {
let id : Int
let title : String
let tag : String
}
func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
var selectedGroup = [workoutList]()
let workoutFav = [1,10,100]
if libraryFilter == 0 {
// This works because I'm filtering based on 1 specific item
selectedGroup = jsonErgWorkouts.filter { $0.tag == workoutGroupBox.text }
} else if libraryFilter == 1 {
// Here I want to filter and show only the favorites
selectedGroup = jsonErgWorkouts.filter { $0.id } //
print("selectedGroup:\(selectedGroup)")
}
return selectedGroup
}
in the above code, the filter works when I have 1(one) something specific item to filter and then I get the entire json array with that tag.
Now I want to implement a favorite list, where the user selects for example ID == [1, 10 ,100] as their favourite.
How can I use the filter command to do it? I tried a few things and searched through SO (but doesn't work). Most of the answers are based on filtering based on specific items eg:
selectedGroup = jsonErgWorkouts.filter { workoutFav?.contains($0.id) }
edit: (omitted that I am using/storing the favourites in userDefaults. This code gives the error of "type of expression is ambiguous without more context"
func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
var selectedGroup = [workoutList]()
UserDefaults.standard.set([1,10,100], forKey: "workoutFavorite")
/// This one gets stored as [Any] so I cast it to [Int]
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]
if libraryFilter == 0 {
// This works because I'm filtering based on 1 specific item
selectedGroup = jsonErgWorkouts.filter { $0.tag == workoutGroupBox.text }
} else if libraryFilter == 1 {
selectedGroup = workoutFav.flatMap { favouriteId in // for each favourite ID
jsonErgWorkouts.filter { $0.id == favouriteId } // This returns Error "type of expression is ambiguous without more context"
} // flatMap joins all those arrays returns by "filter" together, no need to do anything else
print("selectedGroup:\(selectedGroup)")
}
return selectedGroup
}
Final Solution:
Changing from This
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]
to This (notice the as! instead of as?)
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as! [Int]
works using #sweeper's answer. Thanks
Update:
Figured out why this error occurred "type of expression is ambiguous without more context" when casting the output of UserDefaults as? [Int] and had to use as! [Int]
But using as! [Int] force unwrapping it causes app to crash if the user did not have any favorites saved into the UserDefault. (Which I then had to code around) like below
var workoutFav = [Int]()
if !(UserDefaults.standard.array(forKey: "workoutFavorite") == nil) {
workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as! [Int]
}
Which was then simplified and removed the force unwrapping based on this SO https://stackoverflow.com/a/37357869/14414215 to become this one-line
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int] ?? [Int]()
You need to do that filter for each id in the favourites array. You get an array of arrays as a result. To get the final array, you need to join those arrays to a single array. This "map each thing to an array and join the arrays" operation is what a flatMap does:
workoutFav.flatMap { favouriteId in // for each favourite ID
jsonErgWorkouts.filter { $0.id == favouriteId } // find workouts that match the ID
} // flatMap joins all those arrays returns by "filter" together, no need to do anything else
First thing first please give a struct name with a capital so you can distinguish between instance of it. Second you need to have new array where you will store each favorite, and store permanently that array, core data or some base on server, form there you will fetch favorites.
The better way is to add property like isFavorite: Bool that is false by default, and if user change it you can set it to be true, in that way you can avoid using ids for that and you can store whole workout's in one array to core data or base that you use, after that you can fetch from there with
let favorites = workouts.compactMap { $0.isFavorite == true }
Here you go in that way, but just to mention it highly recommended that you store those type of data outside User defaults.
struct Fav {
let name: String
let id: String
}
let df = UserDefaults.standard
let jk = ["aaa", "bbb", "cccc"]
df.setValue(jk, forKey: "favorites")
let fav1 = Fav(name: "zzz", id: "aaa")
let fav2 = Fav(name: "bbb", id: "qqq")
let favs = [fav1, fav2]
let favIDs = df.value(forKey: "favorites") as? [String]
favIDs?.forEach({ (id) in
let f = favs.filter({$0.id == id}) // here it is
})

Type 'Any' has no subscript members when accessing dictionary in UserDefaults

I am trying to access the values of a dictionary which I passed into UserDefaults, however I get this error:
Type 'Any' has no subscript members.
I have looked around on StackOverflow for answers but everything I tried is failing.
This is the Dictionary object that I stored in UserDefaults and I am trying to access Authorities.levelAcess:
{
accountEnabled = 0;
accountLocked = 0;
Authorities = {
levelAccess = "tier2";
accessType = "3rd party";
};
message = "Logged in Successfully";
profile = "trust";
roles = (
{
authority = "admin";
}
);
status = 1;
}
This the code for UserDefaults:
let keyDict: Dictionary<String, AnyObject> = json
UserDefaults.standard.set(json, forKey: "dict")
This is how I am trying to access the dictionary:
The reason of showing this error in your code is: the compiler cannot recognize result as dictionary, if you tried to option and click on result you would see that its type is Any. You have to cast it first as [String: AnyObject] and then get "Authorities" form it.
It should be like this:
if let result = UserDefaults.standard.dictionary(forKey: "dict") {
print(result)
}
That's how you should "optional binding" the dictionary, thus (assumeing that "Authorities" is a [String: String]):
if let addresses = result["Authorities"] as? [String: String] {
print(addresses)
}
You could also do it as one step:
if let result = UserDefaults.standard.dictionary(forKey: "dict"),
let addresses = result["Authorities"] as? [String: String] {
print(result)
print(addresses)
let levelAccess = addresses["levelAccess"]
print(levelAccess) // optional
}
Finally, you could get the levelAcess from addresses as:
let levelAccess = addresses["levelAccess"]
Again, note that levelAccess would be an optional string (String?), which means you should also handle it.
For fetching a dictionary from UserDefaults, use
dictionary(forKey: key)
In your case:
var result = UserDefaults.standard.dictionary(forKey: "dict")

Issue in parsing float number in swift?

Today I come across a very strange situation while getting value from dictionary.I have made two cases here.
Case 1
if I write code below
let str = "{\"num\":2}"
var dict = convertToDictionary(text: str)!
if let num = dict["num"] as? Float {
print("Value is \(num)")
} else {
print("parse error")
}
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
As you can see value for key num is Integer but while fetching value from dictionary I expect value as Float. so it should go to else block but it prints the value.
Case 2
var test = 2
var dict:[String:Any] = [String:Any]()
dict["num"] = test
if let num = dict["num"] as? Float {
print("Value is \(num)")
} else {
print("parse error")
}
As I have not defined data type so by default it should be integer here & I expect value to be Float. This does work as I expect
Now here is does not print value it show the parse error
Please explain to me why this is happening ?
The answer is - dictionaries in those two cases are actually not identical.
In second case, the value of dict["num"] in of Int type, because you explicitly assigned an Int to it.
In first case however, you get a dictionary via JSONSerialization which turns numbers to NSNumber instances. So, the value of dict["num"] is of NSNumber type. And NSNumber initialized with an Int can be succesfuly cast to both Int and Float
In the first case you're creating json and inserting into dict which is getting the value in [String: Any] format. Here dict["num"] is in NSNumber format.
In the second case you created the dict with [String: Any] format but put the value as Int. var test = 2 this line makes variable test as Int and when you're inserting that value into dict with format [String: Any], it's taking the value of format Any as Int.
So basically in second case, dict["num"] is Int.
From Apple Doc, variable will take the datatype of the value it was assigned at the beginning.
That's why you can convert Any to Float at the first case but cannot convert Int to Float at the second.
If you try with var test: Float = 2, you'll get the value at the second case also.
try
if let num = dict["num"] as? NSNumber {
let floatValue = num.floatValue
print("Value is \(floatValue)")
} else {
print("parse error")
}

Check for nil in dictionary

I have a class in my app, where the user inputs values and i set them to an instance of the class, then i upload this data to Database, but i have to convert the class to something the database accepts and i'm converting to dictionary using Mirror Reflection. Some properties in my class can be nil, because by design not all properties are required. But i can't pass nil values to the database.
I have recreated my example is very simplified playground. i didn't set a value for the name property of the class
I tried to check for nil before adding the key, value pair to the dictionary
Below is my code
import UIKit
class Color: NSObject {
var name: String?
var code: Int?
var shade: String?
}
let cl = Color()
cl.code = 3456
cl.shade = "DARK"
var colorDict = [String: Any]()
for x in Mirror(reflecting: cl).children.makeIterator() {
if let val = x.value as Any? {
print(type(of: val))
colorDict[x.label!] = val
}
}
print (colorDict)
the output in console is as below
Optional<String>
Optional<Int>
Optional<String>
["name": nil, "code": Optional(3456), "shade": Optional("DARK")]
how can i check for nil values and skip adding that property to the Dictionary
i have tried to loop through the dictionary after i add all values including nils and check for values too but i get the below warning
Comparing non-optional value of type 'Any' to nil always returns false
declaring the dictionary as below
var colorDict = [String: Any?]()
for x in colorDict {
if x.value == nil {
colorDict.removeValue(forKey: x.key)
}
}
removes the warning but it doesn't remove anything.
I would really appreciate your help.
The way of unwrapping objects of type Any that contain optionals is kind of weird but you can check that the values aren't nil in your mirror like this:
for x in Mirror(reflecting: cl).children {
if case Optional<Any>.some(let val) = x.value {
print(type(of: val))
colorDict[x.label!] = val
}
}
You can do this really easily in a one-liner, using filter:
let dict: [String : Any?] = ["Foo" : 3, "Bar" : nil, "Baz" : "Qux"]
let noNils = dict.filter { $0.value != nil }
print(noNils) // prints ["Foo": Optional(3), "Baz": Optional("Qux")]
As i have suggested, initialise all values.
If you decide not to store the nil values you will end up with children that some of them will have 1, some 2 and some 3 nodes, nothing wrong with that, BUT what happens when you go to read them?
You havent shared any info as to how these values will be used by the app, but assuming you have one function to read the properties/nodes of stored colors, it will go to read all 3 :
ref.child("colors").child("someSpecificColor").observeSingleEvent(of: .value, with: { (snapshot) in
// Get color values
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let code = value?["code"] as? String ?? ""
let shade = value?["shade"] as? String ?? ""
// ...
}) { (error) in
print(error.localizedDescription)
}
See the issue?
Here is a simple solution :
var colorDict = [String: Any?]()
for x in Mirror(reflecting: cl).children.makeIterator() {
if let val = x.value, val != nil {
print(type(of: val))
colorDict[x.label!] = val
}
}
Here before to print and add you val, you check if the val is different than nil. As your output suggests in your console log you print :
Optional<String>
Optional<Int>
Optional<String>
val is an optional. So, if it's nil it won't be added. If not, you enter into the if statement and that's it.

accessing inner dictionaries (dictionary of dictionaries) with one entry, when not knowing what key or value for that entry are

This is how I declared my dictionary:
var dataDictionary: [NSIndexPath:[Int:Bool]]!
The inner dictionary will always have just one entry e.g. [1:true]. When I search dataDictionary I specify a NSIndexPath. The inner dictionary gets returned. Sth like this:
var innerDictionary = dataDictionary[indexPath] // is of type [Int:Bool]
I would like to access inner dictionarys key and value, but I don't want to specify an Integer or Bool, because it is unknown. I only want to get that Int and/or Bool value stored in inner dictionary. No search of inner dictionary is needed, because if will always contains just one entry. Maybe something like this:
var key = innerDictionary.key
var value = innerDictionary.value
How can this be accomplished?
If your dictionary has only one entry, and you don't know the key, with Swift 2 you can get the first element of the keys sequence safely with if let:
let innerDictionary = [42: true]
if let k = innerDictionary.keys.first {
print(k) // prints 42
} else {
// dict is empty
}
Same for the value:
if let v = innerDictionary.values.first {
print(v) // prints true
} else {
// key has no value
}
Try by this way:
func nestedDic(){
var innerDictionary:NSDictionary = ["Item 1": "data 0", "Item 2": "data 1"]
//NSLog("original object:\(innerDictionary)")
NSLog("all keys array:\(innerDictionary.allKeys)")
if(innerDictionary.isKindOfClass(NSDictionary)){
for key in innerDictionary.allKeys{
let value = innerDictionary.valueForKey(key as! NSString as String)
NSLog("key value = \(value as! String)")
}
}
}
You could create a simple struct for the inner dictionary if there is only one entry, then you are able to use your favorite property names.
struct InnerDictionary {
var key = 0
var value = false
}
let indexPath = NSIndexPath(index: 1)
var dataDictionary = [indexPath: InnerDictionary(key: 1, value: true)]
if let innerDictionary = dataDictionary[indexPath] {
var key = innerDictionary.key
var value = innerDictionary.value
}