Progress indicator in table cell while app fetches and parses data - ios

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.

Related

UITableView showing extra separator lines, on top of cells, when reloading page

I have a UITableView that contains mutiple sections. When my tab page first loads, everything looks fine. But when I navigate to a different tab, and come back, my UITableView has some extra separator lines.
I verified that numberOfRowsInSection is properly returning 2.
My row height is set to AutomaticDimension.
I am calling reloadData in viewDidAppear.
I tried setting the background color of my table cells to white, but the extra lines are still visible.
The UITableView is inside of a UIScrollView, which I know is frowned upon, but I am doing the calculation to calculate the size of the TableView. Everything works perfectly on initial load, it's not until I return to the tab that I get the extra lines.
In my AccountViewController, I was calling an API to get the user's get account information in ViewDidAppear.
Instead of creating a new TableViewSource, I would clear out the existing data, and the repopulate it with the result of the API call. The flow was something like this.
Clear the Source Data
Call the API
Populate Source with API data
Reload Table Data
I ended up solving the problem by making a call to Reload Table Data after the source was cleared.
Clear the Source Data
Reload Table Data
Call the API
Populate Source with API data
Reload Table Data
Perhaps not the most efficient approach, but it was the easiest to implement within the current structure of the page.

Avoid UITableViewCell updating content when scrolled

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.

Getting the data of all TableView Cell which all has TextFields in it (including the hidden views)

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.

Cancel NSOperations based on position in TableView/CollectionVIew

Suppose I have a table view or a collection view. In the cells I would need to load certain content (eg. an Image). I can get the content from local storage (if it has been saved there) or from the internet. Either way loading the content takes a while (500 kB Image).
The best way to do this is to create an NSOperation that will load in the background and then call a delegate when it finishes.
But suppose the user enters the table view / collection view and the queues start to get the first set of content (1-10) but then the user scroll quickly right to the end (visible cells 100-110) what should I do? The start-download operation happens when the cell is presented (cellForRowAtIndexPath) so I need to wait for everything to download until the user (who is now at the end of the tableView) sees content.
I tried creating a queue on each cell and cancelling that in prepareForReuse, but that crashes the app.
Anyway if the user is in a section of the tableview and content is downloading, but not shown yet, and scrolls further causing the download to be cancelled and then returns. Then he will need to download the first half of the image again, which is not good on limited data plans.
What is the best way to handle such a situation?
You could have your controller class adopt the UITableViewDelegate protocol, which will allow you to inject custom code into –tableView:willDisplayCell:forRowAtIndexPath: (called for you by the table view when a cell is to be displayed). In it you could calculate that because of the screen size, only rows within k indices of any row being shown could possibly be visible; thus, in this method call, cancel all the load operations associated with rows outside that range (relative to indexPath argument).

Updating a UITableView's data

In my application, I have a UITableView with a few rows (let's call it TV1), now, at the bottom of these rows is a row that drills down into another (TV2). This new TV2 asks you which type of data you would like to add to the first view. Then, when the user selects which kind, they're brought to another view, in which they fill in some fields, the data is saved, and they're sent back to TV1, however, the data they entered, isn't loaded and I'm not really sure how to do this. Any ideas?
Maybe you did this already, but how about using [TV1 reloadData];? This forces the entire table to reload, yet there are also more specific methods for reloading just the cells that have been altered. See also the documentation: UITableView
Reload your table view by calling [self.tableView reloadData] in the viewcontroller's viewWillAppear: method. It will be called when the user switches back to your view. Right before it is actually put on screen.

Resources