I'm curious just how expensive in as far as resources go is UITableView's reloadData? I have an app which will make roughly 10 subsequent HTTP requests, and as it gets data / preps, it reloads the tableView. As the data set grows larger and larger, it's becoming very sluggish. I'm trying to figure out if it's because of the amount of times I'm reloading the tableView or because of how I'm grabbing/parsing the data.
What's the best practice in this case?
From UITableView.h:
- (void)reloadData; // reloads everything from scratch. redisplays visible rows. because we only keep info about visible rows, this is cheap. will adjust offset if table shrinks
"This is cheap."
implement your table view methods well and it'll be no big deal to call this function all the time.
On a side note, you should try to use the appropriate methods to animate adding and removing rows if you are thinking of using reloadData for that.
The best practice is to have your implementation of cellForRowAtIndexPath: do as little work as possible. In fact, it really shouldn't be doing any work except populating the UITableViewCell instance with the data it needs to display.
You should be using cached UITableViewCells so you don't have to allocate a new cell each time. If you can do your parsing and such in a separate thread and make the parsed data, ready to present, accessible to cellForRowAtIndexPath:, you shouldn't have any performance problems.
You didn't say if you were using a custom UITableViewCell subclass, but if you are, deep view hierarchies can also present a performance problem, since each view in the hierarchy gets drawn. The flatter you can make UITableViewCells, the better.
Hope that gets you moving in the right direction.
Best thing to do is profile your app to see where it is slow.
That said, if your table cells are all the same height, then I think
reloadData
only has to call
cellForRowAtIndexPath
for cells that are visible on screen.
Table view reload expense is:
Figuring out how many sections and
rows per sections you have
getting row heights.
Row heights in particular are figured out for all elements of the table, anytime you call reload data.
The remaining expense is cellForRowAtIndexPath, which is usually not too bad because it only is called for as many rows as are on the screen. It can be bad when scrolling if you do not reuse cells like you are supposed to.
The key for you is probably, to ask yourself what triggers the HTML load and possibly move that into a background thread.
Boot To The Head is correct.
I'm doing a progressive one-by-one article list update in Instapaper, and I call -reloadData on each completed download. Sounds similar to what you're doing. It doesn't result in any noticeable performance slowdowns.
Related
I know that cellForRowAtIndexPath only loads visible cells. Is there a way to force it so that it loads the 3 cells below and above it?
No, cellForRowAtIndexPath only loads the current cell.
Your question suggests that you're doing something that is computationally expensive or slow in your cellForRowAtIndexPath. For example, you might be doing lazy loading of images, but also want to "prefetch" some of images you need for candidate "next" cells in order to diminish the user's experience of the lazy loading. Generally, though, you wouldn't actually prefetch the UITableViewCell objects, themselves, but rather just the pieces of data that those cells need.
You might need to give us more information about what sort of stuff you feel the need to prefetch, and we can provide better counsel. It's a non-trivial issue, somewhat contingent upon having a well-designed model that your controller uses when presenting the view. You might want to share a little about your model and the nature of the stuff that you want to make sure is on-hand for the previous three and next three cells.
UITableViews usually take care themselves of the whole process of deciding when to load specific cells. They automatically remove invisible cells from it, sometimes storing those cells in an internal reusability queue.
UITableView provides a mechanism you can use to speed up the cell creation process by retrieving pre-alloc'd cells when available. If you use this method properly you should have no trouble with the scrolling speed in your table views. To do so, you need to configure the reusabilityIdentifier for a cell on creation, and call the method -[UITableView dequeueReusableCellWithIdentifier:] when you need a new cell in your cellForRowAtIndexPath: implementation.
It should be feasible for you to have your own queue of reusable and preconfigured cells if you still need more speed - although the advantages you can get from implementing something like this remain to be seen (you'd have more rows ready to use, but would also slow down cellForRowAtIndexPath: for the cell being requested). You would also need to be very careful not to clash with UITableView's standard queue.
Check out Apple's UITableView reference for more info on the reusability mechanism.
I've decided that I don't want to ever use UIPickerView again... it's completely inflexible in terms of functionality, design, and size (height). It also occasionally gets stuck between rows, and the delay that occurs between letting go of a wheel and when the delegate method is fired indicating that a new row has been selected (because of the "settling in" animation) has caused lots of problems in the context of the apps I've been working on.
That being said, the user-friendly aspects of UIPickerView are good, and I'd like to try to replicate it. I've tried to research different ways that this might be done, but without much success. Does anyone have any ideas as to what would be involved to make something similar from scratch?
I was trying to get a UITableView subclass to behave in such a way that whatever cell was currently in the middle of the table (it would change while dragging, etc.) would change its background colour to something different implying that it was "selected". As soon as the table was dragged such that the "selected" cell was no longer in the middle, the cell would go back to normal and the new middle cell would change colour. So this would be like UIPickerView in a sense that you don't have to tap on a cell; instead you just drag to have one selected by default.
I figured it should have been easy enough to intercept the "touchesMoved" method of UITableView and add some code that looped through all currently viewable cells in the table, checking to see if their frames overlapped the center point of the table, and changing their appearance accordingly (plus sending a notification to other classes as needed to indicate the "selection" change). Unfortunately, I can't get this to work, as the "touchesMoved" method doesn't get called when I drag the table. Am I missing something obvious?
Any ideas or suggestions would be very much appreciated at this point... I made an app that relied heavily on UIPickerView objects, and because of the problems I've run into with them, I'll have to abandon it unless I can figure out a way to make this work.
Thanks very much,
Chris
Remember that a UITableView is a subclass of a UIScrollView, and the UITableViewDelegate gets all the UIScrollViewDelegate method calls too. scrollViewDidScroll: sounds like it would easily fit the bill for knowing when the table view was scrolled.
As for finding which row is in the middle of the view, just use indexPathForRowAtPoint:.
I have a UITableView that contains several cells and some of them (the ones for files that are still uploading) have both an UIActivityIndicator and an UIProgressView. The ones for files that are finished use a different icon (instead of the activity indicator) and hide the progressview.
This table is using a NSFetchedResultsController as data source, so I get the updates on the data model and update the content.
Everything works just fine. The problem, however, is performance. Every time I call reloadData my UIActivityIndicators flicker, and it's not very smooth. Although I'm caching from the nib file, reloadData will have to calculate the new progress % for the ProgressView and I don't do anything with the ActivityIndicator other than hiding it if upload is complete.
Anybody ever tried something similar? Is there a workaround?
I was thinking about having an array of my progressview references and use that instead of calling reloadData.. not sure if this is the correct approach.
Thanks,
Fernando
When you call reloadData on the UITableView, all the cells of the table view are completely refreshed, re-assembled and redrawn. All the old ones are thrown away. This means that all the subviews of the UITableViewCells are removed and re-created too (including your UIActivityIndicator and UIProgressView). The refresh causes these views to flicker, or perhaps jump back to their start state. As there is no way of setting the frame of a UIActivityIndicator, your suggestion of restoring some progress value simply isn't possible.
Instead, perhaps you should try and engineer your "refresh" to not require a complete refresh of the table? For example, if you want to change the text of a UITextField within the view, you could simply access this text field and set the text property (no refresh is required). Or, if you want to hide your progress indicators, you could go into the appropriate object instances and set their property. You should design your app so that this is possible. Making changes this way avoids having to reload cells from scratch.
Besides the benefit of fixing your problem, using this method of updating, you should also see a large performance increase. reloadData is a very costly method to use and should be only used if it is absolutely necessary to really re-create the entire UITableView from scratch.
Hope this helps. :)
can someone please clarify this: when a table cell scrolls off the screen, does it still reside in memory? The reason why I am asking this is, I had to use a tableview that has to handle too many cells.
when you scroll the table up, it loads next set of visible cells. At this point, i want to make sure , that all the cells thats been scrolled off, wont be added as a heap slowing the performance. Thanks,
If you queue the cells then each cell, as soon as it disappears from the screen (that is, it is scrolled up or down), is re-used for other new cells entering in the screen.
This means that if your screen can show no more than 7 cells at the moment, the memory taken for cell allocations will not be higher than the one needed for exactly 7 of them.
The advantage of this approach is memory saving but also performance improvement as you don't need to alloc/init the cells each time.
Of course this is valid if you appropriately autorelease cells when created, if you use the same queue identifier and of course it is independent on your way to manage the data you will insert in the queue (images, strings, ...)
I'm new to iPhone development. I'm working on a table view (default UITableView subclass) that contains complicated custom cells, with multiple subviews and controls. Scrolling is not very smooth, but I'll just leave that for now.
The question is, when I'm scrolling the table view with quick swipes, the table sometimes suddenly stops scrolling and the scroll indicator will not disappear, and I have to swipe again to make it scroll.
If the table contains very few rows, say, 5 or 6, it never stuck. The custom cell class I used is from the example provided here: http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/
Can anyone give a hint or solution to this problem? Thanks in advance.
Table cells are only created when needed, that is when they come into view and they are usually unloaded and released when they go out of view.
Put in an NSLog( #"Cell loading" ); in your cell creation code and check the console to see this happen as you scroll.
Are you using caching? The docs demonstrate how you can cache table cells to improve performance. What else are you doing when you're creating table cells? If there's any performance slow downs you should probably not have that happen while creating cells.
What I do is I generate all my content before the table loads and when cells are created all that content is simply placed into the view.
Any kind of drawing will drastically reduce performance especially if you're using transparency.
For posterity, and only valid if you are using Unity-iphone: this problem was driving me insane and I fixed it with the suggestion in this post:
http://forum.unity3d.com/threads/113815-quot-Sticky-quot-scrolling-in-UITableView-and-UIScrollView-when-interfacing-Unity-and-Obj-C
Changing the preprocessor flag to
#define USE_DISPLAY_LINK_IF_AVAILABLE 0
gave the issue a happy ending.
I'd take a look at your cellForRowAtIndexPath method - for a couple of possible problems.
If you aren't using the cell reuse that will slow things down a lot and if you are re-allocating or re-initializing your custom cells in the cellForRowAtIndexPath delegate method that can cause big performance issues.
If you post your code for that method we can give you some hints as to what might be causing it.