iPHONE: TableView Scroll isn't smooth - What could be wrong? - iphone

I have a table view as shown. It displays various European Languages from database. When I scroll the tableview the scrolling isn't smooth and at times it gets stuck for a second or two. What could be wrong?
I am decoding the languages using below code:
NSString *cellText = [[langArray objectAtIndex:i] valueForKey:[NSString stringWithFormat:#"%#",lblshortName.text]];
if (cellText != (NSString *)[NSNull null] && ![cellText isEqualToString:#""] ) {
NSString *decodedString3 = [NSString stringWithUTF8String:[cellText cStringUsingEncoding:[NSString defaultCStringEncoding]]];
[cellTextArray addObject:[NSString stringWithFormat:#"%# : %#",lblshortName.text, decodedString3]];
}
else {
[cellTextArray addObject:#"<empty>"];
}

I would suggest to do few things..
Check if you are reusing the Cell, if you are then cross check it's working by putting counter for every new cell.
Use static analyzer and instruments for checking any potential leaks, memory allocations and objects are being freed or not.
if everything is fine.. then it's hard to say anything without seeing other part of the code..
one more thing you can do is using asynchronous thread to get data ready for the table view.

It would help if you provide more code, in particular how you use table-view cells, because table-view cells can easily cause performance issues.
Regarding your code, it seems like that the entire cellTextArray is built before presenting the table view, say, when you initialize the data source of the table view. Correct? Pre-building data could be good for the performance in general, but if the array is really, really huge so it causes a memory problem, then you might want to build texts dynamically.
More likely, however, I would check the followings first :
Are table-view cells property being reused?
Are subviews in the table-view cell opaque?
Aren't table-view cells drawing the entire region every time?
Aren't you unnecessarily calling drawRect: method directly?

I have seen such behavior when .layer property is being used in a lot places so, are you accessing the .layer property of a subview in the table cell or so?

Related

How to implement a uitableviewcell who's value is derived from another uitableviewcell

I'm trying to do something thats fairly simple but I can't see the best way to do it. I have a uitableview with two cell's, the first has a uitextfield in the contextview with the input view set as uidatepicker. The cell maps back to a nsmanageobject date property. The nsmanagedobject has a dependent property which is the difference in days between todays date and the date selected. This value is displayed in the second cell when the view loads.
The problem is that when the user changes the date in the first cell the second cell is not updated automatically.
Firstly I thought I could just call setNeedsDisplay on the uitableview. But this seems rather heavy handed.
So I've been reading up on KVO and I've not managed to find a good example of a solution to my problem yet.
I'm using fetchresultscontrollers for my tableviews. I tried to implement a key path dependency as shown in the iPhone docs but this hasn't been successful.
for example...
+ (NSSet *)keyPathsForValuesAffectingDaysRemaining
{
NSLog(#"keyPathsForValuesAffectingDaysRemaining");
return [NSSet setWithObjects:#"dateOfOccasion", nil];
}
-(NSNumber *)daysRemaining {
return [self.dateOfOccasion daysFromNow]; //simple calendar calc.
}
Could someone point me in the right direction of an appropriate solutions to a relatively simple problem.
Thanks,
Gary
I think implementing a [[self tableView] reloadData] strategically should fix the problem. As soon as you change the object in cell1, I would assume you basically save the change globally or in Coredata. Simply reloading the tableView in that configuration should fix the issue.
In your case, since you are implementing the NSFetchedResultsController , I would assume objectDidChange would fire when you change the value of that object. A tableView reloadData would be a nice implementation there !
If you have the modified value , you could reload the row to update the content. You can reload row in your KVO method after updating row's value,
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);

Best practices for drawing dynamic UITableView row height

Possibly a duplicate but I couldn't find a specific question on SO, so here it is.
I'm curious about dynamically changing heights for all rows, typically, because you don't know the length of an NSString that's used for a label.
I know you must use this delegate method to change the row heights:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
The problem is this delegate method is called BEFORE the cell is created (i.e. called before cellForRowAtIndexPath).
So, what I've thought of is to create a mock cell in viewWillAppear and a method that adds cell heights to an array that maps to the table view's data source (which in my case is also an array).
viewWillAppear implements this one important method to get the height:
[NSString sizeWithFont: constrainedToSize: lineBreakMode:]
Then in heightForRowAtIndexPath I can return the cell height like so:
//cellHeights is an ivar populated in viewWillAppear
return [[cellHeights objectAtIndex:indexPath.row] floatValue];
I was wondering if there was a better way to dynamically change the row height?
I realize this will degrade performance for a large number of rows (greater than 1000, I believe). But in my case, my rows won't ever come close to that number. So the performance hit is negligible.
Thanks in advance!
Great question! In fact, I did something similar in some of my applications.
I can think of a couple of alternatives, but all of these are along the same theme. You could also just to use sizeWithFont: inside of heightForRowAtIndexPath: and do away with the array. In that case, you might take a performance hit for recalculating the size each time, if that operation is expensive.
You could do "lazy loading" of the cellHeights array inside of heightForRowAtIndexPAth: so it might look something like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cellHeights objectAtIndex:indexPath.row] == nil) {
... calculate height and store it in the array at the correct index...
}
return [[cellHeights objectAtIndex:indexPath.row] floatValue];
}
The advantage I am thinking of here is that you will only calculate the heights for cells that are definitely going to be loaded. If you do the calculation in viewWillAppear, I guess you end up doing it for every cell, regardless of whether it is displayed?
Finally, you could put the size in your data model itself. If it is, for example, an array of strings, you could make a class that has two properties: a string and a "representationSize" property. Then you can recalculate the size of the string each time the value of the string is changed. Then, there would just be one array, not two, that maps onto your data source, filled with a data class containing both the string and display size of the string, and the value would be calculated when the string changes, not at all once when the view appears.
Anyway, I would love to hear some comments about these various approaches.
Matthew's idea of putting the height in the data model sounds interesting. Here's another answer that proposes a very similar solution: How can I do variable height table cells on the iPhone properly?
I have too problem in my UITableView,when I scroll tableview (on 3gs iPhone). I saw a lot of lags. So i open time profiler (very good tool for optimization) and problem was when I call function sizeWithFont. The best solution for resolve this problem is call sizeWithFont in constructor.

UITableView and NXXMLParser ... Calling Hierarchy

I am stuck in a strange situation , i am getting data from the website using XML files and i am filling an Array (NSMutableArray Type) that i later use to display the data on Table View. The problem is that functions related to UITableView are called earlier and at that time the Array is not filled, this cause the program to crash. When this function is executed arrayData is empty and count functions returns nothing. Is there any way that i call NSXMLParser functions earlier than the UITableView functions.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrayData count];
}
Thanks,
Taimur
The array would return 0 if it existed but without containing any objects - so the app shouldn't crash. This means your array has not been initialized yet. You have propably added a pointer to your array as an instance variable and maybe as property, but you still need to create the actual object towards which that pointer should point.
So, if we're dealing with a property of a viewcontroller subclass here, add something like this to your viewDidLoad method:
NSMutableArray *newArray = [[NSMutableArray alloc] init];
self.arrayData = newArray;
[newArray release];
Your situation is not strange at all - it is extremely common when developing apps with asynchronous data loading requirements.
NSXMLParser is a SAX (event-driven) parser - it will parse data when the data is available. It is up to you when you choose to display your table, but obviously if you try to display it before the XML data is available then you will have to take steps to prevent a crash, or at the very least a bad user experience. Typically you would display an activity spinner or a "loading data..." message until the data is ready, and in a background thread load the XML. Once loaded, the BG thread should signal to the UI thread that the data is ready, and perhaps invoke reloadData on the table to load the data.

How to tell a UITableView to preload all Rows?

Is there a way to tell a UITableView to preload all rows?
The tableView is supposed to show several comments (up to 80 comments).
So my CommentCell uses a Setter to adapt the cell to a specific comment.
-(void)setComment:(Comment *)newComment {
if (newComment != comment) {
[comment release];
comment = [newComment retain];
/*
* set the cells view variables here
*/
}
}
This specific setter takes quite a bunch of processing resources and scrolling gets kinda laggy.
I am using a comment-specific reuseIdentifier instead of a static cellIdentifier when calling
dequeueReusableCellWithIdentifier:
in order to assure, that "newComment" equals the old "comment".
And in fact this does work great when scrolling over cells which have already been loaded.
But when scrolling through the comments for the first time, it still lags like hell.
Which leads me to my question:
Is there a way to tell the tableview to preload all cells? (which I doubt)
or
Do I have to implement my own cache instead of relying on "dequeueReusableCellWithIdentifier:"?
Keep in mind that your comment specific reuseIdentifier could be what is causing everything to go slow (or at least, it isn't helping). The reason we use reuseIdentifier for UITableViewCells is because if you try to allocate a new cell every time you need one it isn't as performant as if you can just reuse one that was already made.
I'd recommend pre-computing your comments so you can just set properties of your cells and reusing cells after they scroll off the tableview.
No, you have to preload your data in your dataSource. Just put everything you need in an array and fill the table's cells from that array of preloaded objects.

Items mixed up after scrolling in UITableView

When I scroll in my UITableView, the cells become mixed up.
What am I doing wrong?
This is my method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[cell insertSubview:[itemArray objectAtIndex:indexPath.row] atIndex:indexPath.row];
return cell;
}
Update
It now works by using cell.contentView, but now when I select an item, the selected one is overlayed with the content of a different cell...
TechZen's advice here is correct. It's clear from your code that you've misunderstood insertSubview:atIndex. I suspect that you probably also need a better understanding of when tableView:cellForRowAtIndexPath: does and doesn't called.
Unfortunately you've gotten some bad advice from sagar here, which may only confuse you further, especially because it may appear to work at first, but it will kill your scrolling performance and memory usage. For his benefit and yours, let me try to clarify tableView:cellForRowAtIndexPath: and the reuse identifier concept.
The key to understanding tableView:cellForRowAtIndexPath: and the reuse identifier is to understand that building a UITableViewCell is expensive. Consider all the things you need to do:
Allocate a cell
Allocate the cell's subviews.
Define the layout of the subviews within the cell.
Add the subviews to the cell.
Configure properties of the subviews such as font sizes, colors, text wrapping, resizing behaviors, etc.
Configure properties of the cell, such as accessory images, etc.
Define the specific text and/or images that you want the cell to display.
When we create a table, we usually want the cells to have the same basic configuration. They'll typically have the same number of subviews, in the same positions, using the same fonts, etc. In fact, the only thing that usually needs to vary from one cell to the next is item 7 in the list above, the text and images displayed by the cell.
Steps one through six are quite expensive (especially the memory allocation), so it would kill our scrolling performance if we were to go through those steps for every cell we created, only to throw that cell away when it scrolls off the screen. It would be better if we could save the cell when it scrolls off the screen, and then just tweak its contents and reuse it for the next cell that we need to display.
Apple recognized the need for this cell reuse optimization, so they built a mechanism for it right into UITableView. When a cell scrolls off the screen, UITableView doesn't throw it away. Instead it looks at the cell's reuse identifier string, and puts the cell into a special buffer associated with that identifier. The next time you call dequeueReusableCellWithIdentifier: with that same identifier, UITableView will pull the cell out of its buffer and hand it back to you for reuse. This cell still has all the same subviews, in the same configuration as before, so all you need to do is step 7 in our list. Simply update the cell's text and/or images, and it's ready to go.
When you use this mechanism correctly, you'll only allocate one cell for each visible row, plus one for the buffer. No matter how many rows you have in your table, your memory usage will stay low, and your scrolling will be as smooth as butter.
Sagar recommended that you use a different reuse identifier for each row. Hopefully you can see why this is a bad idea. When each cell scrolls off the screen, the table view will look at the cell's identifier, see that it's unique, and create a new buffer for that specific row. If you scroll through 10,000 rows, your table view will end up with 10,000 buffers, each dedicated to a single cell. Your scrolling will be unnecessarily slow while you create 10,000 cell objects, and your app will probably run out of memory before you get to the bottom of the table.
So go ahead and keep your common cell identifier. Inside the if (cell == nil) { } block, put all the setup code that would be common for all cells. Beneath that block, put only the code that populates the contents that are unique to each row. To access custom subviews whose contents you want to change per row, you can use -[UIView viewWithTag:], or better yet, create a subclass of UITableViewCell, and expose your custom subviews as properties of your subclass.
I think your problem here is that you are applying your row logic to the view hierarchy inside a cell instead of to the cells themselves.
This line:
[cell insertSubview:[itemArray objectAtIndex:indexPath.row] atIndex:indexPath.row];
Takes a view from an array and adds it to the cell's subviews at a particular index.row of the cell's existing subview stack. It does nothing to make sure the proper view is inserted in the proper cell itself. If you never remove the views from the previous iteration you will just see all these views stacking up within the individual reused cells.
At the very least, you need to remove all the previously added cell subviews before adding the most one. You should also only add subviews to the cell's contentView view and not to the cell itself.
So:
[[cell.contentView.subviews objectAtIndex:0] removeFromSuperview];
[cell.contentView addSubview:[itemArray objectAtIndex:indexPath.row]];