How to get an Array of Objects from Firestore in Swift? - ios

In Swift, to retrieve an array from Firestore I use:
currentDocument.getDocument { (document, error) in
if let document = document, document.exists {
let people = document.data()!["people"]
print(people!)
} else {
print("Document does not exist")
}
}
And I receive data that looks like this
(
{
name = "Bob";
age = 24;
}
)
However, if I were to retrieve the name alone, normally I'd do print(document.data()!["people"][0]["name"]).
But the response I get is Value of type 'Any' has no subscripts
How do I access the name key inside that object inside the people array?

The value returned by document.data()!["people"] is of type Any and you can't access [0] on Any.
You'll first need to cast the result to an array, and then get the first item. While I'm not a Swift expert, it should be something like this:
let people = document.data()!["people"]! as [Any]
print(people[0])

A better way of writing #Frank van Puffelen's answer would be:
currentDocument.getDocument { document, error in
guard error == nil, let document = document, document.exists, let people = document.get("people") as? [Any] else { return }
print(people)
}
}
The second line may be a little long, but it guards against every error possible.

Related

If condition issues in swift

I know its a basic question but I'm facing an issue related to it that i'm getting data from backend service.
From service I got [] in response. and I'm comparing it with my model class array. Mean if my model class array is qual to [], than I have to perform something.
My code is this,
let addonCategory = subCategoryModel![tappedIndex.section].items[tappedIndex.row].addonCategory
print(addonCategory as Any)
if addonCategory == "[]"{
print("Hello")
}
else {
print("Hi")
}
But when i compare it with [] brackets it shows error message that,
Cannot assign value of type 'String' to type '[AddonCategoryModel]?'
How can i compare it with brackets?
You need to check whether an array has an element or not.
if addonCategory.count > 0 {
// array has element
} else {
// array hasn't element
}
Alternatively you can use guard let or if let to check.
Using if let
if let addonCategory = subCategoryModel[tappedIndex.section].items[tappedIndex.row].addonCategory, addonCategory.isEmpty {
print(addonCategory)
print("Hello")
} else {
print("Hi")
}
You have to use array properties on addonCategory. It is not of type String
let addonCategory = subCategoryModel![tappedIndex.section].items[tappedIndex.row].addonCategory
print(addonCategory.count == 0 ? "Hello" : "Hi")
Here I have used ternary operator instead of traditional if-else syntax.

Swift 3: Array Value for key

