I want to get the following structure (screenshot of Firebase Database):
In chats I have the id of the chat. There are the users with the child userid and the values of id and name.
At first I look for the chats which a user have and want to get then the details of the chatId (users with their id and name)
I have the following class in Swift:
class Chat {
var chatId: String!
var userIds: [String]!
var userNames: [String]!
}
I have the following code to get the details, but I get not the userIds or userNames from the chatId:
func getChatsFromFirebase() {
self.ref = Database.database().reference()
self.ref?.child("users").child(userdefaults.getUserId()).child("chats").observe(.childAdded, with: { (snapshot) in
let chat = Chat()
chat.chatId = snapshot.key
chat.userIds = []
chat.userNames = []
//print(chat.chatId)
for i in 0..<self.chats.count {
let usersRef = self.ref.child("chats").child(self.chats[i].chatId).child("users").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
for userid in value!.allKeys as! [String] {
let usersdetailsRef = self.ref.child("chats").child(self.chats[i].chatId).child("users").child(userid).queryOrdered(byChild: "name").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
//print(value)
let id = value?["id"] as? String ?? ""
let name = value?["name"] as? String ?? ""
//print( id + ": " + name)
chat.userIds.append(id)
chat.userNames.append(name)
})
}
})
}
self.chats.append(chat)
self.tableView.reloadData()
})
}
I am very new to the Firebase topic. Can someone help me here?
Thanks.
Well You need to change your datamodel first. You dont need to store id value in , 12345 in this case. you can already fetch the key. Also, in /users/chats, you just can just save the chat id as either chat1 : IBDrbfku887BLIY or IBDrbfku887BLIY : true. You can always fetch them through value or the key respectively.
And in your chat document, you just need to reference the user id, i.e just get them and store them as user1 and user2. You can add more users if your usecase requires more.
Reconfigure your Data Model as follows.
Now You need 2 Objects Users and Chats as follows :
Users.swift
class User : NSObject {
private var _name: String!
private var _username: String!
private var _userid: String!
private var _userRef: DatabaseReference!
var name: String! {
get {
return _name
} set {
_name = newValue
}
}
var username : String! {
get {
return _username
} set {
_username = newValue
}
}
var userid: String! {
get {
return _userid
} set {
_userid = newValue
}
}
var userRef: DatabaseReference! {
get {
return _userRef
} set {
_userRef = newValue
}
}
init(userid: String, userData: Dictionary<String, Any>){
self._userid = userid
_userRef = Database.database().reference().child(_userid)
if let username = userData["username"] as? String {
self._username = username
}
if let name = userData["name"] as? String {
self._name = name
}
}
}
Chats.swift
class Chat : NSObject {
private var _chatid: String!
private var _user1: String!
private var _user2: String!
private var _chatRef: DatabaseReference!
var user1: String! {
get {
return _user1
} set {
_user1 = newValue
}
}
var user2 : String! {
get {
return _user2
} set {
_user2 = newValue
}
}
var chatid: String! {
get {
return _chatid
} set {
_chatid = newValue
}
}
var chatRef: DatabaseReference! {
get {
return _chatRef
} set {
_chatRef = newValue
}
}
init(chatid: String, chatData: Dictionary<String, Any>){
self._chatid = chatid
_chatRef = Database.database().reference().child(_chatid)
if let user = chatData["users"] as? Dictionary<String, Any> {
if let user1 = user["user1"] as? String {
self._user1 = user1
}
if let user2 = user["user2"] as? String {
self._user2 = user2
}
}
}
}
The major issue/or an overlooked issue here is the type of the data. In the /users, you id 12345 will be of type String. But when you fetch the same from /chats, it returns as Int. This downloads the value but never converts it. Always take care while seeding/testing your data.
To fetch the user's credentials just reference that through another query. This is what you can do :
var allUsers = [User]()
var allChats = [Chat]()
func viewDidLoad() {
super.viewDidLoad()
fetchAllChats()
}
func getUser(from userId: String, completion: #escaping (User) -> Void) {
Database.database().reference().child("users").child(userId).observeSingleEvent(of: .value, with: { snapshot in
if let datasnap = snapshot.value as? Dictionary<String, Any> {
let user = User(userid: userId, userData: datasnap)
completion(user)
}
})
}
func fetchAllChats() {
Database.database().reference().child("chats").observeSingleEvent(of: .value, with: { snapshot in
allChat.removeAll()
if let snapshot = snapshot.value as? Dictionary<String, Any> {
for snap in snapshot {
if let chatd = snap.value as? Dictionary<String, Any> {
let chat = Chat(chatid: snap.key, chatData: chatd)
self.allChats.append(chat)
}
}
}
// collectionview.reloadData() <--------- only if required.
})
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let chatData = allChats[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellId, for: indexPath) as! Cell
getUser(from: chatData.user1) { user in
cell.label.text = user.usernme
}
return cell
}
Related
struct UserClass {
var babyName: String!
var babyHeight: String!
var babyWeight: String!
var babyURL: String!
var uid: String!
var reference:DatabaseReference!
var key: String!
init?(snapshot: DataSnapshot?) {
guard let value = snapshot?.value as? [String:AnyObject],
let uid = value["uid"] as? String,
let babyName = value["BabyName"] as? String,
let babyURL = value["BabyURL"] as? String,
let babyHeight = value["BabyHeight"] as? String,
let babyWeight = value["BabyWeight"] as? String else {
return nil
}
self.key = snapshot?.key
self.reference = snapshot?.ref
self.uid = uid
self.babyURL = babyURL
self.babyName = babyName
self.babyHeight = babyHeight
self.babyWeight = babyWeight
}
func getuserData() -> String {
return ("BabyName = \(babyName)")
}
}
func fetchCurrentUserInfo() {
var currentUserRef = Database.database().reference().child("Users").child("\(userID)")
handler = currentUserRef.queryOrderedByKey().observe(DataEventType.value, with: { (snapshot) in
print("User data = \(snapshot.value)")
let user = UserClass(snapshot: snapshot)
print(user?.babyName)
self.babyName.text = user?.babyName
})
}
I am getting user data but not user.babyName. How can I fix this?
May be this will help you, as the db structure is not mentioned in question. but you have to iterate children one by one and then use for loop to fetch the exact data from firebase.
reference = FIRDatabase.database().reference()
reference.child("Users").queryOrderedByKey().observe(DataEventType.value, with: { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots
{
let userId = child.childSnapshot(forPath: "userID").value! as! String
print(userId)
}
}
})
How can I get the data that in Struct model in new viewController
I got a viewController when i print the var in Struct model it appeare just fine.
I created New viewController trying to get the same data in otherview of that struct model but when i print it it give me nil
I tryied to make the model equal to the new model in the new viewController
MessageController View
var messages = [Message]()
var users = [Contact]()
var messagesDicionary = [String: Message]()
var testMSG : Message?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = self.messages[indexPath.row]
let chatPartnerId = message.chatPartnerId()
let ref = Database.database().reference().child("users").child(chatPartnerId)
ref.observe(.value) { (snap) in
if let dictinoary = snap.value as? [String: AnyObject] {
guard let email = dictinoary["email"] ,let profileimage = dictinoary["profileimage"], let userid = dictinoary["userid"], let username = dictinoary["username"] else {
return
}
var user = Contact()
user.userid = chatPartnerId
user.userid = chatPartnerId
let userValue = Contact.init(email: email as? String, username: username as? String, profileimage: profileimage as? String, userid: userid as? String)
self.users.append(userValue)
self.showChatControllerToUser(user: userValue)
print(message.receivername)
}
}
}
result of Print Optional("Omar")
ChatMessagesController View
var user :Contact?
var users = [Contact]()
var message: Message?
var messagesArray = [Message]()
func observeChatMessages() {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let userMessagesRef = Database.database().reference().child("usersmesssages").child(uid)
userMessagesRef.observe(.childAdded) { (snap) in
let messageId = snap.key
let messageRef = Database.database().reference().child("messages").child(messageId)
messageRef.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String:AnyObject] else {
return
}
let text = dictionary["text"] as? String
let receiverid = dictionary["receiverid"] as? String
let senderid = dictionary["senderid"] as? String
let time = dictionary["timestamp"] as? TimeInterval
let senderName = dictionary["sendername"] as? String
let receiverName = dictionary["receivername"] as? String
self.messagesArray.append(MessageValue)
self.chatTableView.reloadData()
print(self.message?.receivername)
})
}
}
result of print NIL
Message Model
struct Message {
var text: String?
var receiverid: String?
var senderid: String?
var timestamp: TimeInterval?
var sendername: String?
var receivername: String?
func chatPartnerId() -> String {
return (senderid == Auth.auth().currentUser?.uid ? receiverid : senderid)!
}
}
I tried to make in MessagesController
let chatMessage = ChatMessageController()
chatMessage.message = self.testMSG
people in my app sometimes needs to update the status of something. Now can you choose of 2 things: The so called "Rollerbank" is still there or the "Rollerbank" is removed. The users can create a data ref. The id that will be created by childbyAutoID. Now is my question how to get the right child and update some childs with a value. My post:
class Post {
let ref: DatabaseReference!
var TypeControle: String = ""
var Stad: String = ""
var Tijd: String = ""
var TijdControle: String = ""
var TijdControleniet: String = ""
var Latitude: String = ""
var Longitude: String = ""
var Extrainformatie: String = ""
var Staater: String = ""
var Staaternietmeer: String = ""
init(TypeControle: String) {
self.TypeControle = TypeControle
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(Stad: String){
self.Stad = Stad
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(Tijd: String) {
self.Tijd = Tijd
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(Latitude: String) {
self.Latitude = Latitude
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(Longitude: String) {
self.Longitude = Longitude
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(Extrainformatie: String) {
self.Extrainformatie = Extrainformatie
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(Staater: String) {
self.Staater = Staater
ref = Database.database().reference().child("Rollerbanken").child("Controletest").childByAutoId()
}
init(Staaternietmeer: String) {
self.Staaternietmeer = Staaternietmeer
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(TijdControle: String) {
self.TijdControle = TijdControle
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(TijdControleniet: String) {
self.TijdControleniet = TijdControleniet
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init() {
ref = Database.database().reference().child("Rollerbanken").childByAutoId()
}
init(snapshot: DataSnapshot)
{
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
TypeControle = value["TypeControle"] as! String
Stad = value["Stad"] as! String
Tijd = value["Tijd"] as! String
Latitude = value["Latitude"] as! String
Longitude = value["Longitude"] as! String
Extrainformatie = value["Extrainformatie"] as! String
Staater = value["Staater"] as! String
Staaternietmeer = value["Staaternietmeer"] as! String
TijdControle = value["TijdControle"] as! String
TijdControleniet = value["TijdControleniet"] as! String
}
}
func save() {
ref.setValue(toDictionary())
}
func toDictionary() -> [String : Any]
{
return [
"TypeControle" : TypeControle,
"Stad" : Stad,
"Tijd" : Tijd,
"Latitude" : Latitude,
"Longitude" : Longitude,
"Extrainformatie" : Extrainformatie,
"Staater" : Staater,
"Staaternietmeer" : Staaternietmeer,
"TijdControle" : TijdControle,
"TijdControleniet" : TijdControleniet
]
}
}
Data for the TableViewCell:
class ControleTableViewCell: UITableViewCell {
#IBOutlet weak var storyControle: UILabel!
#IBOutlet weak var storyTijd: UILabel!
var post: Post! {
didSet {
storyControle.text = "\(post.Staaternietmeer)"
storyTijd.text = "\(post.TijdControleniet)"
storyControle.text = "\(post.Staater)"
storyTijd.text = "\(post.TijdControle)"
}
}
How my update button looks like:
#IBAction func Update(_ sender: Any) {
let alertController1 = UIAlertController(title: "Update melden" , message: "De rollerbank", preferredStyle: .alert)
// Create the actions
let RollerbankAction1 = UIAlertAction(title: "Staat er nog steeds", style: UIAlertActionStyle.default) {
UIAlertAction in
NSLog("Ja Pressed")
self.newStory.Staater = self.Staater
self.newStory.TijdControle = self.TijdControle
self.newStory.save()
}
let cancelAction1 = UIAlertAction(title: "Staat er niet meer", style: UIAlertActionStyle.cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
let Tijd = "\(hour) : \(minutes)"
self.newStory.Staaternietmeer = self.Staaternietmeer
self.newStory.TijdControleniet = Tijd
self.newStory.save()
}
alertController1.addAction(RollerbankAction1)
alertController1.addAction(cancelAction1)
self.present(alertController1, animated: true, completion: nil)
}
This is the Structure that i use. If i run all this code, the new data will go in a other childbyAutoID and thats not what i want. It just needs to update/setvalue in the cleare space named "Staaternietmeer" and "TijdControleniet". Can anybody help me with that?
You would then need to store the Push ID somewhere so that you can reuse it later.
To generate a unique Push ID you would use :
Database.database().reference().childByAutoId()
And to store it somewhere :
let postKey = Database.database().reference().childByAutoId().key
And then, say you need a method to share a post for example, and want to add this post to multiple nodes, that's how it may look like :
func sharePost(_ postContent: String, completion: #escaping (Bool) -> ()) {
guard let currentUserId = Auth.auth().currentUser?.uid else {
completion(false)
return
}
let postKey = Database.database().reference().childByAutoId().key
let postData: [String: Any] = [ "content": "blabla",
"author": currentUserId ]
let childUpdates: [String: Any] = ["users/\(currentUserId)/posts/\(postKey)": true,
"posts/\(postKey)": postData ]
Database.database().reference().updateChildValues(childUpdates, withCompletionBlock: { (error, ref) in
guard error == nil else {
completion(false)
return
}
completion(true)
})
}
Now to access the unique Push ID later on, you would use :
Database.database().reference().observe(.childAdded, with: { (snapshot) in
// Here you get the Push ID back :
let postKey = snapshot.key
// And others things that you need :
guard let author = snapshot.childSnapshot(forPath: "author").value as? String else { return }
guard let content = snapshot.childSnapshot(forPath: "content").value as? String else { return }
// Here you store your object (Post for example) in an array, and as you can see you initialize your object using the data you got from the snapshot, including the Push ID (`postKey`) :
posts.append(Post(id: postKey, content: content, author: author))
})
I have looked around and I cannot figure this out.
I am trying to set a default value of a tableView Cell if a Firebase snapshot returns nil
Example:
A snapshot is made to show all the event names from my Firebase Database
in a tableView using a dequeReusableCell.
But if the snapshot returns nil, the tableView returns with 1 cell with a label saying "Sorry, there are no events."
Here is my firebase snapshot code. This code does currently handle if the snapshot does return nil with a print() statement.
func populateTableView(){
let uid = Auth.auth().currentUser?.uid
ref = Database.database().reference()
ref.child("events").child(uid!).child(currentDate).observeSingleEvent(of: .value, with: { (snapshot) in
self.events = []
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {
//print("SNAP: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let event = Event(postKey: key, postData: postDict)
self.events.append(event)
//print(self.events)
}
}
}
if !snapshot.exists() {
self.eventStatus = false
self.tableView.reloadData()
print("No Event here")
} else {
self.eventStatus = true
self.tableView.reloadData()
}
})
}
The firebase Objects get stored into the Event class and are stored a dictionary. I don't think this code is needed, but here is the event class code for more context.
import Foundation
import Firebase
class Event {
var ref: DatabaseReference!
private var _description: String!
private var _imageUrl: String!
private var _eventTitle: String!
private var _eventType: String!
private var _eventTime: String!
private var _eventStartDate: String!
private var _eventEndDate: String!
private var _monthlyRepeat: String!
private var _weeklyRepeat: String!
private var _eventColor: String!
private var _postKey: String!
private var _postRef: DatabaseReference!
var description: String {
return _description
}
var imageUrl: String {
return _imageUrl
}
var eventTitle: String {
return _eventTitle
}
var eventType: String {
return _eventType
}
var eventTime: String {
return _eventTime
}
var eventStartDate: String {
return _eventStartDate
}
var eventEndDate: String {
return _eventEndDate
}
var monthlyRepeat: String {
return _monthlyRepeat
}
var weeklyRepeat: String {
return _weeklyRepeat
}
var eventColor: String {
return _eventColor
}
var postKey: String {
return _postKey
}
init(postKey: String, postData: Dictionary<String, AnyObject>) {
self._postKey = postKey
if let description = postData["description"] as? String {
self._description = description
}
if let imageUrl = postData["event_Image_URL"] as? String {
self._imageUrl = imageUrl
}
if let eventTitle = postData["event_Title"] as? String {
self._eventTitle = eventTitle
}
if let eventType = postData["event_Type"] as? String {
self._eventType = eventType
}
if let eventTime = postData["event_Time"] as? String {
self._eventTime = eventTime
}
if let eventStartDate = postData["start_Date"] as? String {
self._eventStartDate = eventStartDate
}
if let eventEndDate = postData["end_Date"] as? String {
self._eventEndDate = eventEndDate
}
if let monthlyRepeat = postData["monthly_Repeat"] as? String {
self._monthlyRepeat = monthlyRepeat
}
if let weeklyRepeat = postData["weekly_Repeat"] as? String {
self._weeklyRepeat = weeklyRepeat
}
if let eventColor = postData["color"] as? String {
self._eventColor = eventColor
}
let uid = Auth.auth().currentUser?.uid
ref = Database.database().reference()
let eventRef = ref.child("events").child(uid!).child("Monday May, 29")
_postRef = eventRef.child(_postKey)
}
}
The simplest way to solve this is to add a title UILabel to your ViewcController and change the text when snapshot is not available.
Or if that doesn't work for you for some reason you could try this:
I did not check this, but I might get you on track.
First you will need to change your populateTableView method so that an events array is created even when snapshot has no results. This way the events array count will be 1 (and one row will be added to your tableView) even if snapshot had no result.
populateTableView(){
let uid = Auth.auth().currentUser?.uid
ref = Database.database().reference()
ref.child("events").child(uid!).child(currentDate).observeSingleEvent(of: .value, with: { (snapshot) in
self.events = []
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {
//print("SNAP: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let event = Event(postKey: key, postData: postDict)
self.events.append(event)
//print(self.events)
}
}
}
else{ // Snapshot does not exist
let postDict: Dictionary<String, AnyObject> // Add an empty Dictionary
let key = -1 // Or what ever value you could not possibly expect
let event = Event(postKey: key, postData: postDict)
self.events.append(event)
self.tableView.reloadData()
print("No Event here")
}
})
}
Notice that when snapshot is not valid or available you add an empty Dictionary with an unique key value to your events array.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}
You need to create two custom cells with unique identifiers.
Now you can "actually" populate your tableView similar to this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let event = events.[indexPath.row]
let conditionKey = event.key
if(conditionKey == -1){ // or whatever value you gave in populateTableView to indicate that snapshot did not exist
let cell = tableView.dequeueReusableCell(withIdentifier: "identifierCellNotSoGood", for: indexPath) as! CustomCellNotSoGood
cell.noSnapShotLabel1.text = "Sorry, there are no events."
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "identifierCellAllGood", for: indexPath) as! CustomCellAllGood
cell.yourCustomLabel1.text = event.key // Or whatever data you are displaying
cell.sourCustomLabel2.text = event.event // Or whatever data you are displaying
return cell
}
return UITableViewCell
}
If you need to handle the selection of a table cell you can do this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// get rid of the ugly highlighting
tableView.deselectRow(at: indexPath, animated: false)
let event = events.[indexPath.row
let conditionKey = event.key
if(conditionKey == -1){ // or whatever value you gave in populateTableView to indicate that snapshot did not exist
// Do what you need or not
}
else{
// Do something meaningful with your database
doSomething(withEventData: event)
}
}
I have a service class to fetch data from my Firebase:
class Service {
var myName = String?
var myDev: String?
func getData() {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Data").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
self.myName = name
let dev = value?["dev"] as? String ?? ""
self.myDev = dev
}) { (error) in
print(error.localizedDescription)
}
}
}
and realization in my Main class:
var service = Service()
override func viewDidLoad(){
super.viewDidLoad()
service.getData()
configureLabel()
}
private func configureLabel(){
self.titleLabel.text = service.myName
self.devLabel.text = service.myDev
}
The problem is: data from Firebase fetched only after my label got values of myName and myDev.Thus, this values is nil.
this is not the best solution, but it should solve your problem.
struct MyStructure {
var name = ""
var dev = ""
init(with dictionary: [String: String]) {
if let name = dictionary["name"] {
self.name = name
}
if let dev = dictionary["dev"] {
self.dev = dev
}
}
}
class Service {
let databaseRef = FIRDatabase.database().reference()
func getData(completion: #escaping ((_ structure: MyStructure) -> Void)) {
databaseRef.child("Data").observeSingleEvent(of: .value, with: { snapshot in
if let value = snapshot.value as? [String: String] {
completion(MyStructure(with: value))
}
}) { (error) in
print(error.localizedDescription)
}
}
}
let service = Service()
override func viewDidLoad() {
super.viewDidLoad()
service.getData { myStructure in
DispatchQueue.main.async {
self.titleLabel.text = myStructure.name
self.devLabel.text = myStructure.dev
}
}
}
Of course it does. The getData() method makes an asynchronous call to the Firebase service. Meaning, the .observeSingleEvent method of firebase runs in a async queue as most code blocks that makes a service call. So the compiler reaches to getData() line, pushes the block to a different queue and continues to compile the next lines which are in the original queue. If you want those lines to run after you receive the response, you may add a closure to your getData() method as a parameter.
You can do something like this:
getData(completion: () -> Void) {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Data").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
self.myName = name
let dev = value?["dev"] as? String ?? ""
self.myDev = dev
completion()
}) { (error) in
print(error.localizedDescription)
}
}
override func viewDidLoad(){
super.viewDidLoad()
service.getData(completion: { Void in
configureLabel()
})
}