I have multiple query snapshots with closures and some of them are using the data supplied by the query that came before it.
I have read up on GCD and I've tried to implement a DispatchGroup with .enter() and .leave() but I am apparently doing something wrong.
If somebody can help me by laying out exactly how to force one task to be performed before another, that would solve my problem.
If you can't tell, I am somewhat new to this so any help is greatly appreciated.
//MARK: Get all userActivities with distance(All Code)
static func getAllChallengesWithDistanceAllCode(activity:String, completion: #escaping ([Challenge]) -> Void) {
let db = Firestore.firestore()
let currUserID = Auth.auth().currentUser!.uid
var currUserName:String?
var distanceSetting:Int?
var senderAverage:Double?
var senderBestScore:Int?
var senderMatchesPlayed:Double?
var senderMatchesWon:Double?
var senderWinPercentage:Double?
var validUserActivities = [Challenge]()
db.collection("users").document(currUserID).getDocument { (snapshot, error) in
if error != nil || snapshot == nil {
return
}
currUserName = snapshot?.data()!["userName"] as? String
distanceSetting = snapshot?.data()!["distanceSetting"] as? Int
}
db.collection("userActivities").document(String(currUserID + activity)).getDocument { (snapshot, error) in
//check for error
//MARK: changed snapshot to shapshot!.data() below (possible debug tip)
if error != nil {
//is error or no data..??
return
}
if snapshot!.data() == nil {
return
}
//get profile from data proprty of snapshot
if let uActivity = snapshot!.data() {
senderBestScore = uActivity["bestScore"] as? Int
senderMatchesPlayed = uActivity["played"] as? Double
senderMatchesWon = uActivity["wins"] as? Double
senderAverage = uActivity["averageScore"] as? Double
senderWinPercentage = round((senderMatchesWon! / senderMatchesPlayed!) * 1000) / 10
}
}
if distanceSetting != nil {
db.collection("userActivities").whereField("activity", isEqualTo: activity).getDocuments { (snapshot, error) in
if error != nil {
print("something went wrong... \(String(describing: error?.localizedDescription))")
return
}
if snapshot == nil || snapshot?.documents.count == 0 {
print("something went wrong... \(String(describing: error?.localizedDescription))")
return
}
if snapshot != nil && error == nil {
let uActivitiesData = snapshot!.documents
for uActivity in uActivitiesData {
let userID = uActivity["userID"] as! String
UserService.determineDistance(otherUserID: userID) { (determinedDistance) in
if determinedDistance! <= distanceSetting! && userID != currUserID {
var x = Challenge()
//Sender
x.senderUserID = currUserID
x.senderUserName = currUserName
x.senderAverage = senderAverage
x.senderBestScore = senderBestScore
x.senderMatchesPlayed = senderMatchesPlayed
x.senderMatchesWon = senderMatchesWon
x.senderWinPercentage = senderWinPercentage
//Receiver
x.receiverUserID = userID
x.receiverUserName = uActivity["userName"] as? String
x.receiverAverage = uActivity["averageScore"] as? Double
x.receiverBestScore = uActivity["bestScore"] as? Int
if (uActivity["played"] as! Double) < 1 || (uActivity["played"] as? Double) == nil {
x.receiverMatchesPlayed = 0
x.receiverMatchesWon = 0
x.receiverWinPercentage = 0
} else {
x.receiverMatchesPlayed = uActivity["played"] as? Double
x.receiverMatchesWon = uActivity["wins"] as? Double
x.receiverWinPercentage = ((uActivity["wins"] as! Double) / (uActivity["played"] as! Double) * 1000).rounded() / 10
}
if uActivity["playStyle"] as? String == nil {
x.receiverPlayStyle = "~No PlayStyle~"
} else {
x.receiverPlayStyle = uActivity["playStyle"] as! String
}
x.activity = activity
//append to array
validUserActivities.append(x)
}
}
}
completion(validUserActivities)
}
}
}
}
try this
static func getAllChallengesWithDistanceAllCode(activity:String, completion: #escaping ([Challenge]) -> Void) {
let db = Firestore.firestore()
let currUserID = Auth.auth().currentUser!.uid
var currUserName:String?
var distanceSetting:Int?
var senderAverage:Double?
var senderBestScore:Int?
var senderMatchesPlayed:Double?
var senderMatchesWon:Double?
var senderWinPercentage:Double?
var validUserActivities = [Challenge]()
db.collection("users").document(currUserID).getDocument { (snapshot, error) in
if error != nil || snapshot == nil {
return
}
currUserName = snapshot?.data()!["userName"] as? String
distanceSetting = snapshot?.data()!["distanceSetting"] as? Int
db.collection("userActivities").document(String(currUserID + activity)).getDocument { (snapshot, error) in
//check for error
//MARK: changed snapshot to shapshot!.data() below (possible debug tip)
if error != nil {
//is error or no data..??
return
}
if snapshot!.data() == nil {
return
}
//get profile from data proprty of snapshot
if let uActivity = snapshot!.data() {
senderBestScore = uActivity["bestScore"] as? Int
senderMatchesPlayed = uActivity["played"] as? Double
senderMatchesWon = uActivity["wins"] as? Double
senderAverage = uActivity["averageScore"] as? Double
senderWinPercentage = round((senderMatchesWon! / senderMatchesPlayed!) * 1000) / 10
if snapshot != nil && error == nil {
let uActivitiesData = snapshot!.documents
for uActivity in uActivitiesData {
let userID = uActivity["userID"] as! String
UserService.determineDistance(otherUserID: userID) { (determinedDistance) in
if determinedDistance! <= distanceSetting! && userID != currUserID {
var x = Challenge()
//Sender
x.senderUserID = currUserID
x.senderUserName = currUserName
x.senderAverage = senderAverage
x.senderBestScore = senderBestScore
x.senderMatchesPlayed = senderMatchesPlayed
x.senderMatchesWon = senderMatchesWon
x.senderWinPercentage = senderWinPercentage
//Receiver
x.receiverUserID = userID
x.receiverUserName = uActivity["userName"] as? String
x.receiverAverage = uActivity["averageScore"] as? Double
x.receiverBestScore = uActivity["bestScore"] as? Int
if (uActivity["played"] as! Double) < 1 || (uActivity["played"] as? Double) == nil {
x.receiverMatchesPlayed = 0
x.receiverMatchesWon = 0
x.receiverWinPercentage = 0
} else {
x.receiverMatchesPlayed = uActivity["played"] as? Double
x.receiverMatchesWon = uActivity["wins"] as? Double
x.receiverWinPercentage = ((uActivity["wins"] as! Double) / (uActivity["played"] as! Double) * 1000).rounded() / 10
}
if uActivity["playStyle"] as? String == nil {
x.receiverPlayStyle = "~No PlayStyle~"
} else {
x.receiverPlayStyle = uActivity["playStyle"] as! String
}
x.activity = activity
//append to array
validUserActivities.append(x)
}
}
}
completion(validUserActivities)
}
}
}
}
}
}
}
I solved this by doing the following:
Add a constant before the for-in loop counting the total number of query results
let documentCount = snapshot?.documents.count
Add a counter before the for-in loop starting at 0
var runCounter = 0
Increment the counter with each iteration at the beginning of the for-in loop
runCounter += 1
Add code to the end of the for-in loop to call the completion handler to return the results
if runCounter == documentCount {
completion(validChallenges)
}
This is my code so far, with no errors, but it is not picking the dates from the 5 day forecast. What is wrong in this code?
//: to display the 5 day date array from the open weather API
enter code herevar temperatureArray: Array = Array()
var dayNumber = 0
var readingNumber = 0
if let jsonObj = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary {
if let mainArray = jsonObj!.value(forKey: "list") as? NSArray {
for dict in mainArray {
if let mainDictionary = (dict as! NSDictionary).value(forKey: "main") as? NSDictionary {
if let temperature = mainDictionary.value(forKey: "temp_max") as? Double {
if readingNumber == 0 {
temperatureArray.append(temperature)
} else if temperature > temperatureArray[dayNumber] {
temperatureArray[dayNumber] = temperature
}
} else {
print("Error: unable to find temperature in dictionary")
}
} else {
print("Error: unable to find main dictionary")
}
readingNumber += 1
if readingNumber == 8 {
readingNumber = 0
dayNumber += 1
}
var dateArray: Array<String> = Array()
var dayNumber = 0
var readingNumber = 0
if let weatherArray = jsonObj!.value(forKey: "list") as? NSArray {
for dict in weatherArray {
if let weatherDictionary = (dict as! NSDictionary).value(forKey: "list") as? NSDictionary {
if let date = weatherDictionary.value(forKey: "dt_txt") as? String {
if readingNumber == 0 {
dateArray.append(date)
} else if date > dateArray[dayNumber] {
dateArray[dayNumber] = date
}
}
} else {
print("Error: unable to find date in dictionary")
}
readingNumber += 1
if readingNumber == 8 {
readingNumber = 0
dayNumber += 1
}
}
}
}
}
}
func fixTempForDisplay(temp: Double) -> String {
let temperature = round(temp)
let temperatureString = String(format: "%.0f", temperature)
return temperatureString
}
DispatchQueue.main.async {
self.weatherLabel1.text = "Today: (fixTempForDisplay(temp: temperatureArray[0]))°C"
self.weatherLabel2.text = "Tomorrow: (fixTempForDisplay(temp: temperatureArray[1]))°C"
self.weatherLabel3.text = "Day 3: (fixTempForDisplay(temp: temperatureArray[2]))°C"
self.weatherLabel4.text = "Day 4: (fixTempForDisplay(temp: temperatureArray[3]))°C"
self.weatherLabel5.text = "Day 5: (fixTempForDisplay(temp: temperatureArray[4]))°C"
func formatDate(date: NSDate) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
return dateFormatter.string(from: date as Date)
}
self.dateLabel1.text = ": \(formatDate(date: dateArray[0]))"
self.dateLabel2.text = ": \(formatDate(date: dateArray[1]))"
self.dateLabel3.text = ": \(formatDate(date: dateArray[2]))"
self.dateLabel4.text = ": \(formatDate(date: dateArray[3]))"
self.dateLabel5.text = ": \(formatDate(date: dateArray[4]))"
}
}
}
dataTask.resume()
}
}
It looks to me like you need to change your icon array to contain strings
var iconArray: Array<String> = Array()
and then when parsing the json text
if let icon = weatherDictionary.value(forKey: "icon") as? String {
and finally
self.iconImage1.image = UIImage(named: iconArray[0])
self.iconImage2.image = UIImage(named: iconArray[1])
Of course the below comparison won't work anymore when icon is a string but I don't understand any of this if/else clasue so I don't know what to replace it with
if readingNumber == 0 {
iconArray.append(icon)
} else if icon > iconArray[dayNumber] { //This won't work now.
iconArray[dayNumber] = icon
}
I'm facing this strange problem. I have to parse this json and extract "symbol","High","low" and "direction". this is the original json
[{"ID":101,"Symbol":"PKR","Bid":105.7,"Ask":106,"High":105.7,"Low":106,"Change":0,"Direction":"0","CreateDate":"04:38:26","EntityState":2,
"EntityKey":{"EntitySetName":"v_openmarketrates","EntityContainerName":"tradebizEntities",
"EntityKeyValues":[{"Key":"ID","Value":101}],
"IsTemporary":false}},
{"ID":1,"Symbol":"EUR","Bid":126.696,"Ask":127.327,"High":126.7622,"Low":126.9752,"Change":0.4192,"Direction":"0","CreateDate":"06:37:31","EntityState":2,
"EntityKey":{"EntitySetName":"v_openmarketrates","EntityContainerName":"tradebizEntities","EntityKeyValues":[{"Key":"ID","Value":1}],
"IsTemporary":false}}]
When i'm parsing this in json, it is fetching all the values correctly except the value of "Direction",like this:
[{
Ask = 106;
Bid = "105.7";
Change = 0;
CreateDate = "04:38:26";
Direction = 0;
EntityKey = {
EntityContainerName = tradebizEntities;
EntityKeyValues = (
{
Key = ID;
Value = 101;
}
);
EntitySetName = "v_openmarketrates";
IsTemporary = 0;
};
EntityState = 2;
High = "105.7";
ID = 101;
Low = 106;
Symbol = PKR;
},
{
Ask = "127.265";
Bid = "126.623";
Change = "0.3463";
CreateDate = "06:30:46";
Direction = 0;
EntityKey = {
EntityContainerName = tradebizEntities;
EntityKeyValues = (
{
Key = ID;
Value = 1;
}
);
EntitySetName = "v_openmarketrates";
IsTemporary = 0;
};
EntityState = 2;
High = "126.7306";
ID = 1;
Low = "126.9752";
Symbol = EUR;
}
i have no idea how the value inside json is getting changed while parsing even though rest of the values are correct. this is how i'm parsing it.
if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? [NSDictionary] {
print("kerb rates full json = ",jsonResult )
for field in jsonResult as? [AnyObject] ?? [] {
print("fields of kerb rates = \(field)")
print("kerb directions \((field["Direction"] as? String)!)")
let subfield : AnyObject = (field["EntityKey"] as? AnyObject)!
let sub_subfield : AnyObject = (subfield["EntityKeyValues"] as? AnyObject)!
print("sub_subfield = \(sub_subfield)")
print("subfields = \(subfield)")
// for key_Subfield in sub_subfield as? [AnyObject] ?? [] {
print("inside loop!")
// converting int and bool values
let ask = (field["Ask"] as? Int)!
let bid = (field["Bid"] as? Int)!
let change = (field["Change"] as? Int)!
let EntityState = (field["EntityState"] as? Int)!
let High = (field["High"] as? Double)!
let ID = (field["ID"] as? Int)!
let IsTemporary = ""//(subfield["IsTemporary"] as? Bool)!
let Low = (field["Low"] as? Double)!
let Value = ""//(key_Subfield["Value"] as? Int)!
// it is crashing here due to multple dictionaries
self.Save_KerbRates(ask: (String(ask)),
bid: (String(bid)),
change: (String(change)),
createDate: (field["CreateDate"] as? String)!,
direction: (field["Direction"] as? String)!,
entityContainerName: "",//(subfield["EntityContainerName"] as? String)!,
entitiySetName:"",// (subfield["EntitySetName"] as? String)!,
entitiyState: (String(EntityState)),
high: (String(High)),
id: (String(ID)),
isTemporary: (String(IsTemporary)),
key:"",// (key_Subfield["Key"] as? String)!,
low: (String(Low)),
symbol: (field["Symbol"] as? String)!,
value: (String(Value)))
// }
}
Update:
After using [[String:Any]]
i'm still getting the wrong value of direction,like this
kerb rates full json = [["Low": 106, "Direction": 0, "EntityState": 2, "EntityKey": {
EntityContainerName = tradebizEntities;
EntityKeyValues = (
{
Key = ID;
Value = 101;
}
);
EntitySetName = "v_openmarketrates";
IsTemporary = 0;
}, "ID": 101, "CreateDate": 04:38:26, "Symbol": PKR, "Change": 0, "Ask": 106, "High": 105.7, "Bid": 105.7], ["Low": 126.9752, "Direction": -1, "EntityState": 2, "EntityKey": {
EntityContainerName = tradebizEntities;
EntityKeyValues = (
{
Key = ID;
Value = 1;
}
);
EntitySetName = "v_openmarketrates";
IsTemporary = 0;
}, "ID": 1, "CreateDate": 07:03:46, "Symbol": EUR, "Change": 0.4403, "Ask": 127.349, "High": 126.7654, "Bid": 126.717],
This parses (and prints) all values in the first level of the dictionary and the value for keys IsTemporary and Value in the second level. All variable names start with a lowercase letter to conform to the naming convention.
The code uses a type alias for convenience.
Be aware that the code does not use any error handling to check the dictionary keys. If it is not guaranteed that all items contain all keys and values you have to use optional bindings.
typealias JSONDictionary = [String:Any]
do {
if let jsonResult = try JSONSerialization.jsonObject(with:data!) as? [JSONDictionary] {
for field in jsonResult {
let ask = field["Ask"] as! Double
let bid = field["Bid"] as! Double
let change = field["Change"] as! Double
let createDate = field["CreateDate"] as! String
let direction = field["Direction"] as! String
let entityState = field["EntityState"] as! Int
let high = field["High"] as! Double
let id = field["ID"] as! Int
let low = field["Low"] as! Double
let symbol = field["Symbol"] as! String
let entityKey = field["EntityKey"] as! JSONDictionary
let isTemporary = entityKey["IsTemporary"] as! Bool
let entityKeyValues = entityKey["EntityKeyValues"] as! [JSONDictionary]
let value = entityKeyValues[0]["Value"] as! Int
print(ask, bid, change, createDate, direction, entityState, high, id, low, symbol, isTemporary, value)
}
}
} catch {
print(error)
}
I am querying a JSON database of zombies and it returns them as a Dictionary. I don't know how to mutate it with SWIFT 3
Here's the query::
func getZombieAttackGroupFromDatabase () {
ref?.child("Attacker Group").child((self.userParty?.leadID)!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get data
print("PULLING DATA")
if let value = snapshot.value as? NSDictionary{
// break data into an Array of Zombie Structs
// print(value)
for zombieID in value.allKeys {
print(value[zombieID])
let thisZombieID = zombieID
let thisZombieGroup = value[zombieID]["Group"]
}
} else {
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
this part: let thisZombieGroup = value[zombieID]["Group"] isn't being recognized. How do I access group? If i get that, i can modify to the other components.
Here's the return :
{
"-KrNSmv64Ia32g5nw1L9" = {
Group = 15;
Health = 250;
"Is Undead" = true;
Location = 1;
Number = 0;
};
"-KrNSmv64Ia32g5nw1LA" = {
Group = 11;
Health = 250;
"Is Undead" = true;
Location = 5;
Number = 1;
};
"-KrNSmv64Ia32g5nw1LB" = {
Group = 2;
Health = 250;
"Is Undead" = true;
Location = 3;
Number = 2;
};
"-KrNSmv776r9eO6t7CY0" = {
Group = 14;
Health = 250;
"Is Undead" = true;
Location = 0;
Number = 3;
};
"-KrNSmv776r9eO6t7CY1" = {
Group = 0;
Health = 250;
"Is Undead" = true;
Location = 4;
Number = 4;
};
}
As you can see, each of the Structs has a parent that is an automatically generated ID. I don't know how to access it.
How can I access each element from item 1? I need the parent auto-key "-KrNSmv64Ia32g5nw1L9" and each child value.
"-KrNSmv64Ia32g5nw1L9" = {
Group = 15;
Health = 250;
"Is Undead" = true;
Location = 1;
Number = 0;
Cast value to a proper Swift dictionary, not NSDictionary.
if let value = snapshot.value as? [String:Any].
You just have to iterate through the keys of the dictionaries, get the embedded dictionary using the key value and then parse the "zombie data".
for key in value.keys {
if let zombieData = value[key] as? [String:Any] {
zombieData
if let group = zombieData["Group"] as? Int, let health = zombieData["Health"] as? Int, let isUndead = zombieData["Is Undead"] as? Bool, let location = zombieData["Location"] as? Int, let number = zombieData["Number"] as? Int {
//use the parsed data
}
}
}
Try this:
for zombieID in value.allKeys {
print(value[zombieID])
let thisZombieID = zombieID
if let zombieValues = value[zombieID] as? [String:Any] {
let thisZombieGroup = zombieValues["Group"] as? Int
}
}
First, Thank you so much to Woof and David. It was a combination of both of your ideas that got it to work.
func getZombieAttackGroupFromDatabase () {
ref?.child("Attacker Group").child((self.userParty?.leadID)!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get data
print("PULLING DATA")
if let value = snapshot.value as? [String:Any]{
// break data into an Array of Zombie Structs
for zombieID in value.keys {
let thisZombieID = zombieID
print(thisZombieID)
let zombieValues = value[zombieID] as? [String:Any]
let thisZombieGroup = zombieValues?["Group"] as! String
print(thisZombieGroup)
}
} else {
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
I have the following array which is passed from an API call from a PHP Script:
["playerForm": {
1 = (
{
date = "2017-01-31";
name = Dicky;
result = L;
"results_id" = 42;
},
{
date = "2016-12-24";
name = Dicky;
result = W;
"results_id" = 19;
}
);
2 = (
{
date = "2017-01-25";
name = G;
result = W;
"results_id" = 38;
},
{
date = "2017-01-25";
name = G;
result = D;
"results_id" = 40;
},
{
date = "2017-01-21";
name = G;
result = L;
"results_id" = 34;
}
);
3 = (
{
date = "2017-01-31";
name = Sultan;
result = W;
"results_id" = 42;
},
{
date = "2017-01-15";
name = Sultan;
result = L;
"results_id" = 30;
}
);
}]
However I cannot seem to access the 1,2,3 parts of it...
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:AnyObject]
print (json!)
if let dict = json?["playerForm"] as? [String:AnyObject] {
print ("step 1")
if let arr = dict["1"] as? [[String:String]] {
print ("step 2")
self.leagueForm = arr.flatMap { Form($0) }
print (self.leagueForm)
print (self.leagueForm.count)
for i in 0..<self.leagueForm.count {
let form = self.leagueForm[i]
print (form.player_results!)
self.formGuide.append(form.player_results!)
}
print ("now")
print (self.formGuide)
self.resultsDisplay.results = self.formGuide
self.resultsDisplay.results = self.resultsDisplay.results.reversed()
self.resultsDisplay.displayResults()
}
}
With this code above it only gets as far as Step 1.
What am I doing wrong?
UPDATED WITH PROGRSS*
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:AnyObject]
print (json!)
if let dict = json?["playerForm"] as? [String:AnyObject] {
print ("step 1")
for (_ , value) in dict {
if let arr = value as? [[String:Any]] {
print(arr)
//your code
}
}
}
Custom Struct to define array:
struct Form {
var player_result: String?
var player_name: String?
var result_date: String?
var result_id: String?
init(_ dictionary: [String : Any]) {
self.player_result = dictionary["result"] as? String ?? ""
self.player_name = dictionary["name"] as? String ?? ""
result_date = dictionary["date"] as? String ?? ""
result_id = String(dictionary["results_id"] as? Int ?? 0)
}
}
Change your array type [[String:String]] to [[String:Any]] because it contains both String and Number as value.
if let arr = dict["1"] as? [[String:Any]] {
print(arr)
//your code
}
Note: You need to loop through the dict Dictionary because its each key having array.
for (_ , value) in dict {
if let arr = value as? [[String:Any]] {
print(arr)
//your code
}
}