How to execute two completion blocks in a single function and pass the data of the completion block to next view controller? - swift

This is my database structure:
I'm using a function with closure, performing two completion blocks and store the data in two separate arrays. Once I get the data I want to pass the data to next view controller into different variables, but instead I'm getting same value for both arrays.
#IBAction func GoToAnswerPage(_ sender: Any) {
self.getData(refe:JoinCodeTextField.text!) { (array) in
self.performSegue(withIdentifier:"JoinToAnswerPage",sender:array)
}
}
func getData(refe: String, completion: #escaping (([Any]) -> ())) {
var questionArray = [Any]()
var answerArray = [Any]()
let ref = Database.database().reference(fromURL: "https://pollapp-30419.firebaseio.com/").child("Questions/\(refe)/")
ref.child("Question_And_Options").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
questionArray.append(value)
}
completion(questionArray)
})
ref.child("Answer_Key").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
answerArray.append(value)
}
completion(answerArray)
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let joinViewController = segue.destination as? JoinAnswerViewController
else {
return
}
joinViewController.answers = sender as! [String]
joinViewController.options = sender as! [String]
}
On the next view controller.
var options = [Any]()
var answers = [Any]()
This is the output I'm getting:
answers-["Test Q-1", "Test A-1", "Test A-2"]
questions-["Test Q-1", "Test A-1", "Test A-2"]
answers-["Test A-1"]
questions-["Test A-1"]
Instead I should get:
questions-["Test Q-1", "Test A-1", "Test A-2"]
answers-["Test A-1"]

Your completion handler will be called twice, once for "answers" and once for "questions". They could come in either order, so you should pass an additional type in the completion to know which you have received. Use a [String : [Any]] dictionary to collect the two arrays, and call self.performSegue(withIdentifier:sender:) when you've received both arrays and stored them in the dictionary arrays.
In prepare(for:sender:) unpack the sender dictionary and assign the values:
#IBAction func GoToAnswerPage(_ sender: Any) {
var arrays = [String : [Any]]()
self.getData(refe: JoinCodeTextField.text!) { (array, type) in
arrays[type] = array
if arrays.count == 2 {
self.performSegue(withIdentifier:"JoinToAnswerPage",sender: arrays)
}
}
}
func getData(refe: String, completion: #escaping (([Any], String) -> ())) {
var questionArray = [Any]()
var answerArray = [Any]()
let ref = Database.database().reference(fromURL: "https://pollapp-30419.firebaseio.com/").child("Questions/\(refe)/")
ref.child("Question_And_Options").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
questionArray.append(value)
}
completion(questionArray, "question")
})
ref.child("Answer_Key").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
answerArray.append(value)
}
completion(answerArray, "answer")
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let joinViewController = segue.destination as? JoinAnswerViewController
else {
return
}
guard let arrays = sender as? [String : [Any]],
let answers = arrays["answer"] as? [String],
let questions = arrays["question"] as? [String]
else { return }
joinViewController.answers = answers
joinViewController.options = questions
}
Note: When the user presses a button, they should get an immediate response. Since you are loading the data from the network, there may be a delay making the user wonder if anything is happening. It would be better to pass JoinCodeTextField.text! to JoinAnswerViewController and let it load the question/answer data. JoinAnswerViewController could display a UIActivityIndicatorView (spinner) while the data is loading to let the user know the data is coming. Once you have both arrays, you can set up the JoinAnswerViewController.

Related

Swift Passing Data Structs and Arrays Firebase

