Pushing Firebase events To a Table View - ios

I am trying to take some events from firebase and add them to a table view for the user to see. My current code looks like this:
For some reason, the events are only showing up when I print them in the closure (ref.child("Events") part), but outside, it is only showing the "hello" I pushed. Please help me fix this issue, preferably with actual code.

You need to reload after the for loop
self.tblEvents.reloadData()
Also put these 2 lines at the beginning of viewDidLoad
self.tblEvents.dataSource = self
self.tblEvents.delegate = self

Please reload your evens table view in view did load after loop like I have done in below code,
ref.child(“Events”).observe(.value, with: { (data) in
let events = data.value as! [String:[String:Any]]
For (_, value) in events {
self.eventsArray.append(value[“EventTitle”]! as! String)
})
// add this line in your code
tblEvents.reloadData()

Related

How to make Http request load on time before table view cell

I may be missing a design or simple detail here as I'm new to iOS, but can't figure this one out:
I've got a func called getWeatherData(), that based on the location received from another view controller, gets weather conditions from a http request and assigns it to HikeModel. This function is called in method of ViewDidLoad().
After func gets the weather conditions, it passes this to a child view controller. Which Child VC is a Table View Controller.
Problem is my TableViewCell loads faster than the http request takes to be done, in the parent view controller. And when the cell labels and images try to retrieve information that should have values assigned, theirs is nothing, since the http request hasn't finished on time.
I also attempted using DispatchQueue.main.async but no difference was shown.
I'm doing a native http request with Swift4.
For more detail on the structure of my project, I'm doing based on this work: https://medium.com/#phillfarrugia/re-creating-the-siri-shortcuts-drawer-interaction-9b2bc94e0b05
In ViewDidLoad
let group = DispatchGroup()
group.enter()
DispatchQueue.main.sync {
self.getWeatherData(hikeLocation: self.startHikeLocationString)
group.leave()
}
Method sending information fillDrawer, with what should contain weather vars assigned to HikeModel.
private func configureDrawerViewController() {
let compressedHeight = ExpansionState.height(forState: .compressed, inContainer: view.bounds)
let compressedTopConstraint = view.bounds.height - compressedHeight
containerViewTopConstraint.constant = compressedTopConstraint
previousContainerViewTopConstraint = containerViewTopConstraint.constant
// NB: Handle this in a more clean and production ready fashion.
if let drawerViewController = children.first as? DrawerViewController {
//send distnace too
drawerViewController.delegate = self
drawerViewController.fillDrawer(hike: self.hikeModel, userLocation: self.userLocation)
}
TableView CellsForRowAt
cell.temperature.text = hikeModel.temperature
cell.weather.text = hikeModel.weather
cell.weatherIcon.text = hikeModel.weatherIcon
cell.humidity.text = hikeModel.humidity
cell.barometer.text = hikeModel.barometer
cell.sunrise.text = hikeModel.sunrise
cell.sunset.text = hikeModel.sunset
I expect the tableViewCell in child controller to load after the http request is done in parent view controller.
Your code is incomplete to identify issue but you should have to do like:
1) load data from webservice then,
2) reload tableView
In your ViewDidLoad
let group = DispatchGroup()
group.enter()
DispatchQueue.main.sync {
self.getWeatherData(hikeLocation: self.startHikeLocationString)
group.leave()
}
add line after complete process of data modeling and it should be in mainQueue
yourtable.reloadData()

Clearing Firebase observations from a UITableViewCell