New to swift, just trying to catch some data.
so here is my swift code i used
let url = Bundle.main.url(forResource: "kurdiebg", withExtension: "plist")!
do {
let data = try Data(contentsOf: url)
let dataArray = try PropertyListSerialization.propertyList(from: data, format: nil) as! [[String:String]]
// Bloody Error is this
let request = dataArray(value(forKey: "Key"))
print(dataArray.count)
} catch {
print("This error must not happen", error)
}
i am getting
Cannot call value of non-function type '[[String : String]]'
what exactly i am trying to do?
so i have this plist file and i want to allow users to search in it , i perform this via a button action with this code above, so if a user write abbey, it would get the kurdi meaning,
The error occurs because you are using wrong syntax.
dataArray is not a function, you probably mean
dataArray.value(forKey: "Key")
However using KVC methods like value(forKey is only useful if you know what KVC is and why you need KVC. In this case it's inappropriate.
dataArray – as the name implies – is an array which is subscripted by index so you can get for example the value for key english of the first item with
dataArray[0]["english"]
which is a synonym for
dataArray[0].object(forKey:"english")
Or if you want to find an item for a specific key
dataArray.first(where: { $0["english"] == "abbey"})

Ambiguous use of subscript?

I can make a Facebook SDK Graph Request to get a user's likes, but I'm having trouble taking the returned values and storing one of the keys in an array of Strings. The request returns an NSDictionary of keys/values. Then, using objectForKey I can get the data key which returns what I want: the id and name of the "liked" page on Facebook.
Data returns elements like this:
{
id = 486379781543416;
name = "Star Wars Movies";
},
I specifically want only the "name" of all of these objects and to throw them into an array [String]. I tried to loop through the objects but I'm getting error ambiguous use of subscript. Here's the relevant code:
request.startWithCompletionHandler{(connection:FBSDKGraphRequestConnection!, result:AnyObject!, error:NSError!) -> Void in
let resultdict = result as! NSDictionary
let likes = resultdict.objectForKey("data") as! NSArray
print("Found \(likes.count) likes")
print(likes)
for object in likes{
let name = object["name"] as! String //error: ambiguous use of subsript
print(name)
}
}
After doing some research it looks like the issue is with the NSArray and that I should instead use Swift data types. I tried casting it to a Swift array but I got different errors.
What's the best way to handle this error?
Thanks!
update: Here is what the facebook API request returns:
{
data = (
{
id = 111276025563005;
name = "Star Wars (film)";
},
{
id = 115061321839188;
name = "Return of the Jedi";
}
);
paging = {
cursors = {
after = MTE1MDYxMzIxODM5MTg4;
before = Mjc0NzYzODk2MTg4NjY5;
};
next = "https://graph.facebook.com/v2.5/10155262562690368/likes?access_token=<redacted>";
};
}
You should always use the native Swift collection types wherever possible as NSArray and NSDictionary are really type-inspecific, and therefore can easily trigger "ambiguous use of subscript" errors.
You'll also want to avoid force down-casting, in case you receive data that's in the wrong format, or no data at all. This situation would be more elegantly handled with a guard, in order to prevent a crash. If your program depends on the force down-casting succeeding, and therefore should crash – then you can always call fatalError in the guard, with a descriptive error message in order to assist you in debugging the problem.
If I understand your data structure correctly, the request returns an AnyObject that should be a [String:AnyObject] (A dictionary of strings to any objects). In the case of the "data" key, the AnyObject value is then a [[String:AnyObject]] (An array of dictionaries of strings to any objects).
Therefore you'll want to do your casting in two stages. First, using a conditional downcast on your result to cast it as a [String:AnyObject]. If this fails, then the else clause of the guard will be executed and the code will return. You'll then want to get out your "data" value (your 'likes' array), and conditionally downcast it to a [[String:AnyObject]]. Both of these statements will handle the possibility of resultDict or resultDict["data"] being nil.
guard let resultDict = result as? [String:AnyObject] else {return}
guard let likes = resultDict["data"] as? [[String:AnyObject]] else {return}
You can put whatever error handling logic you want in the brackets of these statements to handle cases in which the results dictionary doesn't exist, was the wrong format, or there wasn't a 'likes' array in it.
You can then get an array of 'like' names through using flatMap.
let likeNames = likes.flatMap{$0["name"] as? String}
This will create an array of the like names of each dictionary – if the like names don't exist or aren't strings, then they won't be added. Because the compiler knows for certain that likes is a [[String:AnyObject]] – there's no ambiguity in subscripting its elements.
If you want a more general approach such as you're doing in your question, you can use a guard statement within a for loop.
for object in likes {
guard let name = object["name"] as? String else {continue}
print(name)
}
Again, you can put whatever error handling you wish in the brackets of the guard.

iOS 9 JSON Parsing loop

I'm creating an app that should retrieve some JSON from a database.
This is how my JSON looks:
[{"id":"1","longitude":"10","latitude":"10","visibility":"5","timestampAdded":"2015-10-01 15:01:39"},{"id":"2","longitude":"15","latitude":"15","visibility":"5","timestampAdded":"2015-10-01 15:06:25"}]
And this is the code i use:
if let jsonResult = JSON as? Array<Dictionary<String,String>> {
let longitudeValue = jsonResult[0]["longitude"]
let latitudeValue = jsonResult[0]["latitude"]
let visibilityValue = jsonResult[0]["visibility"]
print(longitudeValue!)
print(latitudeValue!)
print(visibilityValue!)
}
As you can see it only gets the first chunk from the JSON and if there are no JSON at all it will crash, but if i want it to count the amount and make an array out of it like this:
var longitudeArray = [10, 15]
var latitudeArray = [10, 15]
And so on...
I also need this to be apple watch compatible so i can't use SwiftyJSON.
What do i do? I really hope you can help me!
Thanks.
SOLVED!
Problems was solved by "Eric D."
This is the code:
do {
if let url = NSURL(string: "YOU URL HERE"),
let data = NSData(contentsOfURL: url),
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String:AnyObject]] {
print(jsonResult)
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
print(longitudeArray)
print(latitudeArray)
}
} catch let error as NSError {
print(error.description)
}
Thank you soo much Eric!! :-)
You could use flatMap to get an array of your elements:
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
etc.
flatMap is like map but unwraps optionals, which is adequate because we need to safely cast the type of the object we get from each dictionary in the json array.
$0 represents the object in the current iteration of flatMap of the array it's applied to.
If you're currently using SwiftyJSON, then that would be:
let longitudeArray = jsonResult.flatMap { $1["longitude"].string }
let latitudeArray = jsonResult.flatMap { $1["latitude"].string }
because .string is SwiftyJSON's optional String value getter.
But as you said, you don't want to use it (anymore), so you need to use NSJSONSerialization to decode your JSON data, there's plenty of examples on the Web and on SO. Then you will be able to use my original answer.
You're already getting an array with all of the elements (not just the first one. you're simply only accessing the first one). jsonResult is an array of dictionaries. Each dictionary (in this case, based on the json you provided) contains these elements: id, longitude, latitude, visibility and timestampAdded. In order to access each of them, you can simply loop over jsonResult and access the i'th element (and not always the 0 element). This will also prevent the crash you're experiencing with the json is blank or invalid (since you'll only be going over the valid elements in jsonResult.
This will give you the flexibility to create the custom arrays you wish to create (in order to create an array of all of the longitudes, for example, you will simply add that element to the new array while looping over jsonResult). However, if you'd like to save yourself the trouble of manually building these arrays and assuming you have control over the json structure, I would recommend changing the received json to the relevant structure (a dictionary or arrays instead of an array of dictionaries), so it would better fit your needs and provide you the results in the relevant format right "out of the box".

Parse Nested Array in JSON in Swift

I'm trying to Parse JSON with code and structure like this:
userApiService.getAllUsers { (responseDict:NSDictionary?, error:NSError?) -> Void in
//Parse responseDict for the key "result"
}
Here is Json Structure
{
error = "";
result = (
{
name = AnotherUser;
password = AnotherPassword;
userId = 1343;
},
{
name = TestUser;
password = TestPassword;
userId = 1344;
},
{
name = TestUser;
password = TestPassword;
userId = 1347;
},
);
status = 200;
}
I've tried code like this:
self.loadingIcon.endRefreshing()
if let resultDict = responseDict["result"] as? NSArray {
for userRecord in resultDict{
var userModel = User(userDict: userRecord as! NSDictionary)
self.tableData.append(userModel)
}
}
self.tblView.reloadData()
}
But this results in the error "NSArray?" is not convertible to StringLiteralConvertible If I remove the optional and add the ! force unwrap to the closure signature then this error goes away. However, I've seen instances where my app was crashing if something wrong came from the backend. So my questions are:
Is there a way to parse this JSON and still keep the optional NSDictionary in the closure signature.
Or do I just need to check if the dictionary is not nil and then proceed with the code I posted above?
You can use "nil coalescing" to access the key in the Optional dictionary by adding a ? between the dictionary variable and its subscript, like this:
if let resultDict = responseDict?["result"] as? NSArray {
// ...
}
With this syntax the evaluation will not try to access the key if responseDict is nil.
The easiest way to do this is to use a library.
1) You can use swiftyJSON. It uses the objective C JSON parsing library. https://github.com/SwiftyJSON/SwiftyJSON
2) If you want a library which uses a pure swift parser try JSONSwift. The readme on github shows how you can retrieve nested values from the JSON file. And integrating it in your project just requires you to import a single file. https://github.com/geekskool/JSONSwift
Try to use objectForKey to retrive the data from the dictionary, like so:
self.loadingIcon.endRefreshing()
if let resultDict = responseDict.objectForKey("result") as? NSArray {
for userRecord in resultDict{
var userModel = User(userDict: userRecord as! NSDictionary)
self.tableData.append(userModel)
}
}
self.tblView.reloadData()
}

Resources