I am trying to send data from vc_1 to vc_2. But I am observing the submitted data is missing in vc_2 (Basket items). How can ı observe all data in vc_2?
Firebase node;
Pro_:
Basket:
ID_1:
cat: “Tech”
info:”iOS”
orderid:”Ref_1”
ID_2:
cat: “Tech”
info:”Android”
orderid:”Ref_2”
name:”Mike”
First_VC
var name_list = [Name_Struct]()
var SecondArray : [BasketArray] = []
func retrieve_data(){
ref = Database.database().reference()
self.ref.child(“Pro_”).observeSingleEvent(of:.value) { (snapshot) in
self.name_list.removeAll()
for child in snapshot.children{
let snap = child as! DataSnapshot
let dict = snap.value as! NSDictionary
let name_ = dict[“name”] as? String ?? ""
let baskets = (child as AnyObject).childSnapshot(forPath: “Basket”).value as? [String: Any] ?? [:]
for basket in baskets {
let pro_detail = basket.value as? [String: Any]
let info_ = basket?[“info”] as? String ?? ""
let cat_ = basket?[“cat”] as? String ?? ""
let orderid_ = basket?[“orderid”] as? String ?? ""
self.SecondArray.append(BasketArray(info: info_, cat: cat_, orderid: orderid_)
}
let names = Name_Struct(name: name_)
self.name_list.append(names)
DispatchQueue.main.async {
self.tableView.reloadData()
}}}}}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? Second_VC {
destination.SecondArray = SecondArray[indexPath.row]
print(“Send array:”,self.SecondArray)
}}
Send array output
Send array: [“cat”: “Tech”, “info”: “iOS”, “orderid”: “Ref_1”]
Send array: [“cat”: “Tech”, “info”: “Android”, “orderid”: “Ref_2”]
Second_VC
var SecondArray : BasketArray!
override func viewDidLoad() {
super.viewDidLoad()
table_view.delegate = self
table_view.dataSource = self
print(“retrieve:”,self.SecondArray)
}
Second_VC output;
retrieve: [“cat”: “Tech”, “info”: “Android”, “orderid”: “Ref_2”]
It's probably because in your most nested for loop in First_VC, you have
self.SecondArray = BasketArray(info: info_, cat: cat_, orderid: orderid_)
Because it's in a for loop, self.SecondArray is set to the ID_1 values, then after that on the second run of the loop you're overwriting it to be the ID_2 values. You want to be appending both the datas to the SecondArray, not overwriting them like you have here.

How do i return the value of an Array from snapshot closure and use it in prepare for segue?

I'm trying to get the value of an Array outside the function.But as soon as the block for the snapshot ends the value of newArray gets empty.
I want to access the value of an Array even after the closure ends.
After passing the value from prepare for segue to the next View Controller the value is still empty.
var newArray = [Any]()
func getData(refe: String) -> [Any]{
let currUser = Auth.auth().currentUser?.uid
let ref = Database.database().reference(fromURL: "MyURL").child("users/\(currUser!)/Questions/")
ref.child("\(refe)").observeSingleEvent(of: .childAdded, with:{ snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
self.newArray.append(value as! Any)
}
print(self.newArray)
//This gives [test,test1,test2]
})
return newArray
// Here the Array is empty.
}
Actual Result: []
Expected Result: [test, test1, test2]
I also tried this.
func getData(refe: String, completion: #escaping (([Any]) -> ())) {
var newArray = [Any]()
let currUser = Auth.auth().currentUser?.uid
let ref = Database.database().reference(fromURL: "MYURL").child("users/\(currUser!)/Questions/")
ref.child("\(refe)").observeSingleEvent(of: .childAdded, with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
newArray.append(value)
}
completion(newArray)
})
}
and calling it in helper function
func getDataD() -> [Any]{
let ref = getReference()
var arr = [Any]()
self.getData(refe: ref) { (array) in
arr = array
// This gives [test,test1,test2]
}
return arr
// This gives []
}
After that calling the function in prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let detailViewController = segue.destination as? HostOptionTableViewController
else {
return
}
detailViewController.ref = getReference()
self.getData(refe: detailViewController.ref) { (array) in
detailViewController.data = array
}
}
}
Actual result of detailViewController.data after execution should be [test,test1,test2]
but it is []
It's too late to call getData inside prepareForSegue , This should be inside the action that navigates to the second vc
self.getData(refe:yourRef) { array in
self.performSegue(withIdentifier:"segueName",sender:array)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let detailViewController = segue.destination as? HostOptionTableViewController
else {
return
}
detailViewController.ref = getReference()
detailViewController.data = sender as! [Model] // Model is type of array
}
the request is asynchronous and the navigation will occur before getting the data

Passing Firebase database reference data

