In my app, it have about thousand contents to display in tableView. Each of content has different heights since there are one to three lines UILabel in it. Currently, it calculates and returns the heights of each cell in the tableView delegate function:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
And the way it calculate is:
contentCell = (RSContentViewCell *)self.tmpCell;
UIFont *font = contentCell.myTextLabel.font;
width = contentCell.myTextLabel.frame.size.width + 30;
size = [contentStr sizeWithFont:font
constrainedToSize:CGSizeMake(width, CGFLOAT_MAX)
lineBreakMode:contentCell.myTextLabel.lineBreakMode];
height = size.height;
return height;
It works but takes about 0.5 secs to calculate those heights, so the UX is not so good, since the app will be no response during the calculation process.
So what's the correct way and where is the correct place to calculate heights of these cell?
UPDATE
The data is from the server and requested at the time that entering the table view.
As you are laoding your Data from a Server you do have a delay no matter what.
=> I suggest you do the Height calculating in background bevor you reload the table / remove the spinner etc.
// Method you call when your data is fetched
- (void)didReciveMyData:(NSArray *)dataArray {
// start background job
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.myCachedHeightArray = [[NSMutableArray alloc] initWithCapacity:[dataArray count]];
int i = 0;
for (id data in dataArray) {
float height;
// do height calculation
self.myCachedHeightArray[i] = [NSNumber numberWithFloat:height];// assign result to height results
i++;
}
// reload your view on mainthread
dispatch_async(dispatch_get_main_queue(), ^{
[self doActualReload];
});
});
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [[self.myCachedHeightArray objectAtIndex:indexPath.row] floatValue];
}
I put this into my custom cell subclass usually... so the code doesn't clutter my controller and is correct for the cell I use. (+ it is better suited to MVC that way... the height of a cell is a view property IMO)
I DONT measure with each cell, but with a static method - see https://github.com/Daij-Djan/TwitterSearchExampleApp (the ViewController and the DDTweetTableViewCell class for an example)
You still have to do this on the front end but only do it once and cache the results in an Array and use that for future calls.
i.e. The first time the array value is empty so calculate it and store it in the array.
The next time the array has a value so use that without having to calculate.
How do you set/get self.tmpCell ? Do you save the reusable cell in a property?
Instead getting text from cell and calculating the size you can calculate the size of text from data source of the cell. I mean you set the texts in cellForRowAtIndexPath: somehow (eg. from an array) just use the text from that to calculate it.
For the frame : The cells have the same width of tableview
For the font : Just write a method called
- (UIFont *)fontForCellAtIndexPath:(NSIndexPath *)indexpath
and use it also from cellForRowAtIndexPath. It makes your job easier if you change the fonts of texts later.
Related
I have List of Hindi Text from Web-serviceAnd want to display in UILabel.
I am trying this using following code.
For testing I set static text Like:
NSString *unicode=#"ये भी एक तमाशा है इश्क और मोहब्बत में... - ये भी एक तमाशा है इश्क और मोहब्बत में दिल किसी का होता है और बस किसी का चलता है.";
cell.lblPostContent.text = unicode;
In this way problem is that...My UITableview is not smoothly scrolling. too much lag in UITableView. Is there any way to display this text properly in Hindi language and also to have smooth scrolling on UITableView?
This is My cellForRowAtIndexPath........................
- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath{
cell=(GroupDetailCustomeCell*)[tableview dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell==nil){
cell=[[GroupDetailCustomeCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
NSString *unicode=[[post.postContent URLDecode] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if(unicode==nil)
unicode=[post.postContent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if([post.postType isEqualToString:#"0"]){
//textonly
cell.lblPostContent.text =unicode;
}
}
return cell;
}
tableView component is a most hard to learn component in whole platform as 90% of iOS applications are data in a tableView. There are many things which could provide this incorrect behaviour. You could possibly try some of that options to fix it:
Most common reason for this type of problems in tableView is heightForRowAtIndexPath method. Try to override it and count real cells height there.
Example:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *yourHindiText = [NSString stringWithString:#"some text"];
CGSize yourTextSize = [yourHindiText sizeWithFont:[UIFont fontWithName: #"SomeFont" size:someSize] constrainedToSize:kLabelFrameMaxSize];
return self.tableView.estimatedRowHeight + yourTextSize.height;
}
Try to use estimatedRowHeight property for your cells. As the Apple said, this property providing a nonnegative estimate of the height of rows can improve the performance of loading the table view. If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.
When you create a self-sizing table view cell, you need to set this property and use constraints to define the cell’s size.
Example:
self.tableView.estimatedRowHeight = 44.0; //in your viewDidLoad method
Always use dequeueReusableCellWithIdentifier:forIndexPath: method in your cellForRowAtIndexPath: if you still not.
Example:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; //in your cellForRowAtIndexPath method.
You may set the row height for cells if the delegate doesn’t implement the tableView:heightForRowAtIndexPath: method with rowHeight property.
Example:
self.tableView.rowHeight = UITableViewAutomaticDimension; //in your viewDidLoad method
There are could be more and more solutions for this problem (like using Core Text Framework or creating layers for using GPU instead of CPU to offload it etc).
You could find plenty of possible solutions here. Please pay attention on the first one.
If limiting is not a problem, you can limit Hindi characters to improve UITableView scroll lag.
Here is the code to limit in swift 3.
extension String
{
func limit(by:Int) -> String {
let startIndex = self.startIndex
let endIndex = self.index(startIndex, offsetBy: by)
let range = Range(uncheckedBounds: (lower:startIndex , upper: endIndex))
return self[range]
}
}
Hope this helps.
I'm trying to create detail view controller as a list of information and I think it would be nice and clean to present this with a static UITableView. But after that it came to my mind that on some level it might be difficult, so please resolve my doubts!
Every UITableViewCell has different style (some are custom, some are basic and few are right-detailed etc.).
What is more, content size of each cell may vary as I have long names put inside labels so they use autolayout to fit.
There is no problem when I have the same cells repeating but with different tex inside UILabels. In that case I use a simple:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.prototypeCell) {
self.prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:#"ActivityCell"];
}
[self fetchedResultsController:[self fetchedResultsController] configureCell:self.prototypeCell atIndexPath:indexPath];
CGSize size = [self.prototypeCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
I don't know how to deal with heightForRowAtIndexPath. I can give an identifier to each cell, call cellForRowAtIndexPath:, and make a big switch or if statement, but is it right? The same problem occurs while I think of cellForRowAtIndexPath: and populating those UITableViewCells. With those testing statements this code won't be pretty and readable.
Any ideas on that case?
In the delegate function of the table view named heightForRowAtIndexPath try to calculate the height for each row and then return it.
//return height for row
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView==tblLanguage)
{
//Here calculate the dynamic height according to songs count for specific language
return (([[arrSongListForSpecificLanguage objectAtIndex:indexPath.row] count]*40)+40);
}
return 40.0;
}
Almost every time I write an app for a client I have to implement some kind of 'hack' to get UITableViewCells to dynamically become the right height. Depending on the content of the cells this can vary in difficulty.
I usually end up running through code that formats the cell twice, once in heightForRowAtIndexPath: and then again in cellForRowAtIndexPath:. I then use an array or dictionary to store either the height or the formatted cell object.
I've probably written and rewritten this code 20 times over the past 2 years. Why did Apple implement it in this order? It would be much more straightforward to configure the cells and THEN set the height either in cellForRowAtIndexPath: or shortly thereafter in heightForRowAtIndexPath:.
Is there a good reason for the existing order? Is there a better way to handle it?
Best guess: UITableView needs to know the total height of all cells so that it can know the percentage of scroll for the scroll bar and other needs.
Actually, in iOS 7, it doesn't have to work that way. You can now set an estimated height for your rows and calculate each row's real height only when that row is actually needed. (And I assume that this is exactly because people made complaints identical to yours: "Why do I have to do this twice?")
Thus you can postpone the height calculation and then memoize it the first time that row appears (this is for a simple one-section table, but it is easy to adapt it if you have multiple sections):
- (void)viewDidLoad {
[super viewDidLoad];
// create empty "sparse array" of heights, for later
NSMutableArray* heights = [NSMutableArray new];
for (int i = 0; i < self.modeldata.count; i++)
[heights addObject: [NSNull null]];
self.heights = heights;
self.tableView.estimatedRowHeight = 40; // new iOS 7 feature
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
int ix = indexPath.row;
if ([NSNull null] == self.heights[ix]) {
h = // calculate _real_ height here, on demand
self.heights[ix] = #(h);
}
return [self.heights[ix] floatValue];
}
You supplied an estimated height, so all the heights are not asked for beforehand. You are asked for a height only before that row actually appears in the interface, either because it is showing initially or because you or the user scrolled to reveal it.
NOTE Also, note that if you use dequeueReusableCellWithIdentifier:forIndexPath: the cell you get has the correct final height already. That is the whole point of this method (as opposed to the earlier mere dequeueReusableCellWithIdentifier:).
Currently I have webviews loading in customized uitableview cells. The problem is the web views have variable sizes. In webViewDidFinishLoad I am able to set the size of the web view based on the actual size of the html document just fine. My problem is the table cells which have already had their height set in heightForRowAtIndexPath before the web views having finished loading. How can I change the height of a table cell after it has already been loaded?
Ideally I feel like I should be able to use some line of code like this.
cellW.frame = cellW.cellWebView.frame;
However I don't seem to have access to cell information in heightForRowAtIndexPath. I've felt like I've explained the situation fairly well, but any code you think I should post I can put up here. I've tried a lot things (so there comments and failed attempts at this everywhere), but the main issue is I can't seem to access cell information in the right places such as heightForRowAtIndexPath. If I could even set cell information somehow in webViewDidFinishLoad, I could simply set the frame of the cell where I am also setting the frame size of the web view.
Below is the setup for my table cell subclass.
#import <UIKit/UIKit.h>
#import "DefinitionsAndConstants.h"
#interface UITableViewCellWebView : UITableViewCell
{
UIWebView *cellWebView;
}
#property (nonatomic,retain) UIWebView *cellWebView;
#end
Here is what I have tried last trying to use part Gavin's code. But of course there is no way to set the table cell now that I've gotten out because cellForRowAtIndexPath is not assignable.
-(void)webViewDidFinishLoad:(UIWebView *)webViews {
[webViews stringByEvaluatingJavaScriptFromString:#"twitterfy()"];
[webViews stringByEvaluatingJavaScriptFromString:#"document.getElementById('tweettext').innerHTML=tweet"];
NSString* h = [webViews stringByEvaluatingJavaScriptFromString:#"getDocHeightMax()"];
int height = [h intValue];
webViews.frame = CGRectMake(0.0f, 0.0f, 320.0f, height);
NSString* i = [webViews stringByEvaluatingJavaScriptFromString:#"return indexPath"];
int ii = [i intValue];
NSIndexPath* ip = [NSIndexPath indexPathForRow:ii inSection:0];
UITableViewCellWebView *tableCell = (UITableViewCellWebView *)[self.tableView cellForRowAtIndexPath:ip];
if (tableCell) {
tableCell.frame = webViews.frame;
}
NSLog(#"Got height from webview %d", height);
}
I would set up a mutable array to read heights from. In viewDidLoad, you'll have to assign some starting values (probably just a bunch of #"44.0", it doesn't really matter, it won't be used for much yet). In your heightForRowAtIndexPath:, just return the number from that array. Then in webViewDidFinishLoad, replace the heights in the array with the height you actually need for that cell (the NSString *h in the code you posted, I believe), and call reloadData on your table view. reloadData will hit heightForRowAtIndexPath:, which will look at the heights array, which now has the actual height needed for that cell, so everything should be shiny.
You should be able to do something like this for your heightForRowAtIndexPath method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *tableCell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
if (tableCell) {
return tableCell.cellWebView.frame.size.height;
}
return 44;
}
This way, if the table cell is visible, it'll set the height according to the height of the embedded web view, otherwise it'll use a default height. In order to make the table view refresh the height of the cell, you'll have to call the following once the web view is sized properly:
[self.tableView beginUpdates];
[self.tableView endUpdates];
That won't make it reload the cells, which could screw things up for you, it'll just make it check the heights again, and the cell height will animate to the new height.
I have a custom cell shown in attachment:
I am using a UIWebView to display tweet text. Some of the tweets are short and I want to resize the web view, move up the Time Since, and then finally resize the cell. I know how to resize the web view, but don't know how to figure out how to move the time since and resize the cell.
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSFetchedResultsController *fetchedResultsController = nil;
if (self.tweetsSegmentedControl.selectedSegmentIndex == REPORTERS_SELECTED_INDEX)
{
fetchedResultsController = self.reportersFetchedResultsController;
}
else if (self.tweetsSegmentedControl.selectedSegmentIndex == PLAYERS_SELECTED_INDEX)
{
fetchedResultsController = self.playersFetchedResultsController;
}
Tweet *tweet = [fetchedResultsController objectAtIndexPath:indexPath];
//Calcaulate height of box, instead of return a fixed number
return 170;
}
I know how to resize the web view
Don't. Use autosizing to stretch the UIWebView with height of the cell.
how to move the time since
Don't. Use autosizing to pin Time Since to the bottom of the cell.
resize the cell
Use -tableView:heightForRowAtIndexPath:
Update
The way I did this before is to set a known size for the flexible view (for you the web view) in the prototype cell and set a known size for the prototype cell. In this case we'll say the web view is kPrototypeViewHeight and the prototype is kPrototypeCellHeight.
In the table view controller:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIWebView *webView = … // Whatever you need to do to get the web view
CGFloat webViewHeight = webView.frame.size.height;
return kPrototypeCellHeight - kPrototypeViewHeight + webViewHeight;
}
Note: This will be done before -tableView:cellForRowAtIndexPath: is called. So you need to know your size before you have to draw yourself.
Note 2: Because you are changing the size of webView yourself, you will not want it autosized.
Note 3: You will be redering each web view in your table to calculate your height. My guess is scrolling will be very slow.
Hope that helps.