This forEach loop works sometimes and sometimes it skips. I am not sure what I am doing wrong here. The loop will skip the last item and will never exit. So the completion block does not get fired at all.
I am using firebase, Eureka forms and it's ImageRow extension.
I would appreciate some help here.
//MARK: - Get Form Values
var returnedValues: [String: Any] = [:]
fileprivate func getFormValues(values: [String: Any], completion: #escaping ([String:Any])->()) {
if let name = values["name"] as? String,
let description = values["description"] as? String,
let images = values["images"] as? [UIImage],
let category = values["category"] as? String,
let price = values["price"] as? Double,
let deliveryFee = values["deliveryFee"] as? Double,
let deliveryAreas = values["deliveryArea"] as? Set<String>,
let deliveryTime = values["deliveryTime"] as? String {
guard let uid = Auth.auth().currentUser?.uid else { return }
var imagesData = [[String: Any]]()
var counter = 0
images.forEach({ (image) in
let imageName = NSUUID().uuidString
let productImageStorageRef = Storage.storage().reference().child("product_images").child(uid).child("\(imageName).jpg")
var resizedImage = UIImage()
if image.size.width > 800 {
resizedImage = image.resizeWithWidth(width: 800)!
}
if let uploadData = UIImageJPEGRepresentation(resizedImage, 0.5) {
productImageStorageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("Failed to upload image: \(error?.localizedDescription ?? "")")
return
}
//Successfully uploaded product Image
print("Successfully uploaded product Image")
if let productImageUrl = metadata?.downloadURL()?.absoluteString {
counter += 1
let imageData: [String: Any] = [imageName: productImageUrl]
imagesData.append(imageData)
if counter == images.count {
let deliveryAreasArr = Array(deliveryAreas)
self.returnedValues = ["name": name, "description": description, "images": imagesData , "category": category, "price": price, "deliveryFee": deliveryFee, "deliveryArea": deliveryAreasArr, "deliveryTime": deliveryTime, "creationDate": Date().timeIntervalSince1970, "userId": uid]
completion(self.returnedValues)
}
}
})
}
})
} else {
let alert = UIAlertController(title: "Missing Information", message: "All fields are required. Please fill all fields.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
alert.dismiss(animated: true, completion: nil)
}))
UIActivityIndicatorView.stopActivityIndicator(indicator: self.activityIndicator, container: self.activityIndicatorContainer, loadingView: self.activityIndicatorLoadingView)
self.present(alert, animated: true, completion: nil)
}
}
There are a number of if statements inside your for loop that can result in counter not being incremented. If any of these fail then you will never call the completion handler.
I understand that you are using the counter in an attempt to know when all of the asynchronous tasks are complete, but a dispatch group is a better solution for this.
It is also important that your completion handler is called in all paths; such as when the initial guard fails or in the else clause of the initial if - Your completion handler should probably accept an Error parameter so that it knows that there was a problem.
//MARK: - Get Form Values
fileprivate func getFormValues(values: [String: Any], completion: #escaping ([String:Any]?)->()) {
var returnedValues: [String: Any] = [:]
if let name = values["name"] as? String,
let description = values["description"] as? String,
let images = values["images"] as? [UIImage],
let category = values["category"] as? String,
let price = values["price"] as? Double,
let deliveryFee = values["deliveryFee"] as? Double,
let deliveryAreas = values["deliveryArea"] as? Set<String>,
let deliveryTime = values["deliveryTime"] as? String {
guard let uid = Auth.auth().currentUser?.uid else {
completion(nil)
return
}
var imagesData = [[String: Any]]()
let dispatchGroup = DispatchGroup() // Create a Dispatch Group
images.forEach({ (image) in
let imageName = NSUUID().uuidString
let productImageStorageRef = Storage.storage().reference().child("product_images").child(uid).child("\(imageName).jpg")
var resizedImage = UIImage()
if image.size.width > 800 {
resizedImage = image.resizeWithWidth(width: 800)!
}
if let uploadData = UIImageJPEGRepresentation(resizedImage, 0.5) {
dispatchGroup.enter() // Enter the group
productImageStorageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
guard error == nil else {
print("Failed to upload image: \(error?.localizedDescription ?? "")")
dispatchGroup.leave() // Leave the dispatch group if there was an error
return
}
//Successfully uploaded product Image
print("Successfully uploaded product Image")
if let productImageUrl = metadata?.downloadURL()?.absoluteString {
let imageData: [String: Any] = [imageName: productImageUrl]
imagesData.append(imageData)
}
dispatchGroup.leave() // Leave the dispatch group in normal circumstances
})
}
})
// Schedule a notify closure for execution when the dispatch group is empty
dispatchGroup.notify(queue: .main) {
let deliveryAreasArr = Array(deliveryAreas)
returnedValues = ["name": name, "description": description, "images": imagesData , "category": category, "price": price, "deliveryFee": deliveryFee, "deliveryArea": deliveryAreasArr, "deliveryTime": deliveryTime, "creationDate": Date().timeIntervalSince1970, "userId": uid]
completion(self.returnedValues)
}
} else {
completion(nil)
let alert = UIAlertController(title: "Missing Information", message: "All fields are required. Please fill all fields.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
alert.dismiss(animated: true, completion: nil)
}))
UIActivityIndicatorView.stopActivityIndicator(indicator: self.activityIndicator, container: self.activityIndicatorContainer, loadingView: self.activityIndicatorLoadingView)
self.present(alert, animated: true, completion: nil)
}
}
Some other points:
It would be better to pass structs rather than dictionaries. Using a struct for your input would get rid of that massive if let at the start of your function since you would know the types of the values and by making them non-optional properties of the struct you would know that the values were present.
It is unusual for a function such as this to present an alert; it would normally just return an error via the completion or perhaps throw back to the caller to indicate that there was a problem and let the caller handle it
I don't see why imagesData needs to be an array of dictionaries. Each dictionary in the array only has one entry, so you could just use a dictionary of [String:String] (There is no need to use Any when you know what the type will be.
Related
My requirement : I will be having a list of chat details in a table view.On top of table view ,there will be a search functionality using text field.based on unique id of user, search should get done.if there is no chat with the unique id, which the user entered,then it has to redirect to another screen which is called chatcreatepage. when ever we are searching chat, we will be using an api called FIND API and in that FIND API there is a chat dictionary,if it is null,then create chat will get called.If that chat dictionary is not nil then need to display that chat details in chat list table view. When the chat list page is loading then ,we will be calling Chat list Api.when we are searching the chat by entering the unique id in textfield,we will be getting the corresponding details of that entered unique id & that unique id details we have to show in the table view.
This is the task and i have done till the chat list showing in the table.FIND API Integration is also done.When i am reloading the data with search result(find api response),I am getting fatal error like "Unexpectedly found nil while unwrapping an Optional value" at the line of "var recepient = dict["recipient"] as! [String:Any]" . If anyone helps me to solve it, would be really great.Thank in advance.I am providing the code below.
import UIKit
import Alamofire
import SwiftyJSON
import SDWebImage
class ChatlistViewController: UIViewController{
var pro = [[String:Any]]()
var dict:[String:Any]!
var idd = ""
var id = ""
var chatt:Dictionary = [String:Any]()
var searchActive : Bool = false
var filtered:[String] = []
var data:[String] = []
#IBOutlet weak var searchtext: UITextField!
#IBOutlet weak var chatlisttable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
apicall()
}
func apicall(){
let acce:String = UserDefaults.standard.string(forKey: "access-tokenn")!
print(acce)
let headers:HTTPHeaders = ["Authorization":"Bearer \(acce)","Content-Type":"application/X-Access-Token"]
Alamofire.request(Constants.Chatlist, method: .get, encoding: URLEncoding.default, headers: headers).responseJSON { response in
switch response.result {
case .success:
print(response)
if response.result.value != nil{
var maindictionary = NSDictionary()
maindictionary = response.result.value as! NSDictionary
print(maindictionary)
var userdata = NSDictionary()
userdata = maindictionary.value(forKey: "data") as! NSDictionary
var productsdetails = [[String:Any]]()
productsdetails = userdata.value(forKey: "chat") as! [[String:Any]]
self.pro = productsdetails
print(self.pro)
self.chatlisttable.reloadData()
}else{
let Alertcontroller = UIAlertController(title: "Alert", message: "No data found ", preferredStyle: .alert)
let CancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
Alertcontroller.addAction(CancelAction)
self.present(Alertcontroller, animated: true, completion: nil)
}
break
case .failure(let error):
print(error)
}
}
}
func searchapicall(){
idd = searchtext.text!
let acce:String = UserDefaults.standard.string(forKey: "access-tokenn")!
print(acce)
let headers:HTTPHeaders = ["Authorization":"Bearer \(acce)","Content-Type":"application/X-Access-Token"]
print((Constants.Chatlistsearch)+(idd))
Alamofire.request((Constants.Chatlistsearch+idd), method: .get, encoding: URLEncoding.default, headers: headers).responseJSON { response in
switch response.result {
case .success:
//print(response)
if response.result.value != nil{
var maindictionary = NSDictionary()
maindictionary = response.result.value as! NSDictionary
var chat:Dictionary = maindictionary.value(forKey: "data") as! [String:Any]
var chattt:Dictionary = chat["chat"] as! [String:Any]
if (chattt != nil) {
// print("Find Action")
self.chatt = chat["user"] as! [String:Any]
print(self.chatt)
self.pro = [self.chatt]
// print(self.pro)
self.chatlisttable.reloadData()
}else{
let viewc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "ChatViewController") as? ChatViewController
self.navigationController?.pushViewController(viewc!, animated: true)
}
}else{
let Alertcontroller = UIAlertController(title: "Alert", message: "No data found on this unique id", preferredStyle: .alert)
let CancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
Alertcontroller.addAction(CancelAction)
self.present(Alertcontroller, animated: true, completion: nil)
}
break
case .failure(let error):
print(error)
}
}
}
}
extension ChatlistViewController: UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.searchapicall()
return true
}
}
extension ChatlistViewController: UITableViewDataSource,UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (searchActive == false){
return self.pro.count
}else{
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = chatlisttable.dequeueReusableCell(withIdentifier: "ChatlistTableViewCell", for: indexPath) as! ChatlistTableViewCell
if (searchActive == false){
dict = pro[indexPath.row]
var recepient = dict["recipient"] as! [String:Any]
print(recepient)
var name = recepient["name"] as! String
print(name)
id = recepient["unique_id"] as! String
print(id)
var image = recepient["avatar"] as! String
print(image)
cell.namelbl.text = name
cell.idlbl.text = id
cell.imageView!.sd_setImage(with: URL(string:image), placeholderImage: UIImage(named: "Mahi.png"))
}else{
cell.namelbl.text = chatt["name"] as! String
cell.idlbl.text = chatt["unique_id"] as! String
}
return cell
self.chatlisttable.reloadData()
}
}
//Response format
{
"success": 1,
"status": 200,
"data": {
"user": {
"id": 3,
"unique_id": "10002",
"name": "nani",
"avatar": "https://www.planetzoom.co.in/storage/user/avatar/AkgcUFF3QIejMhZuLF4OXnSFHjxNAOo4FuXV3Mgi.jpeg"
},
"chat": null
}
}
//Response with chat dictionary data
{
"success": 1,
"status": 200,
"data": {
"user": {
"id": 8,
"unique_id": "10007",
"name": "Mahitha",
"avatar": "https://www.planetzoom.co.in/storage/user/avatar/cZt9yQlBzIEewOdQ1lYZhl3dFiOv2k3bxG7HLOzR.jpeg"
},
"chat": {
"id": 4,
"status": 0,
"created_at": "2019-02-27 12:26:24",
"updated_at": "2019-02-27 12:26:24"
}
}
}
You should update you code to get chat details as follow due to I have seen chat in nil in your response
if var productsdetails = userdata.value(forKey: "chat") as? [[String:Any]] {
// Code to display chat
} else {
// Code to display nil error
}
I hope this will fix you issue.
From your response chat is null. So productsdetails = userdata.value(forKey: "chat") as! [[String:Any]] you are doing like that, you have set a null value in userdefaults across the key 'chat', and you are retrieving the null value as [[String : Any]]
So you have to check this using the block
if let productsdetails = userdata.value(forKey: "chat") as? [[String:Any]] {
// write code that for chat
} else {
// write code for chat is null
}
This is called Optional Chaining, you may find this link
Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil.
Or you may solve using guard statement
guard let productsdetails = userdata.value(forKey: "chat") as? [[String:Any]] else {
// write code for chat is null
return
}
\\ write code that for chat
\\ you can use productsdetails variable here
I would like to provide two feedback to you:
Please try to make a habit to avoid forced casting or forced unwrap how much ever is possible.
Use Camel Case Naming Convention
So, you can change
var recepient = dict["recipient"] as! [String:Any]
to
guard let recepient = dict["recipient"] as? [String:Any] else {return cell}
Similar way you can have a condition before loading the tableView
if let productsDetails = userdata.value(forKey: "chat") as? [[String:Any]] {
// write code
} else {
// write code to handle else
}
when I parse My json response
and this my error
Could not cast value of type '__NSArray0' (0x104bcd838) to 'NSDictionary' (0x104bcf818).
this is my code
#IBAction func LoginAction(_ sender: Any) {
let parameters: Parameters=[
"email":emailUser.text!,
"password":passWord.text!,
]
let url2 = "http://marwen1994.alwaysdata.net/Carpooling/public/loginpost.php"
Alamofire.request(url2, method: .post, parameters: parameters).responseJSON
{
response in
let A = response.result.value as! Dictionary<String,Any>
let list = A["items"] as! Dictionary<String,Any>
let nom = list["name"] as! String
let pass = A["password"] as! String
let email = list["email"] as! String
let adresse = A["adresse"] as! String
let DateNaissance = A["DateNaissance"] as! String
let id = A["id"] as! Int
let numTel = A["num_tel"] as! Int
if(pass == self.passWord.text)
{
UserDefaults.standard.set(true, forKey: "ConnectionStatus")
UserDefaults.standard.set(id, forKey: "ConnectedID")
UserDefaults.standard.set(nom, forKey: "nom")
// UserDefaults.standard.set(prenom, forKey: "prenom")
UserDefaults.standard.set(email, forKey: "email")
UserDefaults.standard.set(adresse, forKey: "adresse")
UserDefaults.standard.set(pass, forKey: "motDePasse")
UserDefaults.standard.set(numTel, forKey: "numTel")
UserDefaults.standard.set(DateNaissance, forKey: "DateNaissance")
self.performSegue(withIdentifier: "toProfile", sender: nil)
}
else{
let alert = UIAlertController(title: "Woah!!!", message: "You inserted a wrong email or a wrong password! Please enter a valid mail and password.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
}
}
Hard to know where the error is without seeing the actual JSON response, but probably this is an Array and not a Dictionary
let list = A["items"] as! Dictionary<String,Any>
Should be something like
let list = A["items"] as! Array
If you could consider using Codable this might help
struct JsonResponse: Decodable {
let items: [Item]
}
struct Item: Decodable {
let id: String
let name: String
let email: String
let encrypted_password: String
let num_tel: String
let adresse: String
let DateNaissance: String
}
The actual decoding is done like this
let decoder = JSONDecoder()
do {
let jsonResponse = try decoder.decode(JsonResponse.self, from: data)
for item in jsonResponse.items {
UserDefaults.standard.set(item.name, forKey: "nom")
UserDefaults.standard.set(item.email, forKey: "email")
//and so on
}
} catch {
print("Decode error: \(error)")
}
data is the json message in response, I don't know how Alamofire works so you need to figure out yourself how to access it correctly.
There is an array inside "items" with a single element, access it like
if let A = response.result.value as? Dictionary<String,Any>, let list = A["items"][0] as? Dictionary<String,Any> {
}
func teacherExists(teacherName: String) -> Bool
{
var dataBaseRef2: DatabaseReference!
dataBaseRef2 = Database.database().reference()
let teachersTableRef = dataBaseRef2.child("teachers")
self.teachersList.removeAll()
teachersTableRef.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
// teachersTableRef.observe(.value)
//{
// snapshot in
let teachersNamesDictionary = snapshot.value as? [String: Any] ?? [:]
for(key, _) in teachersNamesDictionary
{
if let teacherDict = teachersNamesDictionary[key] as? [String: Any]
{
if let teacher = Teacher(dictionary: teacherDict)
{
//print(teacher.teacher_name)
self.teachersList.append(teacher.teacher_name)
}
}
}
print(self.teachersList.count)
})
print("Outside \(self.teachersList)")
return false
}
Because Firebase APIs are all asynchronous. It would be bad for your app if they blocked your code path, because that could cause your app to hang indefinitely.
observeSingleEvent returns immediately, and the passed observer gets invoked some time later, whenever the data is finally ready. Execution continues on the next line, which prints to the console.
getting error upon calling teacherExists function
let OKAction = UIAlertAction(title: "OK", style: .default, handler:
{
(action: UIAlertAction!) ->Void in
let textfield = alert.textFields![0] as UITextField
newTeacherName = textfield.text!.uppercased()
if !(newTeacherName.isEmpty)
{
//checking if teacher already exists using function teacherExists
let exists = self.teacherExists(teacherName: newTeacherName, completion:
if exists == true //if duplicate teacher is found
{
let alert = UIAlertController(title: "Duplicate Teacher", message: "Teacher \(newTeacherName) has been added earlier", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
else
{
//add teacher to database here
let dict = ["teacher_name" : newTeacherName]
let newTeacher = Teacher(dictionary: dict)
let tableRef = self.dataBaseRef.child("teachers") //getting reference of node with name teachers
let recordRef = tableRef.childByAutoId() //creating a new record in teachers node
recordRef.setValue(newTeacher!.toDictionary())//adding data to new record in teachers node
}
}
})
You can use closure to callback after check for duplicate
func teacherExists(teacherName: String, completion: #escaping ((Bool) -> Void)) -> Void {
var dataBaseRef2: DatabaseReference!
dataBaseRef2 = Database.database().reference()
let teachersTableRef = dataBaseRef2.child("teachers")
self.teachersList.removeAll()
teachersTableRef.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
let teachersNamesDictionary = snapshot.value as? [String: Any] ?? [:]
for(key, _) in teachersNamesDictionary
{
if let teacherDict = teachersNamesDictionary[key] as? [String: Any]
{
if let teacher = Teacher(dictionary: teacherDict)
{
//print(teacher.teacher_name)
self.teachersList.append(teacher.teacher_name)
}
}
}
let exists = self.teachersList.contains(teacherName)
completion(exists)
})
}
And call function as below
teacherExists(teacherName: newTeacherName) { (exists) in
if exists {
// show alert
} else {
// add new teacher to db
}
}
Hope it help!
Everything was working fine, then I deleted some old messages and conversations from My Firebase Database. Now every time I send a message I get a crash. I deleted all old users and created new users and tried to send messages and I still keep getting a crash. I am not sure what can be causing this. Any suggestions will be helpful. It first happened after I tested out this function to delete the table cell...
func deleteConversation(_ conversation:Conversation) {
guard let user = Auth.auth().currentUser else { return }
let ref = Database.database().reference()
let obj = [
"conversations/users/\(user.uid)/\(conversation.partner_uid)/muted": true
] as [String:Any]
print("OBBJ: \(obj)")
ref.updateChildValues(obj, withCompletionBlock: { error, ref in
if error != nil {
let alert = UIAlertController(title: "Error deleting conversation!", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
} else {
let alert = UIAlertController(title: "Conversation deleted!", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
}
})
}
func downloadMessages() {
self.messages = []
downloadRef?.observe(.childAdded, with: { snapshot in
let dict = snapshot.value as! [String:AnyObject]
if let sender = dict["sender"] as! String!, let recipient = dict["recipient"] as! String!, let text = dict["text"] as! String!, text.characters.count > 0 {
let timestamp = dict["timestamp"] as! Double
let date = NSDate(timeIntervalSince1970: timestamp/1000)
let message = JSQMessage(senderId: sender, senderDisplayName: "", date: date as Date!, text: text)
self.messages.append(message!)
self.reloadMessagesView()
self.finishReceivingMessage(animated: true)
}
else if let id = dict["sender"] as! String!,
let photoURL = dict["imageUrl"] as! String!, photoURL.characters.count > 0 { // 1
// 2
if let mediaItem = JSQPhotoMediaItem(maskAsOutgoing: id == self.senderId) {
// 3
let timestamp = dict["timestamp"] as! Double
let date = NSDate(timeIntervalSince1970: timestamp/1000)
if let message = JSQMessage(senderId: id, senderDisplayName: "", date: date as Date!, media: mediaItem) {
self.messages.append(message)
if (mediaItem.image == nil) {
self.photoMessageMap[snapshot.key] = mediaItem
}
self.collectionView.reloadData()
}
if photoURL.hasPrefix("gs://") {
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: nil)
}
}
}
else {
print("Error! Could not decode message data")
}
})
// We can also use the observer method to listen for
// changes to existing messages.
// We use this to be notified when a photo has been stored
// to the Firebase Storage, so we can update the message data
updatedMessageRefHandle = downloadRef?.observe(.childChanged, with: { (snapshot) in
let key = snapshot.key
let messageData = snapshot.value as! Dictionary<String, String> // 1
if let photoURL = messageData["imageUrl"] as String! { // 2
// The photo has been updated.
if let mediaItem = self.photoMessageMap[key] { // 3
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: key) // 4
}
}
})
}
It's very likely the error is a result of force casting - as!
Instead of
let messageData = snapshot.value as! Dictionary<String, String>
do
guard let messageData = snapshot.value as? Dictionary<String, String> else { return }
Your snapshot.value is either nil, or is not an instance of Dictionary<String, String>, and force casting it to such will result in crash.
You should also read more about optionals and type casting in Swift, because you use ! a lot, and not once in your program is it used correctly.
So I am really new to threading and I've been reading up on it all day. For some reason though the data isn't loading before other code executes
Basically I need all the values that have a key ["whatever"] to be filled into an array, which works in other places because I don't need to load it first. So i have checked and double checked the keys that I am updating do exist and the keys I am extracting do exist maybe not the values yet but the keys do.
The problem is the code goes to fast to through the method. How would I make the main thread wait untill my firebase has loaded the data I have tried it below but it does not seem to be working
here is my code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + amountBets[indexPath.row], preferredStyle: .alert)
let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
// let them know to wait a second or the bet won't go through
var waitController = UIAlertController(title: "Please Wait", message: "You must wait for the bet to go through", preferredStyle: .alert)
self.present(waitController, animated: true, completion: nil)
//take away that bitches money
self.takeAwayMoney(self.amountBets[indexPath.row], completion: { (result: Bool?) in
guard let boolResult = result else {
return
}
if boolResult == true {
self.updateBet(indexPath.row, completion: {(result: String?) in
guard let resultRecieved = result else {
return
}
print(self.opposingUserNames)
//let delayInSeconds = 7.0 // 1
//DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { // 2
self.dismiss(animated: true, completion: nil)
let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.opposingUserNames!, preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
successController.addAction(okButt)
self.present(successController, animated: true, completion: nil)
//lastly delete the opposing UserName
print(self.opposingUserNames)
self.amountBets.remove(at: indexPath.row)
self.tableView.reloadData()
print("Second")
print(self.opposingUserNames)
//}
})
} else {
return
}
})
//then delete that cell and do another pop up that says successful
// check if value is yes or no in the database
})
alertController.addAction(okButton)
alertController.addAction(yesButton)
present(alertController, animated: true, completion: nil)
}
The below function updates the values OpposingUsername and show
func updateBet(_ index: Int, completion: #escaping (_ something: String?) -> Void) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
let values = ["OpposingUsername": self.userName,"Show": "no"]
self.datRef.child("Bets").child(self.tieBetToUser[index]).updateChildValues(values)
// now get the opposing username which is just the Username registered to that specific bet
self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
guard let dict2 = snapshot.value as? [String: AnyHashable] else {
return
}
let userNameOfOtherPlayer = dict2["Username"] as? String
self.opposingUserNames = userNameOfOtherPlayer!
completion(self.opposingUserNames)
})
})
}) { (error) in
print(error.localizedDescription)
}
}
ok so with this updated code it cuts out the logic errors I had earlier, but now the app hangs on my waitAlertViewController. Not sure why. it does updated the bet in the firebase database so I know its working and running that code but its like never completing it all. sorry bibscy I see what you mean now
completion handlers are pretty powerful once you understand them better
//Notice that I made `result: String?` optional, it may or may not have a value.
func getOpoosingUserNames(_ username: String,_ index: Int, completion: #escaping (_ result: String?) -> Void ) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
self.userName = username
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
let betId = snapshot.key as String
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
if let show = dict["Show"] as? String {
let opposingUser = dict["OpposingUsername"] as? String
self.opposingUserNames.append(opposingUser!)
}
completion(opposingUserNames)
})
}) { (error) in
print(error.localizedDescription)
}
}
//update the be
func updateBet(_ index: Int, completion: #escaping (_ something: [String]?) -> Void) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
let values = ["OpposingUsername": self.userName,"Show": "no"]
//store the values received from Firebase in let valueOfUpdate and pass this
// constant to your completion handler completion(valueOfUpdate) so that you can use this value in func
//tableView(_ tableView:_, didSelectRowAt indexPath:_)
let valueOfUpdate = self.datRef.child("Bets").child(self.tieBetToUser[index]).updateChildValues(values)
completion(valueOfUpdate)
}) { (error) in
print(error.localizedDescription)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + amountBets[indexPath.row], preferredStyle: .alert)
let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
//take away that bitches money
self.takeAwayMoney(self.amountBets[indexPath.row])
//then delete that cell and do another pop up that says successful
// check if value is yes or no in the database
self.updateBet(indexPath.row, completion: {(result: String) in
guard let resultReceivedInupdateBet = result, else {
print("result of updateBet() is \(result)")
}
print("If you see this print, database was updated")
//calling this method with the indexPath.row clicked by the user
self.getOpoosingUserNames(self.userName, indexPath.row, completion: { (result: [String]) in
guard let resultReceivedIngetOpoosingUserNames = result{
print("result of getOpoosingUserNames is \(result)")
}
print("If you see this print, you received a value from db after calling getOpoosingUserNames and that value is in \(result) ")
//result is not nil, resultReceivedIngetOpoosingUserNames has the same value as result.
}//end of self.getOpoosingUserNames
self.checkForNo(indexPath.row)
self.amountBets.remove(at: indexPath.row)
self.tableView.reloadData()
print(self.opposingUserNames)
let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.opposingUserNames[indexPath.row], preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
successController.addAction(okButt)
self.present(successController, animated: true, completion: nil)
//lastly delete the opposing UserName
self.opposingUserNames.remove(at: indexPath.row)
})
alertController.addAction(okButton)
alertController.addAction(yesButton)
present(alertController, animated: true, completion: nil)
}