So I am using a UICollectionView with an NSFetchedResultsController with cache (leaning on Ash Furrow's implementation https://github.com/AshFurrow/UICollectionView-NSFetchedResultsController). Everything is functional, but I'm getting sluggish response due to what seems like redundant cycling of the uicollectionviewcontroller delegates.
This is what I'm doing:
Every few seconds, I am inserting a new entity into the managedObjectContext. Each entity ends up in it's own section based on the value of section key attribute when fetched. Each entity has 22 different attributes that end up being presented in 22 new cells (rows) for the entity section.
This is what is happening:
My NSFetchedResultsController delegates appear to working as expected (they capture the single section entry). When I end up running the batch updates, it calls cellForItemAtIndexPath for only the new entity (as expected). BUT (and this is MY ISSUE) the UICollectionViewController cycles through numberOfItemsInSection and sizeForItemAtIndexPath (I have different sized cells for the 22 new cells) for every single existing section. Isn't this cached? Don't we already know this? Any ideas why this is happening? I'm a noob, so am I misunderstanding something fundamental here? Am I fundamentally implementing the sections and rows of the collectionView incorrectly?
And the end result is that the extra cycles become very expensive as the number of entities/sections increase and the ui quickly becomes too staggeringly sluggish to be acceptable/usable.
Ideas?
UPDATE: REASONING FOUND
So I guess I needed to delve deeper into the implications of using custom cell formatting calls in my delegate. I don't really understand why this needs to be so costly (calling it on every row not just those in view), but it is.
heightForRowAtIndexPath being called for all rows & how many rows in a UITableView before performance issues?
So I guess I needed to delve deeper into the implications of using custom cell formatting calls in my delegate. I don't really understand why this needs to be so costly (calling it on every row not just those in view), but it is.
This SO thread talks about it:
heightForRowAtIndexPath being called for all rows & how many rows in a UITableView before performance issues?
Related
I am wondering what exactly happens, when you dont use dequeueReusableCellWithIdentifier in the cellForRowAtIndexPath-method.
In one Project I am collaborating we have different types of custom UITableViewCells which all appear in one single tableview. So here we fill arrays with all TableCells that should be displayed. These arrays are not very big (10-15 Cells) so for us that way works even not using any identifiers for dequeueReusableCellWithIdentifier. The next question is how at all you could use identifiers resp. dequeueReusableCellWithIdentifier when using different Cells in one single section of an UITableView. Is someone here hwo can explain, what exactly happens in background? Regards Nils
The dequeueReusableCellWithIdentifier is something that reminds me of Flyweight pattern.
Since allocation and instantiation of a cell can be an expensive task, using this mechanism you have the opportunity to create only the first visible cells and later reuse them just changing their contents. Scrolling animation must be as fast as possible to give a good experience to the user.
Is it worth it? Yes and it basically comes for free, we just need to pay attention that some old data can be still present in a new visualization, the trick is to always implement the method -prepareForReuse() correctly, here, you can eventually wipe all displayed data before setting the new one.
If you want to use different cells in the same section is absolutely possible, also if they have different height. You just need to crate different cell identifiers, one for each cells and tie them somehow along with your data.
I usually map data to be displayed in struct (swift) or dictionaries along with a key for the cell identifier to be used.
If your type of cells are representing themselves while scrolling you should dequeue them.
In a calendar app I display events based on the EventKit API. I fetch events from EKEventStore and display them in daily, weekly, monthly views, as lists, etc. Now I am running into some performance problems on iPhone 4.
The performance problems are mainly speed related. It takes several seconds, for example, to collapse or expand all table view sections (representing dates) to show the rows (representing events). It also takes 5-8 seconds to reload the table for the editing / export interface. I would have to check Instruments to give more details.
So far there have been no memory issues.
My strategy right now is to minimise the memory footprint. I am using arrays in memory, but they only contain the eventIdentifier, a short string. I can retrieve events with the EKEventStore method eventWithEventIdentifier:. I suspect that this is the reason for the performance hit.
Two alternatives come to mind:
Use EKEvent objects instead of identifiers. However, I believe that this can be unpredictable regarding memory. Some events have lots of text so that the amount of data to be kept in memory is not limited. The duration of the period that has to show events could potentially also be very long.
Port everything to Core Data, maybe with original EKEvent objects stored as transformable objects. This would be a major refactoring, but I could take advantage of NSFetchedResultsController and its optimisation features.
I have tried 1 and 2 - performance is still bad!
What is your experience? Have you seen performance issues with repeated calls to the EKEventStore database? What would be your advice?
UPDATE:
Instruments report that indeed the tableView's reloadData takes quite long (1.5 secs). I am not sure why because the state of the table view (collapsed sections or not) and the entire data are loaded before and that code is efficient.
I am not calculating any cell heights (sometimes this has been reported to force the entire table to load before display). The same lag appears when I call
[tableView beginUpdates];
[tableView endUpdates];
in order to animate the collapsing of the sections.
Note: maybe the topic of this question should be changed eliminating the EventKit part.
While I have never used EventKit, I can give you some suggestions:
NSFetchedResultsController is a great thing, especially when your data is going to be changing after your table view has initially been loaded. The NSFetchedResultsController monitors for changes for your data and the NSFetchedResultsControllerDelegate protocol allows for incremental changes to your UITableView rather than reloading an entire UITableView when changes are made to the data that populates your table. In general, avoid doing a full reloadData on your UITableView when possible.
If drawing your UITableView is really slow, chances are the source of the issue lays somewhere in the tableView:cellForRowAtIndexPath: method (almost guaranteed, although other culprits could be other UITableViewDataSource methods such as tableView:titleForHeaderInSection: or
tableView:titleForFooterInSection:). If you are reading from disk, not using cached UITableViewCells, or doing heavy drawing in these methods, it could cause your UITableView to be extremely slow to reload / scroll.
If you find that you are reading from the disk or performing some other slow operation in tableView:cellForRowAtIndexPath:, consider performing one read from the disk before your UITableView is drawn (i.e. in viewDidLoad:) and caching the results in an NSArray. Then your UITableViewDataSource methods can just read from the NSArray instead of the disk.
The problem is your row animations and (probably) recalculating sections. This is pretty common, and is a UITableView thing rather than an EventKit or CoreData thing. More profiling should give you some ideas of how you can optimize your sections to be more performant.
Look at your implementation of the UITableViewDataSource methods that involve sections:
- numberOfSectionsInTableView:
- tableView:numberOfRowsInSection:
– tableView:sectionForSectionIndexTitle:atIndex:
– tableView:titleForHeaderInSection:
– tableView:titleForFooterInSection:
You are probably doing things in those methods that are relatively heavy. Maybe to build your sections you have to look at all of your data (this is common). If that's the case, come up with a reasonable strategy for caching the section information and invalidating it when the event store changes.
All,
I hope most of you know that with ios7 there is not need to do a null check for tableview reuse
if (cell == nil) {
But unfortunately, because of that the cells are always reinitialized, as we put the code in the same method for initializing values. The problem is only with text fields inside the tableview though.
Let me explain the scenario. I have a table view with multiple rows, and some rows contain multiple text boxes. I populate the textboxes with data from server when the page is loaded. Since the cells are always re-initialized as i explained above, whatever I enter in the field goes away and the server data is re populated once i scroll down and come back to the initial stage. This is because the populating the data code is also in the same place. After fetching a reusable cell it populates the data.
Previously till ios6, we used if(cell==nil) and hence we loaded server data inside the cell and when reusing the cell, this piece of code will never be called.
I have other dirty solutions, but would like to know if someone else has a graceful way of dealing this. Please help.
You just don't store any data in the table view cell but in the model that fills the table cell. This is always the way it should be done.
Looking from the MVC standpoint than the UITableViewCell is a view. Since it is reused by iOS you should use a model to the view.
Yes, this is the expected behavior of UITableView. For performance reasons, cells are reused. Thus, it is your responsibility to populate the views in a Table View Cell every time tableView:cellForRowAtIndexPath: is called.
The thing I don't understand from your question - are you making a network call every single time a cell comes into view? If so, cache the results somewhere. Or, if it's a small amount of data, consider just doing it all in one shot at the beginning (still need to be asynchronous though).
One thing I see a lot of developers do is move a lot of code into UITableViewCell subclasses, which sounds like a good idea because it's modular, but makes solutions for problems like this more difficult. Have the Table View Data Source manage the network calls.
If you need some inspiration, look at Apple's LazyTableImages sample.
I as wondering how expensive reloadData on tableView is. I wanted to refresh the tableview every 5 seconds. Is that going to cause any performance issues?
Reload data will call the two main methods on your table view data source, numberOfRowsInSection and then iterate over cellForRow:atIndexPath: for the visible cells required, depending on your tableView's contentOffset
If your app scrolls nicely already then the only performance hit your app will endure is if you're doing a lot of work in numberOfRowsInSection (like hitting the network or something time consuming).
Edit: As noted, heightForRow:atIndexPath: can also be a pain point if you're using it to do complex calculations for different height cells.
In addition to #Jessedc answer,
If all your rows are of same height, then heightForRowAtIndexPath is more expensive than tableview.rowHeight.
If you have a lot of rows with varying heights for each row, and you are calculating the height of the row each time the heightForRowAtIndexPath method is called, then its even more expensive.
There are a lot of factors involved in deciding how expensive the reloadData call is. Everything depends on what and how much you are doing each time the delegate methods are called.
If applicable to your situation, an NSFetchedResultsController can notify your table view when the data changes, instead of refreshing it every "n" seconds.
I have a UITableView that collects data from a database. What I would like to know is if there is some way I can iterate in the UITableView collection and check the values of the cell? The reason I ask is because I would like to update each cell based on the current value that it has (change font, size, color, etc.). I've seen in another SO post regarding this topic, but since the cells are already created and their values are changed it is a bit harder for me. I was thinking of iterating through the UITableView before I call reloadData, but any other suggestions are welcome.
You should not iterate over the cells of UITableView, because some of them (in fact, most of them) may not be present until you request them. UITableView aggressively recycles its cells, so if a cell is not visible, it is very likely that you would be creating it from scratch only to put it back into recycle queue moments later.
Changing your model and calling reloadData the way your post suggests would be the right solution. iOS will ensure that it runs the update in a smallest number of CPU cycles possible, so you do not need to worry about the cells that are already created. This is also the easiest approach in terms of your coding effort.
A table view is for displaying data. The properties of your table cells should only be written to, not read from. The appropriate way of handling this situation would be to update your underlying model objects -- the objects that you use to populate the table view -- as the data changes, and then reload the affected rows.
The issue you'll encounter is that UITableView reuses table cells. Once a table cell scrolls off the screen, it's quite likely that the table view will reuse the same cell to display a different row.
This means it's fundamentally not possible to iterate over the table cells. When you need to refresh a row because its data has changed, you should call reloadRowsAtIndexPaths:withRowAnimation: (or reloadData if all rows have changed) and if the row is visible on screen, UITableView will call your data source methods and give you an opportunity to configure the cell for display.