In all iOS classes that use Firebase you will have code like this,
private func clearObservations() {
// your method for clearing observations, probably something like
blah blah. removeAllObservers()
}
In view controllers, it's essential that you call this in viewWillDisappear (or viewDidDisappear)
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
clearObservations()
}
That's fine.
Assume that you have created an observation in a UITableViewCell.
What is the best place in a cell to "clear observations" ?
Note that prepareForReuse is useless, try it.
The only approach we've found is
override func willMove(toSuperview newSuperview: UIView?) {
if newSuperview == nil {
clearObservations()
}
super.willMove(toSuperview: newSuperview)
}
Seems flakey/bizarre though.
What's the deal on this?
Update
Note while "XY Answers" are interesting and informative, if anyone knows the answer to the question that would be great also!
Preface
This was an attempt to answer the question but the question was misunderstood. I'll leave it here as it does have some relevance regarding observers, handles and tableView cell interaction.
While you can go through those gyrations, it's not really needed in most use cases.
For example, if you add and observer to a node, there wouldn't necessarily be a someRef? variable hanging around. So here we are watching the Posts node for new posts
let postsRef = self.ref.child("Posts")
postsRef.observe(.childAdded, with: { snapshot in
print(snapshot) //add the post to the dataSource and reloadTableview/cell
})
Here's another example of watching for any posts that are changed by uid_2
let postsRef = self.ref.child("Posts")
let queryRef = postsRef.queryOrdered(byChild: "poster_id").queryEqual(toValue: "uid_2")
queryRef.observe(.childChanged) { (snapshot) in
print(snapshot) //change the post in the dataSource and reloadTableview/cell
}
No class vars are needed for this functionality and nothing needs be nil'd. The point here being that you do not have to have class vars to get observing functionality and you do not need to keep a handle for every observer (keep reading)
In view controllers, it's essential that you call this
(someRef?.removeAllObservers()) in viewWillDisappear (or Did)..
will use Firebase in the cells of tables.
To clarify; I wouldn't want to put Firebase observers in the cells of tables. The observers should be in whichever viewController controls the tableView that has cells. Cells should pull data from the dataSource array (which is backed by Firebase)
There are some circumstances where you may want to remove all observers, again no need to have a class var or a need to nil a var.
let postsRef = self.ref.child("Posts")
postsRef.removeAllObservers()
There are times when a specific observer needs to be removed (in the case where a node has observers on it's child nodes for example), and in those cases, we store a handle to that observer as say, a class var (keeping them in an array is a tidy way to do it)
class ViewController: UIViewController {
var myPostHandle : DatabaseHandle?
func addObserver() {
let postsRef = self.ref.child("Posts")
self.myPostHandle = postsRef.observe(.childAdded, with: { snapshot in
print(snapshot)
})
func stopObserving() {
if self.myPostHandle != nil {
let postsRef = self.ref.child("Posts")
postsRef.removeObserver(withHandle: self.myPostHandle) //remove only the .childAdded observer
}
}
}
Again though, once the observer is removed, the handle would go out of scope once the class closes.
Tableviews that contain cells are backed by a dataSource and that dataSource get's it's data from firebase. When something is added, changed or removed from Firebase, your app is notified and the array is updated and then the cell refreshed. No need for an observer in the cell itself.
There's no need to add dozens of observers (in the cells) - add one central observer and let it keep the array current. Refresh tableView only when something changes.
EDIT
To Address a comment regarding the use of removeAllObservers: code is worth 1000 words:
Create a new Firebase project with two button actions. Here's the code for button0 which adds an observer to a node:
func button0() {
let testRef = self.ref.child("test_node")
testRef.observe( .value) { snapshot in
print(snapshot)
}
}
when this button0 is clicked, from there on, any adds, changes, or deletes to the test node will print it's contents to the log.
func button1() {
let testRef = self.ref.child("test_node")
testRef.removeAllObservers()
}
This will remove all observers for the node specified. Once clicked, no events will print to the console.
Try it!
It is not right to clear observations in cell and therefore there is not a best place to do it in cell, because, firstly, this approach contradicts MVC pattern. Views only responsible for displaying content and they should only contain code that describes how they must be draw. And in the view controller you give the content for showing by views. Usually content has provided by your model. So controller connects views and model. In your case, when you place clearObservations() in cell class, you also have someRef as a class property, so you have a model in your view class and this is incorrect.
Secondly, if you try to clear observations in table cell you definitely make logic of showing some content in table in wrong way. Cell only show data that has to be generated by some object that conforms to UITableViewDataSource protocol and implements protocol methods. For instance, in cellForRow method you generate cell and setup it with some content from array. This array is generated from model (Firebase service). Your view controller may be this data source object. You have to include array property to controller class and someRef, than you fill array and reload table data. If controller's view disappeared you clear observations, but you do it only inside view controller (in viewWillDisappear()).
Overall, all manipulations with someRef you should do in view controller and therefore "clear observations" also inside controller.

attach Firebase listeners to tableViewCell

I have a chat feature in an iOS app. the chat previews are presented in a tableView and I am using a class for the TableView cells themselves. I want to listen for new messages so I can put a "new Message" label on the cells. The relevant parts of the tableView Cell are:
var chat: Chat! {
didSet {
self.updateUI()
self.observeNewMessages()
}
}
extension ChatTableViewCell {
func observeNewMessages() {
let chatMessageIdsRef = chat.ref.child("messageIds")
chatMessageIdsRef.observe(.childAdded, with: { snapshot in
let messageId = snapshot.value as! String
DatabaseReference.messages.reference().child(messageId).observe(.childAdded, with: { snapshot in
//let message = Message(dictionary: snapshot.value as! [String : Any])
print("looks like there is a new message") //works
self.messageLabel.text = "New Message!"
//self.delegate?.newMessage()
})
})
}
}
The print statement is there to test the listener. The problem I am having is when the table view loads and the cells get created, observeNewMessages() is executing the print statement regardless of whether there were new messages. It is just reading the old children that are already there first? Obviously this is going to cause the message label to always read "New Message." I'm not too good with Firebase so is there a better Firebase function to use or a better way to do this?
*I am using 2 queries because the Message dictionary will allow me to see if the current user made the message or not. The delegate is there because my next step will be to reload the tableview -- as it is now, this will also cause an infinite print statement loop...

iOS label does not update text even from main thread

I've spent a fun couple of hours trying all sorts of different combinations to have a label properly update its title after a Firebase async download. It's the same issue raised here and here. Seems like a clear fix, but I'm doing something wrong and would appreciate any help pointing me in the right direction.
The basic flow is view loads, data is downloaded from Firebase, some labels are updated accordingly with downloaded data. One representative iteration I have tried is as follows:
// Query Firebase.
let detailsRef = self.ref.child("eventDetails")
detailsRef.queryOrdered(byChild: "UNIQUE_ID_EVENT_NUMBER").queryEqual(toValue: eventID).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("error")
}
else {
var tempDict = [NSDictionary]()
for child in snapshot.children {
let data = child as! FIRDataSnapshot
let dict = data.value as! NSDictionary as! [String:Any]
tempDict.append(dict as NSDictionary)
}
self.dictionaryOfRecoDetails = tempDict
self.ParseFirebaseData()
DispatchQueue.main.async {
// This is the function that updates labels and button text in format like self.websiteLabel.titleLabel?.text = "Appropriate String"
self.loadDataForView()
}
}
})
func loadDataForView() {
// Example of the label update that happens within this function.
// Do not show website button if there is no website.
if self.recommendation.recommendationWebsiteUrl == "" || self.recommendation.recommendationWebsiteUrl == nil || self.recommendation.recommendationWebsiteUrl == "NA" {
self.websiteLabel.titleLabel?.text = ""
self.websiteHeight.constant = 0
self.websiteBottom.constant = 0
}
else {
self.websiteLabel.titleLabel?.text = "Go to Website"
}
}
EDIT UPDATE: The call to the code above is coming from viewDidAppear(). It doesn't update if I call it from viewDidLayoutSubviews() either.
From debugging I know the label update is getting called, but nothing is changing. Feels like something simple I'm missing, but I'm stuck. Thanks for your ideas.
I'm pretty sure you don't need the DispatchQueue.main.async bit. Just try calling self.loadDataFromView() and see if that helps.
This ended up being a lesson in mis-labeling causing confusion. The label being changed actually isn't a label, but a button. Shouldn't have been named websiteLabel! Once the title was changed with self.websiteLabel.setTitle("Go to Website", for: .normal) then everything worked as expected.

