I am using Firebase to store my backend data. I am trying to display a leaderboard using a UITableViewController in Swift.
One of the data source methods include,
override func tableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
which in my case requires me to fetch data from Firebase and store the data in a local array variable e.g var leaderboardArr = [Int]()
Problem arises when there is a change to the rankings in the leaderboard. I know Firebase has built-in methods to sort the data and adjust the rank accordingly but how do I manage the local array variable?
UITableViewController dataSource method displays the data according to the indices of the local array variable.
How to manipulate the array according to the changes in data from Firebase??
When the user refreshes (or when you decide the view should refresh) retrieve the leaderboard stats from Firebase again and then reload the table view.
If you're looking to update the data in real time you can add a ChildEventListener. So when the array changes, it activates the listener, and from there you can retrieve the necessary data and reload the view.
Here is some reference: https://firebase.google.com/docs/reference/android/com/google/firebase/database/ChildEventListener
Related
I have used UITableViewDiffableDataSource as the datasource for my tableView.
On initial load, 8 items are fetched from server and displayed.
When I try to scroll,
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
is called, but indexPaths always has 0 and/or 1. It is never getting incremented to ask for prefetching 10, 11,12, etc rows.
What am I missing here?
From Apple Docs:
Table views do not call this method for cells they require immediately, so your data source object must also be able to fetch the data itself.
This approach is offered by the docs:
The tableView(:prefetchRowsAt:) method is not necessarily called for every cell in the table view. Your implementation of tableView(:cellForRowAt:) must therefore be able to cope with the following potential situations:
Data has been loaded via the prefetch request, and is ready to be displayed.
Data is currently being prefetched, but is not yet available.
Data has not yet been requested.
One approach that handles all of these situations is to use Operation to load the data for each row. You create the Operation object and store it in the prefetch method. The data source method can then either retrieve the operation and the result, or create it if it doesn’t exist.
How do I reload a UiTableView only after the data has been loaded from the database. I have a datasource class that contains the function to load in all the data, and stores the data after it has been loaded into an array.
When I access the data using the Tableview though it displays nothing because the tableview loads before the database has returned its data. The only way to load the data is for me to have a button that manually reloads the tableview.
How do I cause the tableview to automatically reload once the array in the datasource object has been populated from the database?
Being a new programmer it took me a while to learn this was even possible, but I just realized completion blocks are the way to go. It will carry out some specified code only after your function has finished fetching from the database. In this case, I can update the tableview AFTER I've retrieved the data from the database. Here's an example.
Function definition:
func getDataFromDatabase(someInput: Any, completion: #escaping(String?) -> Void) {
let retrievedData: String = databaseCallFunction()
// 'Get data from database' code goes here
// In this example, the data received will be of type 'String?'
// Once you've got your data from the database, which in this case is a string
// Call your completion block as follows
completion(retrievedData)
}
How to call the function:
getDataFromDatabase(someInput: "Example", completion: { (data) in
// Do something with the 'data' which in this example is a string from the database
// Then the tableview can be updated
let tableView.title = data
tableView.reloadTableView
})
The code in the completion block will happen AFTER the database call has completed.
Why not just call tableView.reloadData() once you've populated your data? It'll repopulate the entire table (well technically it'll only re-create whatever cells are visible).
The point is that your tableView:cellForRowAt: method should have the ability to access your data so as to fill in each table cell appropriately based on that data. Anytime your data changes, just call tableView's reloadData() again. It's efficient in that only the visible cells will be created, and the rest will be created whenever they are about to become visible, if ever.
Also, tableView:numberOfRowsInSection: should similarly be looking at your data to judge how many rows shall exist. It too will be called again during each call to reloadData().
Your data should always be prepared to be interrogated by the tableView delegate methods, even when it doesn't contain anything yet. When there's no data, your tableView:numberOfRowsInSection: would decide there should be 0 rows.
Is it memory intensive to retrieve data from NSUserDefaults within the cellForRow(at:IndexPath method? Code shown below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let recentFavorite = UserDefaults.standard.string("recentlyStoredFavorite")
if self.favorite == recentFavorite {
cell.imgForFavorite = self.imgForFavorite
}
}
Or should I save the value from UserDefaults in viewWillAppear and use that within cellForRow?
Please help me understand the memory implications of both the choices.
According to Apple
At runtime, you use UserDefaults objects to read the defaults that
your app uses from a user’s defaults database. UserDefaults caches the
information to avoid having to open the user’s defaults database each
time you need a default value.
You should be 'ok' to retrieve the info in cellForRow, as its likely sitting in a dictionary in memory (assumption), fetched by the key you provide, however to vadian's point, you could just put it in a model or property and eliminate the assumption.
Also, consider if that data will be changed by another process and if you need to observe UserDefaults key.
I'm building an iOS app with a table view. I have an array containing ID's that I use to populate the table view. Now I want to do a call to a web service in each table view cell that contains this ID in the URL. I want to use the JSON response to fill the cell with the correct data. I think the data has to be fetched in the background.
Can anyone point me in the right direction of how I can do this with Swift? I searched the web but I couldn't find any tutorials about this.
I think help full this step for you.
Step 1 :- Call API service from viewDidLoad() function.
Step 2 :- Handle API response and Use one array for store API data
Step 3 :- array count use for numberOfRowsInSection
Step 4 :- Use array data in cellForRowAtIndexPath
I have a UITableViewController that displays data retrieved over the network. I am working to figure out what the best design pattern would be so that I can reuse that same table view, but display data from many potential data sources. In my case I could potentially have upwards of 50 completely different network requests that will retrieve data to put into this table view. I don't want to subclass and have 50 different table views all with just a different network request method. What would be the best way about reusing a single class, but enabling the ability to have the table views data source retrieve data from many places?
Whether you want to actually subclass UITableViewController only depends on the kind of data that you want to display and if you need customized UITableViewCells for it.
If you don't need any customization and you just want to display the data in a simple way, there is no need to subclass UITableViewController at all.
Also, the design pattern you are looking for isn't really required, since UITableViewController itself is designed in such a flexible way that you can easily show data from different data sources (that's what the UITableViewDataSource protocol ist for after all).
In case all your 50 data sources always return an array of objects, you can just implement one version of UITableViewController and let it flexibly pull the data from any of these data sources. Then you can use a property, e.g.
#property (nonatomic, strong) NSArray* data;
within the UITableViewController.
Then you use your web service interface to perform the different kinds of requests when needed and pull the appropriate data. And then, once the new data returns from the web service, you can assign it to your property data and call [self.tableView reloadData] which will cause the UITableView to repopulate with the new contents of data.
Not sure if this exactly solves your issue, please let me know if I didn't get your problem quite right. Maybe you need to elaborate a bit more precisely on your issue as well so that I can provide better help :)
You can definitely use one UITableView in this scenario. The root of the issue is translating data from the server of 50 different versions to a data type (i.e. model) that will be displayed in each UITableViewCell.
You need to do the following:
Figure out the data that will be displayed in the cell (i.e. title, description, author, etc.)
Create a custom class/struct that holds this data (i.e. NewsItem)
Create NewsItem objects based on server data.
Populate your data source.
Match up your model properties with your UITableViewCell.
Invoke tableView.reloadData() when you get new data.
So let's say I find that I have a title, description and author, I could create this struct:
struct NewsItem {
var title: String
var detail: String
var author: String
}
As part of my code that interacts with the server, I create objects:
var newsItem1 = NewsItem(title: "Swift's Amazing", detail: "Here we go into why Swift is....", author: "Dustin Hill")
news.append(newsItem1)
Then when as part of configuring my UITableViewCells, I do the following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let newsItem = news[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseId")
cell.textLabel.text = "\(newsItem.title) by \(newsItem.author)"
cell.detailTextLabel.text = newsItem.detail
return cell
}
This way, you only have one type of object to display in the cell. The key is to translate what you are getting from the server and store them in a generic data type (which from reading your comments is what you are looking for)