I am building a simple application with UIKit and Core Data. What I am trying to accomplish is simply display an empty state illustration when the tableview datasource is empty or essentially there's no data to display.
Times when this is valuable would be: 1) on initial usage of the app and the user hasn't yet populated any data 2) there is an error and want to display a custom image such as when the network timed out or 3) when a user cleared out all tasks/items such as an inbox.
if listOfItems.isEmpty {
// display empty state illustration
} else {
// display tableView
}
I tried to create a custom cell with an illustration and text to render as 1 huge cell in the cellForRowAt but does not render correctly.
Related
I've found some similar questions already on SO, but nothing that seems to address this specific problem.
I'm using a UITableView with around 25 dynamic cells. Each cells contains a hidden UIProgressView. When the user taps on the download button within the cell to download that item, the UIProgressView is displayed and it indicates the progress of the download.
I've achieved this by assigning a tag to each cell which is equivalent to its corresponding downloadItemID. Each instance of MyCell has its own progressBar property.
This works fine as long as the table view is not scrolled during the download. It works also when the user is downloading multiple items at the same time. The code looks like this:
UIProgressView *theProgressBar;
for (MyCell *cell in self.tableView.visibleCells)
{
if (cell.tag == downloadItemID) theProgressBar = cell.progressBar;
}
float progressPercentage = (float)completedResources / expectedResources;
[theProgressBar setProgress:progressPercentage animated:YES];
The problem is that when the user scrolls the table view, the cell and progress view are transferred to another cell. It's simple enough to reset and hide the progress view for the new cell, but when the original/downloading cell is scrolled back into view, no progress is visible.
I've tried caching active progress bars into a dictionary and reallocating them back to the original cell in cellForRowAtIndexPath, but this is giving me the same result: no visible progress after scrolling the cell off and on the screen. Even if I can get them to show up, I'm doubtful I can get this to work seamlessly by rolling my own caching method.
What I need is to keep cells in memory. But can I work around dequeueReusableCellWithIdentifier? This whole problem has arisen because I had to switch to a dynamic system of allocating the cells, but it is too dynamic. Can I create the cells dynamically, but keep them in memory all the time once created, or at least keep the ones that are currently downloading?
(I understand the reasons why cell reuse is the preferred way to go with table views.)
You are working against the framework. As vadian says in the comment, you need to separate the logic and state information from the cells and put them elsewhere.
Here is one way of doing it.
Create a class to hold each download state, like a download ongoing flag, download progress, title, URL, etc.
In your view controller, create and add all your download objects to an array when the view controller is created. The first object corresponds to the first row in the table.
Whenever you dequeue a cell, populate it with data from the array. The NSIndexPath row property serves as the index into the array.
All your updates (download progress) updates the download objects in the array, then update the cell content using the updated object. You can use UITableView cellForRowAtIndexPath to get the cell for a specific array index, if you get nil there is no need to update it.
This questions is referencing just before a tableview fetches its data and displays its cells.
I've seen a few apps lately display a rough outline type image of the tableview cells for the brief moments before the populated cells get displayed.
How is this done?
Is a placeholder image used for the entire tableview or are placeholder type images rendered for each cell until the cell is dequeued?
Here are examples from Facebook and the fiverr app
Create a separate UITableViewCell class where the content of the cell is a UIImageView that has some kind of placeholder image of what your cells will look like. Populate the UITableView with those cells while your background request is being made. When the request completes, start a table update in which you remove all the placeholder cells, then insert all the "real" cells.
According to me it would be better to add backgroundView to tableView.
write:
while searching /fetching data:
if results.count == 0{
tableview.backroundView = emptyBlurView
}
once data is received so before reload :
tableview.backroundView = nil
when start requesting on server show the place holder cell and network response are received show the data container cell. using a placeholder cell same as activity indicator.
FaceBook are using simmer effect for its placeholder cell.
Pod
https://github.com/malkouz/ListPlaceholder
https://github.com/Juanpe/SkeletonView
I'd like to get every data that is within all cells in one tableview which is quite a long list.
I'm looking for an approach on how to retrieve everything including those hidden in view, which I know the views are reused. I think some of you might have experienced this problem before, what are your approach on this?
I've tried
let cells = self.tableView.visibleCells
then looping into every cell and saving each data to an array but it is not effective in getting those that aren't part of the view or hidden. Is there a way to get over this?
In cellForRowAtIndexPath, YOU are telling the table what is in each cell. So why would you turn around and ask the table what's in each cell? If the user puts "Hello" in your first cell, then scrolls the table enough to push that first cell out of view, then when the user scrolls back to the top, YOU are the one telling it to put "Hello" back in that first cell. YOU own the data source, not the table.
You need a data source. That can be "empty" at first, maybe an array of empty strings if that's what you want (each index in the array could map to a table row for example). But then, as the user interacts with the text fields in the cells, you need to update that data source with the text they entered.
You should use that data source as your source for the cellForRowAtIndex method. That way you can handle populating the cells when they are requested by the table, and you also know all the data when the user is done.
Why not just update the model each time the user taps a key when editing a textfield? You could create a protocol for that cell subclass and make your view controller the delegate for each cell. As long as cells are guaranteed to stay on the screen while you're typing (you'll get some weird behaviors if not) the cell can send a message to the view controller or whatever you hook it up to telling it what new value to store. Then everything is already stored for you when you need the full list, and you don't have to interact with the tableview.
I have a table view showing search results. It has an array of all objects to search through, and an array of filtered objects, which feeds the table view.
When the user changes the text in the search bar, I update the filtered array and reload the table view. In addition, each cell has a thumbnail which is either cached or downloaded asynchronously before updating the table view cell.
So, the two places where the table view's data is reloaded are in the filtering method, which calls tableView.reloadData(), and in the cellForRowAtIndexPath, which calls self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimationStyle: .None) on the main thread, if the image needed to be downloaded.
So. I've been having all kinds of problems. The three kinds of errors I've gotten:
Fatal error: index out of range. This seems to be happening when cellForRowAtIndexPath is trying to get the contents from the array after it's been emptied because the user changed the search text.
Errors with the table view: e.g., error trying to insert row 34 into section 0. I'm not sure what's causing this to happen, but I guess it's trying to insert rows when the table view has no sections due to the array being empty.
Internal Inconsistency exception: I don't get any extra information when this happens, it just crashes. So I don't know what the problem is here either.
So how can I avoid these problems? I've been trying for a couple days now to get rid of these errors but nothing I try works. Does anyone have any ideas? Thanks.
Thanks to someone on Reddit, I found the answer.
Before updating the thumbnail image after asynchronously downloading it, I needed to check if the cell is still being used for the same index path (or for any index path.) Also, I switched from asking the table view to reload the row at that index path to setting the image of the cell's image view.
Basically, inside of cellForRowAtIndexPath, after determining the image needed to be downloaded and then doing so asynchronously, I switched from this:
NSOperationQueue.mainQueue().addOperationWithBlock {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
to this:
NSOperationQueue.mainQueue().addOperationWithBlock {
if indexPath == tableView.indexPathForCell(cell) {
cell.imageView?.image = downloadedThumbnail
}
}
Since I made this change I haven't gotten any crashes.
I have an app that displays a table of places, that when a cell is tapped, it fetches data from a web API and parses it. However, sometimes this request takes 3-5 seconds to process. I've tried to display a spinning progress indicator in the content view of the cell, but I haven't been able to get it totally correct.
I was able to get the indicator to appear when the cell was tapped, however when I return to the table, the indicator is still visible. What's the best way to do do this?
Here is one way to approach this issue:
when the cell is clicked, store the indexPath (or some other reference) so you know which cell was clicked
add the spinner
make an async call to get the data from your data source
when the async call returns, update the appropriate data in your array (or wherever you store the data for the table)
call reloadRowsAtIndexPaths to reload just the cells that changed, and remove your spinner.