Firebase filling array twice -- Swift

I have two String arrays: one that holds order numbers, and one that holds addresses.
I pull data from Firebase in viewDidAppear using a function that contains the following:
self.localOrderNumberArray.removeAll()
self.localAddressArray.removeAll()
self.orderNumbers.removeAll()
self.addresses.removeAll()
self.tableView.reloadData()
if onItsWayCompanyNameStoreNumberCourierNumberRootRef != nil {
let deliveryRef = onItsWayCompanyNameStoreNumberCourierNumberRootRef.childByAppendingPath("deliveries")
deliveryRef.observeEventType(.ChildAdded, withBlock: { snapshot in
self.orderNumbers.removeAll()
self.addresses.removeAll()
print(snapshot.value.objectForKey("orderNumber"))
let orderNumberPulledFromFirebase = snapshot.value.objectForKey("orderNumber") as! String
self.localOrderNumberArray.insert(orderNumberPulledFromFirebase, atIndex: 0)
let addressPulledFromFirebase = snapshot.value.objectForKey("address") as! String
self.localAddressArray.insert(addressPulledFromFirebase, atIndex: 0)
self.orderNumbers = self.localOrderNumberArray
self.addresses = self.localAddressArray
self.tableView.reloadData()
})
}
The function fills a UITableView with the data pulled from Firebase.
Everything works great when I first run the app. I can add data to Firebase through a different function, and the function above will pull the new data into the UITableView just fine.
However, when I segue to a different view controller (another UITableView, in this case), and then come back to the view that holds the function above, the function fills the order number and address arrays twice when I add new data.
If I segue to the other UITableView a second time, and then come back to view that holds the function above, the function fills the order number and address arrays three times when I add new data. And so on and so on.
It's the strangest thing. I can't figure it out, and it's about to drive me over the edge. Please help.
You are calling deliveryRef.observeEventType in viewDidAppear. viewDidAppear will be called each time the ViewController is presented. So when you segue to other ViewController and comes back, viewDidAppear will be called again and deliveryRef.observeEventType is registered again. So effectively there are two listeners doing the same job in your viewController which will add duplicate data to the array.
You have to implement a logic to do observeEventType only once in the ViewController.

Resources