Passing Firebase database reference data - swift

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

Related

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

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.

How to Update CollectionView Model with Firebase?

I am trying to intitialize my Collection View Model which holds a list of [Users] with a firebase snap that holds data.
Here is the Collection View Model code:
typealias JSON = [String:Any]
class HomeDataSource: Datasource {
var users: [User]
init?(with snapshot:DataSnapshot) {
var users = [User]()
guard let snapDict = snapshot.value as? [String: [String:Any]] else {return nil}
for snap in snapDict {
guard let user = User(dict: snap.value) else {continue}
users.append(user)
}
self.users = users
}
}
User Model:
struct User {
let name: String
let username: String
init?(dict: JSON) {
guard let name = dict["name"] as? String,
let email = dict["email"] as? String
else {return nil}
self.name = name
self.username = email
}
}
Firebase Snap:
Snap (users) {
8CVeHMNHI6hZAWj1zhGHEjPwYYz1 = {
email = "Silviu#isidors.sjsj";
name = Bshdjdj;
};
9CuqgR4Es7TOPPJQEpSnQXlfYnm1 = {
email = "Test#silviu.com";
name = "Test#silviu.com";
};
DBqGWlpdJKME570euqUz2rqI5Z83 = {
email = "Test#test.test";
name = Test;
};
}
Fetch Function:
func fetchUser() {
let ref = Database.database().reference().child("users")
ref.observe(.childAdded, with: { (snapshot) in
let user = User(dict: snapshot.value as! JSON)
self.users.append(user!)
print(self.users)
let new = HomeDataSource(with: snapshot)
print(new)
DispatchQueue.main.async(execute: {
self.datasource = new
self.collectionView?.reloadData()
})
}, withCancel: nil)
}
Right now, I am getting an array of Users from Firebase, however my collection view won't update.
My question is how should I update my Collection View Model and Fetching Function so it can fetch data from Firebase and Populate the Collection View Correctly?
So the problem was the node I was targeting. Removed "child("users")" form fetching function and targeted the whole users node with uids. Then I was looping the snapshot.value while I casted it in [String:[String:Any]], because each snapshot.valuelooked like this (key: "vijwUkzAlbgcqjAammfy0JuVMB33", value: ["name": Silviu, "email": Office#isidors.com]) Finally, I updated the HomeDataShource Class like this:
class HomeDataSource: Datasource {
var users: [User]
init(with snapshot:DataSnapshot) {
var users = [User]()
let snapDict = snapshot.value as? [String:[String:Any]]
for snap in snapDict! {
guard let user = User(snap: snap.value) else {continue}
users.append(user)
}
self.users = users
}

Why .childAdded is not called when new data is added? Firebase

I am trying to read data from media when data is updated on /media node, but .observe(.childAdded is not called.
For example, I update data at /media/-LKN1j_FLQuOvnhEFfao/caption , but I never receive the event in observeNewMedia .
I can read the data with no problem the first time when ViewDidLoad completes.
The first step is to download the user data, second is to get the locality from currentUser and the last step is to attach a listener .childAdded on media.
I suspect that the event is not triggered because fetchMedia is called inside DDatabaseRReference.users(uid: uid).reference().observe(.value
media
-LKNRdP4ZsE3YrgaLB30
caption: "santa"
mediaUID: "-LKNRdP4ZsE3YrgaLB30"
locality: "barking"
users
Q6Dm3IMLNLgBH3ny3rv2CMYf47p1
media
-LKNReJCxgwtGRU6iJmV: "-LKNRdP4ZsE3YrgaLB30"
email: "john#gmail.com"
locality: "barking"
//enables the programmer to create references to different childs in Firebase
enum DDatabaseRReference {
case root
case users(uid:String)
case media //to store photos
func reference() -> DatabaseReference {
return rootRef.child(path)
}
//return root reference to our database
private var rootRef: DatabaseReference {
return Database.database().reference()
}
private var path: String {
switch self { //self is the enum DDatabaseReference
case .root:
return ""
case .users(let uid):
return "users/\(uid)"
case .media:
return "media"
}
}
}//end of enum DatabaseReference
class NewsfeedTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//observe ~/users/uid
DDatabaseRReference.users(uid: uid).reference().observe(.value, with: { (snapshot) in
DispatchQueue.main.async {
if let userDict = snapshot.value as? [String : Any] {
self.currentUser = UserModel(dictionary: userDict)
self.fetchMedia()
self.tableView.reloadData()
}
}
})
}
func fetchMedia() {
Media.observeNewMedia((currentUser?.locality)!) { (newMedia) in
//check if newly downloaded media is already in media array
if !self.media.contains(newMedia) {
self.media.insert(newMedia, at: 0)
self.tableView.reloadData()
}else {
//remove old media and add the newly updated one
guard let index = self.media.index(of: newMedia) else {return}
self.media.remove(at: index)
self.media.insert(newMedia, at: 0)
self.tableView.reloadData()
}
}
}
}//end of NewsfeedTableViewController
class Media {
class func observeNewMedia(_ userLocality: String, _ completion: #escaping (Media) -> Void) {
DDatabaseRReference.media.reference().queryOrdered(byChild: "locality").queryEqual(toValue: userLocality).observe(.childAdded, with: { snapshot in
guard snapshot.exists() else {
print("no snap ")
return}
print("snap is \(snapshot)")
let media = Media(dictionary: snapshot.value as! [String : Any])
completion(media)
})
}
} //end of class Media
Let's first update the structure so make it more queryable
assume a users node
users
-Q6Dm3IMLNLgBH3ny3rv2CMYf47p1 //this is each users uid
email: "john#gmail.com"
locality: "barking"
and a media node that contains media for all users
media
-abcdefg12345 //node created with childByAutoId
caption: "santa"
for_uid: -Q6Dm3IMLNLgBH3ny3rv2CMYf47p1 //matches the uid in the /users node
Then our main viewController which contains a reference to Firebase and logs the user in
class ViewController: UIViewController {
var ref: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
self.ref = Database.database().reference()
//log user in which will populate the Auth.auth.currentUser variable
}
.
.
.
We need an object to store the media in and then an array to hold those objects
class MediaClass {
var key = ""
var caption = ""
init(k: String, c: String) {
self.key = k
self.caption = c
}
}
var mediaArray = [MediaClass]()
then set up the observers which will add, update or remove from the array when media for this user is added, changed or removed.
let thisUid = Auth.auth().currentUser?.uid
let mediaRef = self.ref.child("media")
let queryRef = mediaRef.queryOrdered(byChild: "for_uid").queryEqual(toValue: thisUid)
queryRef.observe(.childAdded, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let key = snapshot.key
let caption = dict["caption"] as! String
let m = MediaClass.init(k: key, c: caption)
self.mediaArray.append(m)
self.tableView.reloadData()
})
queryRef.observe(.childChanged, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let key = snapshot.key
let caption = dict["caption"] as! String
let index = self.mediaArray.index { $0.key == key } //locate this object in the array
self.mediaArray[index!].caption = caption //and update it's caption
self.tableView.reloadData()
})
//leaving this an an exercise
queryRef.observe(.childRemoved....
Note we added .childAdded, .childChanged and .childRemoved events to the media node via a query so the only events the app will receive are the ones that pertain to this user.
Also note there's no error checking so that needs to be added.

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()
}

How can I retrieve data from firebase (Swift)

I am beginner and I would like to use swift as my programming language.
user can add register users and transfer money to each other, therefore when user type the photo number and email, the app can check if the typed number is registered in the firebase system
Any simple way to search if the user exist, thanks a lot
Here's the structure of the database
var ref: DatabaseReference!
var tref: DatabaseReference!
var handle : DatabaseHandle!
var usersArray = [NSDictionary?]()
var filteredUsers = [NSDictionary?]()
var user : NSDictionary?
override func viewDidLoad() {
super.viewDidLoad()
self.picker.isHidden = true
tref = Database.database().reference()
ref = Database.database().reference()
self.handle = self.ref?.child("users").child((Auth.auth().currentUser?.uid)!).child("contact").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String {
self.pickerdata.append(item)
self.picker.reloadAllComponents()
}
})
self.picker.delegate = self
self.picker.dataSource = self
tref.child("users").queryOrdered(byChild: "phone").observe(.childAdded, with: {(snapshot) in
self.usersArray.append(snapshot.value as? NSDictionary)
})
print(usersArray.count)
}
#IBAction func ContactChange(_ sender: UITextField) {
filteredContent(searchText: contactText.text!)
print(filteredUsers.count)
print(usersArray.count)
print("ARRAY")
}
func filteredContent(searchText: String){
self.filteredUsers = self.usersArray.filter{ user in
let username = user!["phone"] as? String
return (username?.lowercased().contains(searchText.lowercased()))!
}
}
func findUsers(text: String){
self.handle = ref.child("users").queryOrdered(byChild: "phone").queryStarting(atValue: contactText.text!).queryEnding(atValue: contactText.text!+"\u{f8ff}").observe(.value, with: { snapshot in
if let item = snapshot.value as? String {
self.contact.append(item)
} else{
print("error")
}
})
}
The code above doesn't work much. Thanks so much for helping
The question is a little unclear but I think what the OP is asking is:
how to see if a user exists by their phone number?
If that's what's being asked, a node can be retrieved by query'ing for a child of that node. For example, let's create a query on the users node to find the child that contains the name: Kam querying by phone
let usersRef = firebaseRef.child("users")
let query = usersRef.queryOrdered(byChild: "phone").queryEqual(toValue: "2330504")
query.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
//need to iterate in case we find more than one match
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let name = dict["name"] as! String
print("found \(name)")
}
} else {
print("not found")
}
}
When this code is run it would print
found Kam
assuming the phone number is 2330504