I'm looking to pass an array that contains user info pulled from Firebase from one controller to another using a segue. I'm able to do it when everything is in a tableview, but not when it's in a regular controller view. Can someone help plz?
View Controller
var userArray = [User]()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showGuestView" {
let guestVC = segue.destination as! GuestUserViewController
guestVC.ref = userArray.ref //this would work using userArray[indexPath.row].ref if it was a tableview
//ref represents DatabaseReference?
}
}
DatabaseClass.fetchUser() { (user) in
if let user = user {
self.userArray.append(user)
}
Database Class
func fetchUser(completion: #escaping (User?)->()){
let currentUser = Auth.auth().currentUser!
let postRef = Database.database().reference().child("getinfo").child(currentUser.uid)
postRef.observe(.value, with: { (snapshot) in
for childSnapshot in snapshot.children.allObjects as! [DataSnapshot] {
let request = childSnapshot.key
let userRef = self.databaseRef.child("users").child(request)
userRef.observeSingleEvent(of: .value, with: { (currentUser) in
let user: User = User(snapshot: currentUser)
completion(user)
})
}
})
}
User Structure
struct User {
var firstname: String!
var uid: String!
var ref: DatabaseReference!
init(firstname: String, uid: String){
self.firstname = firstname
self.uid = uid
self.ref = Database.database().reference()
}
init(snapshot: DataSnapshot){
if let snap = snapshot.value as? [String:Any] {
self.firstname = snap["firstname"] as! String
self.uid = snap["uid"] as! String
}
self.ref = snapshot.ref
}
func toAnyObject() -> [String: Any]{
return ["firstname":self.firstname, "uid":self.uid]
}
}
I can see userArray is an array of users, hence you could not use userArray.ref because the array doesn't have any property like ref in its definition. In the table view, it is working because you pulled a user object and then passed ref.
Try to get a selected user instance before passing to the guest view controller.
let user = userArray[selected user index];
guestVC.ref = user.ref

How to clear TableView and reload data again?

I have a function like this and when I reload with button this function collectionViewTable shows same data second time .How can I solve it ??
func getWallpaperFromDB(){
let databaseRef = Database.database().reference()
databaseRef.child("wallpapers").observe(DataEventType.childAdded) { (snapshot) in
if let value = snapshot.value! as? [String: Any] {
let categoryID = value["categoryID"] as! String
let categoryName = value["categoryName"] as! String
let wallpaperName = value["wallpaperName"] as! String
let wallpaperId = snapshot.key
let DBWallpaper = Wallpaper(categoryID: categoryID, categoryName: categoryName, wallpaperId: wallpaperId, wallpaperName: wallpaperName)
self.wallpapers.append(DBWallpaper)
self.collectionViewTable.reloadData()
}
}
}
#IBAction func slideMenuButton(_ sender: Any) {
getWallpaperFromDB()
}
You need to clear every call
#IBAction func slideMenuButton(_ sender: Any) {
wallpapers.removeAll()
getWallpaperFromDB()
}
You can empty your wallpapers array inside the closure function. That way every time the function is called the wallpapers array will be empty before the data is being fetched again. That way you won't have duplicate data.
func getWallpaperFromDB(){
let databaseRef = Database.database().reference()
databaseRef.child("wallpapers").observe(DataEventType.childAdded) { (snapshot) in
self.wallpapers = []
if let value = snapshot.value! as? [String: Any] {
let categoryID = value["categoryID"] as! String
let categoryName = value["categoryName"] as! String
let wallpaperName = value["wallpaperName"] as! String
let wallpaperId = snapshot.key
let DBWallpaper = Wallpaper(categoryID: categoryID, categoryName: categoryName, wallpaperId: wallpaperId, wallpaperName: wallpaperName)
self.wallpapers.append(DBWallpaper)
self.collectionViewTable.reloadData()
}
}
}
#IBAction func slideMenuButton(_ sender: Any) {
getWallpaperFromDB()
}

Swift Firebase sending data inside a closure

I am trying to send data to another view controller. However, the data cannot be reached at the second view controller. Here is my code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "tograddetail":
print("Going to Grad Detail")
guard let gradDetailViewController = segue.destination as? graduatedetailViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedgradCell = sender as? GradTableViewCell else {
fatalError("Unexpected sender: \(sender)")
}
guard let indexPath = tableView.indexPath(for: selectedgradCell) else {
fatalError("The selected cell is not being displayed by the table")
}
ref = FIRDatabase.database().reference().child("Database")
ref.observe(FIRDataEventType.value, with: { (snapshot) in
//print(snapshot.value)
if snapshot.exists() {
if let countdowntime = snapshot.value as? NSDictionary {
let selectedgrad = self.graduatename[indexPath.row]
if let graddata = countdowntime[selectedgrad] as? NSDictionary {
let theinstitution = graddata["Institution"] as! String
let thelocation = graddata["location"] as! String
let thetimeleft = graddata["timeleft"] as! Int
guard let firstgrad = graddetail(institution: theinstitution, location: thelocation, timeleft: thetimeleft) else {
fatalError("Unable to instantiate graddetail")
}
//print(firstgrad.institution)
//print(destinationgraddata.grad?.institution)
let destinationVC = segue.destination as! graduatedetailViewController
destinationVC.grad = firstgrad
}
}
}
})
default:
fatalError("Unexpected Segue Identifier; \(segue.identifier)")
}
}
And here is my code for the second view controller:
var grad: graddetail?
#IBOutlet weak var theinstitution: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let grad = grad {
theinstitution.text = grad.institution
}
}
However, the grad.institution value always return nil. Any idea?
The issue is observe(_:with:) is async and segue will called synchronously, so that when you get response in completion block of observe your segue is already performed.
To solved the issue what you need to do is call the observe before calling the performSegue and inside the completion block of observe when you get response call the perfromSegue with the value that you want to pass.