Context for UITableViewCell/UICollectionViewCell - ios

This question probably looks similar to some other but I think it is not.
Going straightforward ...
Is it for you a best practice set an opaque context object (void * / id / AnyObject) to an UITableViewCell/UICollectionViewCell subclass in general a best practice?
Why this question? ... You probably know that UITableView / UICollectionView are builded in an asynchronous way, and this carry me to think that this can be a good solution to avoid crashes or wrong data usage.
Let me explain.
Probably the majority of iOS developer encountered at least one time the needs to subclass an UITableViewCell with a delegate object used to pass the cell to react to some interaction on the cell itself.
Doing so the delegate (ViewController) probably will search the index path of the cell to discover the related data object and do some process with the data. Ok but a lot of time it happens that the delegate message is delivered after the tableView was reloaded due to a remote data update but being an asynchronous process old cells are still on screen ...
In the worst case you will crash and in the best case you will act on the wrong data object.
So a solution to this could be, as asked before, an opaque object (context) carried by the cell subclass that will be available to the cell delegate getting back the context from the cell sent to the delegate message.
Obviously due to reuse the cell class must be cleaned up of the context during the prepareForReuse.
In this way TableViewCell do not know nothing about the model but it will act as postman for the delegate.
What do you think about?

but a lot of time it happens that the delegate message is delivered after the tableView was reloaded due to a remote data update but being an asynchronous process old cells are still on screen
You shouldn't get this issue if:
Your delegate message is sent synchronously on the main queue.
You deal with model and table view updates synchronously on the main queue.
If you have some delay between your model updating and your table UI reloading, you'll have lots of issues, not just the one you describe.
Essentially, I prefer the method you detailed of looking up the index path etc. over a context object added to the cell.

Related

How to add cell to tableview's reuse queue before 'cellForRow:'?

Creating a cell costs a lot of time and make the first scroll lagging, so I want to create a cell and add it to tableview's reuse queue before cellForRow: called.
I use dequeueReusableCellWithIdentifier: in viewDidLoad, but when I scroll the table, the cell is being created again.
In general all drawing methods of scrollview should be kept as simple as possible to avoid lag. This means you should prepare your data/model in viewDidLoad/viewWillAppear or even in previous ViewController. Your cellForRow should be as simple as set this image(s) and those text(s) - no checks, no expensive operations such as bluring, retrieving data from CoreData/Network, etc.
If you are not sure which thing exactly causes your lag, you should learn how to use TimeProfiler. If you feel lost in documentation, have a look this(quite outdated though) tutorial.
With thus said I cannot be able to help you anymore until you post some code which we could discuss.

How can I recycle controllers in UICollectionView's cellForItemAtIndexPath?

I am displaying a number of cells, whose content is reasonably memory intensive. I have a custom controller that sets up a view to display the content, and updates the UI periodically. However, often UICollectionView asks for a particular cell several times. I want to know how to design (well) a system to re-use the controller + view if I have already created it for a given piece of data. I thought about storing a controller reference in the data object, but don't know if storing UI elements in what is meant to be data is a good idea. Also, if I did this, I would need to monitor retainCount on cell unload, and when it gets to '1', remove it from the data object, which seems a bit hacky. Similarly, a NSDictionary of data->controller pairs in the UICollectionView could also work, and would again require monitoring retainCount, or re-implementing a retain counting mechanism for my particular case. It's doable, but seems clunky.
I'm very new to ios, so it may be I'm approaching this all wrong. Any suggestions are greatly appreciated!
First of all, think if displaying a controller view inside a cell is a good idea. I'm not sure, but since i'm not familiar with your project, it's something only you can tell.
Secondly, You can create your own pool of controllers. When you need a controller to put inside the cell (in cellforIndexPath method), take one from the pool. If the pool is empty, create a new one. The cell shouldn't come with a controller of it's own, put it only in cellForItemMethod.

What is a better way to deal with data after an async call using NSURLConnection?

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.
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;
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.

Nil out NSFetchedResultsController Delegate when offscreen?

Is it best practice to Nil out NSFetchedResultsController Delegate when offscreen?
For example, I have a list managed by a NSFRC. When I tap a list item, I get a detail view. I can potentially change something on a detail view that will remove it from the list. Or, if I continue to slide through the detail views, I can cause it to load in more data (which would subsequently update the parent table view NSFRC).
I'm getting strange behavior when I DO nil it out. Can't seem to find advice on this anywhere.
No reason to nil out your NSFRC here. In fact, this is the main convenience of having your tableView managed by an NSFRC, is that it will update itself appropriately while you are off in other views changing data. This also applies data changes sequentially, rather than having to refetch all data when returning to your list.
I have posted a similar question time ago, looking for a technical explanation of this 'best practice'. However I didn't find anything.
I suppose that much of the suggested 'best practices' came from older iOS version, where viewDidUnload could have been called in case of memory warning, but that's not the case anymore.
In my experience, I found out that it is only mandatory to resign in case of background update and merge. All the other case depends on your application logic.
For example when you have nested CoreData entities with subsequent UIView, like so:
child1 (UITableView)-->child2 (Detail)-->child3 --> (UITableView)-->ecc...
Then, a change on child3 will have the child 1 table view react on that change, so you may want to avoid unnecessary call to a remote fetched controller, by resigning as delegate or introspecting the changement and returning YES or NO within the delegate method. It could be simpler and much efficient to refetch the query.
Keeping the same structure, suppose you have a mass update on latest UITableView by merging two context, if you have used the same context for all the controllers, then they will be trying to update its view even if not needed.
So for a simple case like UITableView-->UIView (detail) I can see no problem of keeping delegate assigned, all the other case, I would do a little analysis.

Performance issue when reloading tableview section

When new data is received by my app, a notification is sent out and received by my view controller. The view controller then reloads one of the sections of its tableview. This typically takes just 40ms or so.
However, if I have pushed another view, the notifications are still processed by the original controller but the reload of the tableview takes about 10-20x longer.
I can certainly work on improving my tableview performance, but first I need to understand what is causing this. Would also appreciate some help using Instruments on how to debug this myself. So far I have run the 'Time Profiler' instrument and it looks like most of the effort is expended in system method, '[UIView(Hierarchy) layoutIfNeeded]'. This doesn't mean much to me.
You definitely don't need to be updating UI on an element that isn't onscreen. UI updates will take place in the main thread, which will negatively impact user experience.
Updating your data source (in a background thread) should be more then adequate, and then you can update the tableview when it is next shown to the user.

Resources