0

This is the current logic that I'm using to populate a table view with NSURLConnection. It doesn't seem elegant to me.

Table View Controller's viewDidLoad method calls "sendConnection" method in my api wrapper class with the URL string as a parameter. This method makes the NSURLConnection. In connectionDidFinishLoading (which is in my wrapper class), another method is called (also in the wrapper class) with the connection as a parameter. This method extracts the URL from the connection object and examines it. It then uses a switch statement to deal with the data depending on the URL. The data is stored in variables in the wrapper class itself. By the time cellForRowAtIndexPath is called, the async call has finished and the data has been processed.

Is there a better way of doing this?

My reason for asking this is as follows: I want to refresh a cell with a new height and a new text label when it is clicked. The data for this text label will be retrieved from the server upon the cell being tapped. Each cell will have slightly different data in the label (each cell represents a 'user' and the label will display how many mutual friends you have with the user). I want to store the data in the cell itself when the data is retrieved and then place it into the text label. This doesn't seem possible with my current way of making URL calls.

Any help with how to achieve this would be appreciated.

Rory Byrne
  • 923
  • 1
  • 12
  • 22
  • "I want to store the data in the cell itself" -- don't do that. Cells are for displaying data, not storing it. You should have a model object (usually an array) that stores the data. – rdelmar May 08 '14 at 23:40
  • @rdelmar where should that model be stored? The table view controller? – Rory Byrne May 09 '14 at 00:57

2 Answers2

1

Here is some pseudo code for a pattern I like to use in these situations. Maybe it will help you as well.

- (void)viewDidLoad {
     //1. put up some type of progressHud or spinner
     //2. call your NSURL wrapper
     //3. in the completion block of your wrapper, set your datasource variables
          //example: @property (nonatomic,strong) NSArray *listOfData;
     //4. create a custom setter for your datasource that calls tableview reload
     //5. enable a refresh function; like "pull to refresh" or a bar button
     //6. when pull to refresh is tapped or called, just repeat these steps


}
- (void)setListOfData:(NSArray*)listOfData {

       _listOfData = listOfData;
       if (_listOfData) {
           [self.tableView reloadData];
       }
}

As I read your question again, here are a couple more thoughts: the pattern above will work for your initial load, to create the list of people or friends, etc.

If you plan on making another round trip after the cell is tapped, then you have to consider a number of issues. This is similar to a common problem with lazy loading images into tableview cells. There are issues like scrolling to consider - what if the cell is scrolled off the view before the data returns, for example, what if the cell has been reused, now the data is not tied to that cell any longer.

There are many async image libraries available on Github that would be good to look at to see how they solved those issues. Generally they are keeping track of the item in the cell and then checking if the cell is still in view and if so, they set the image.

You have a similar issue to solve. Tap the cell, get the new data, then update the cell. Resizing the cell will require you to reload it.

Look into [tableview reloadRowsAtIndexPaths:(NSArray*) with RowAnimation:(UITableViewRowAnimation)];

hope that helps best wishes;

CocoaEv
  • 2,984
  • 20
  • 21
  • "The completion block of your wrapper" - By this do you mean "sendAsynchronousRequest:queue:completionHandler:"? Also (forgive me I'm not very experienced), can the block I pass to completionHandler be used to store the data in the same class I call "sendAsynchronousRequest:queue:completionHandler:" from? Could I call it in my wrapper class and use that block to store the data in that class once the connection finishes? edit: formatting on this site is beyond me... – Rory Byrne May 08 '14 at 20:38
  • yes, I think so without seeing your actual code. You would make the call from your tableview controller and it would go get your data and then run the code you put in your completionHandler block. In that block, you would process your data then set the tableview datasource. Once you set it, everything kicks off from there. Hope that makes sense, if not, please post how you call your networking api. – CocoaEv May 08 '14 at 20:45
  • how does one "link" listOfData to the tableView in the first place? I mean before the networking call. Can it be done via storyboards? – user3060636 May 08 '14 at 20:46
  • @CocoaEv - I call the URL from a method in my wrapper class. Can I use a block to store the data in a variable in that same object? I'm pretty sure that's the intended use, but I want to be sure. I am currently using reloadRowAtIndexPaths for the cell. It's just a matter of getting the data into the cell when I reload it. If I call the API just before reloading the cell, then the cell will be reloaded _before_ the data has been retrieved, and so the data won't be in the cell. What can I do to combat this? edit: specifically, how can I refresh the table view once the data has been processed? – Rory Byrne May 08 '14 at 21:02
  • user3060636 - Your tableview needs some type of data source ~ where it gets it data. Table data is often stored as records in an array. That would look like this: @property (nonatomic,strong) NSArray *listOfData; in your .h interface file. You don't set your data model in your storyboard,just your layout. In your controller subclass, you have to populate the datasource with the data you want to display, then tell the tableview to reload. – CocoaEv May 08 '14 at 23:39
  • You shouldn't really try to save the data in the cell. The pattern would look like this: a) fetch the new data, b) update your model c) reload the cell. However here are some tips: a) after tapping the cell, save a reference to the cell and to the data object the cell is pointing to b) after the network call comes back, find the cell and verify its still on screen (isVisible) and that it still is pointing to the same data object. If it is, then its safe to reload the cell, if its not, then ignore it. The data will be in it the next time its back in view. – CocoaEv May 08 '14 at 23:51
1

You should have a "Data Model" which represents the content (that is the cells) of your Table View.

Since you have "rows" in your table view, it makes sense this data model is a kind of array (possibly a NSArray) whose elements keep the data and state of the cell.

The data for each cell should not only have all the "data" properties rendered in your cell (e.g. the label) but also its state:

When a user tabs on a cell it will start an asynchronous task. This task may take a while to finish since it fetches data from a remote server. Think of several seconds, or even longer. You need to keep track of pending update tasks, since your implementation should prevent the user to update a cell again before the corresponding pending update task has been finished.

There are several techniques to accomplish this. One way is to have a property in your "Cell Data" class which reflects this state, for example:

@interface CellModel : NSObject    
@property (atomic) BOOL hasPendingUpdate;
...

When the cell will be rendered, you retrieve the value of the property and render the cell appropriately.

When the update task finishes, it updates its cell model data.

This model update will eventually update your Table View. There are several techniques to accomplish this. You should take care about thread-safety here and the "synchronization" of your Data Model and the table view cells. For example ensure the value of the hasPendingUpdate only changes on the main thread - since otherwise your rendered cell may become out of sync with the data model (not to mention race conditions in case you modify and access the property on different threads without synchronization primitives).

While the cell waits for an update, it should visually indicate this state (using a spinner for example) and disable the action to start an update task.

Very much recommended is a "Cancel" button, which either cancels a certain cell update task or all pending update tasks.

When the user moves away from this view, you may consider to cancel all pending tasks.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67