Using UIWebView in a UITableViewCell to display dynamic content - ios

I need to display non-editable text with different styles and colors in table view cells. In researching, it seems that UIWebView is the way to achieve the style/color flexibility that I want. Here is what I did: (note iOS 5, XCode 4)
Created a class WebViewTableViewCell derived from UITableViewCell. The class contains a UIWebView.
In storyboard, I created the table view controller with 1 dynamic cell prototype containing a UIWebView object. The cell is of type WebViewTableViewCell. I made the connection between the class UIWebView and the storyboard.
In the table view controller code, overrode "heightForRowAtIndexPath" and used "sizeThatFits" to dynamically adjust the cell height
When I run, all of this "works", except that the cells do not resize to fit all of the text contained in the web view. In fact they do not resize at all; they are the height as drawn in the storyboard. Here is some sample code:
cellForRowAtIndexPath:
UITableViewCell *cell = nil;
WebViewTableViewCell *webCell = (WebViewTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"cellid"];
// Now populate the cell with dynamic data
NSDictionary *thisData = [self.dynamicData objectAtIndex:indexPath.row];
NSString *myString= [NSString stringWithFormat:#"%#. %#",
[thisData objectForKey:#"key1"],
[thisData objectForKey:#"key2"]];
[webCell.webView loadHTMLString:myStringbaseURL:nil];
cell = webCell;
return cell;
heightForRowAtIndexPath:
WebViewTableViewCell *webCell = (WebViewTableViewCell *) [self tableView:a_tableView cellForRowAtIndexPath:indexPath];
// Now that we have the cell, let it determine what its height should be.
CGFloat rowHeight = 45; // default unless if a problem accessing the cell
if( webCell )
{
CGSize size = [webCell.webView sizeThatFits:CGSizeZero];
rowHeight = rowHeight < size.height ? size.height : rowHeight;
}
One more thing - the cells, although not the correct height, are scrollable. I want to make them a fixed height and not scrollable.
Any thoughts?
--John

To disable scrolling you can just set tableView.scrollEnabled = NO. To get the correct height you can set your view controller as the webview delegate, and do something like this:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *result = [webView stringByEvaluatingJavaScriptFromString:#"var height = 0; for (var i=0; i < document.all.length; i++) { document.all[i].height; if (document.all[i].height > height) { height = document.all[i].height; } }"];
CGFloat height = [result floatValue];
}
I'm sorry about the big JavaScript, but I think it will work. However, I recommend you to use the built in UILabel that supports NSAttributedString, or an open source UILabel subclass if you support iOS versions prior to 6.

Here is a better aproach to custom tableview.

Related

UITableView cells do not respond to input after resizing

I am adding new rows in my table view and resizing it according to its contents, but after the resize, the cell contents, like a button and didSelectRowAtIndexPath: are not being invoked.
Here is the resizing code:
CGRect frame = cell.replayTableView.frame;
int height = (model.replyComments.count*61)+3;
frame.size = CGSizeMake(cell.replayTableView.frame.size.width, height);
cell.tableviewReplay.refTable.frame=frame;
Assign a height on heightForRowAtIndexPath
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.Row == 1)
return 50.0;
else if (indexPath.Row == 2)
return 60;
}
First if you are using auto layouts you have to update constraints also .
In your code you are getting frame from a different view
CGRect frame = cell.replayTableView.frame;
after changing height
CGRect frame = cell.replayTableView.frame;
int height = (model.replyComments.count*61)+3;
frame.size = CGSizeMake(cell.replayTableView.frame.size.width, height);
but then setting it to different view
cell.tableviewReplay.refTable.frame=frame;
This may be creating issue for you.
// Declare a variable in yourController.h CGFloat tableHeight;
// set height in heightforRowAtindex
// run loop for number of rows in ur tableview data array
// put this code when ur data array is not empty i.e after responce of webservice etc
// as your tableview is and custom cells are not created programatically , so you need viewDidLayoutSubviews to reset frames
tableHeight = 0.0f;
for (int i = 0; i < [dataArray count]; i ++) {
tableHeight += [self tableView:self.multipleArticletableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self viewDidLayoutSubviews];
// put code below in viewDidLayoutSubviews method . it will work
self.multipleArticletableView.frame = CGRectMake(self.multipleArticletableView.frame.origin.x, self.multipleArticletableView.frame.origin.y,self.multipleArticletableView.frame.size.width, tableHeight);
Most common cause for this kind of issues are cells having some UI components that intercepts the user tap and do not pass on the tap to underneath cell. You can fix this by disabling user interaction on those components.
For example if you had added a UIView in your cell then call
otherView.userInteractionEnabled = NO;

UILabel Height is not updating in UITableView at launch

I am trying to set height of UILabel dynamically in the UITableView. During the launch height update is not reflected but as soon as I scroll down and scroll up back, update can be seen.
At Launch
After Scroll down and Scrolling back up again - This what I need. See the change in text in front of player icon. I need the complete text at launch itself.
Here is the code that I am trying to use:
- (void) updateMessageTextForRow:(long)row ofCell:(ESGameStreamCellView *)cell
{
NSString *item = _gameFeedItems[row];
NSString *title = item ?: NSLocalizedString(#"[No Title]", nil);
cell.message.preferredMaxLayoutWidth = cell.message.bounds.size.width;
// Update message label height
CGSize maximumLabelSize = CGSizeMake(296, FLT_MAX);
CGSize expectedLabelSize = [title sizeWithFont:cell.message.font
constrainedToSize:maximumLabelSize
lineBreakMode:cell.message.lineBreakMode];
//adjust the label the the new height.
CGRect newFrame = cell.message.frame;
newFrame.size.height = expectedLabelSize.height;
cell.message.frame = newFrame;
NSLog(#"Message = %#, Height: %f", title, cell.message.frame.size.height);
}
During Custom TableCellView Initialization
- (void)awakeFromNib {
// Initialization code
self.backgroundColor = [UIColor clearColor];
_message.lineBreakMode = NSLineBreakByWordWrapping;
_message.numberOfLines = 0;
}
Code for the row height
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// Currently fixed height. Will be calculating dynamic height after adjusting the views.
return 300;
}
Are you using autoLayout ? I highly suspect that your constraints aren't set up properly: the UIImageView top should by tied with the titleLabel's bottom.
Also, you should use the new property for dynamic row : rowHeight and estimatedRowHeight.
You are getting the correct height after the cell's reuse : set the preferredMaxLayoutWidth property in the viewDidLayoutSubviews inside your custom cell class.
firstly, you should understand of the working flow of tableview delegate in objective c. Your cell height & position will be fixed after init. that's why you have to define each row's height in
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// Currently fixed height. Will be calculating dynamic height after adjusting the views.
return 300;
}
From this point forward, your cell's height will be fixed, even if you re-config the frame.
The best practice is some article called "Dynamic height tableview cell" and you can easily find it here
http://www.raywenderlich.com/87975/dynamic-table-view-cell-height-ios-8-swift
I learn on above article (thanks for Joshua Greene) and re-write it to another library allow you to make a dynamic tableview easily. You can find it here
https://github.com/EugeneNguyen/XBMobile
it's not too perfect, but hope that if can help.

how to get the frame of the label which is in the view in table view cell in ios

I have used the following code to dynamically change the row height in ios. But the height is too high. I dont know where i made mistake. My code is as follows:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(!self.customCell){
self.customCell = [self.goalDetailsTableview dequeueReusableCellWithIdentifier:#"GoalDetailsCell"];
}
NSMutableDictionary *cellData = [self.databaseCall transactionFromDatabase:indexPath.row goalId:self.goalId andStageId:self.stageId];
//Cell Layout
self.customCell.tipsDescription.text = [cellData objectForKey:#"tipsDescription"];
[self.customCell.tipsDescription sizeToFit];
//Height of cell
float height = (CGRectGetMaxY(self.customCell.tipsDescription.frame) + 20);
return height;
}
Note: tipsDescription is a UILabel inside the UIView which is kept inside the UITableViewCell.
You need to calculate your text height and based on it have to set cell height (yes, its going to be little mind expensive but at last you'll be happy!). Here's the one solution for this, Replacement for deprecated sizeWithFont: in iOS 7? and if you want some more info, Calculating number of lines of dynamic UILabel (iOS7) and this too UITableViewCell with UITextView height in iOS 7? (I know its not for UITextView but you can consider it with UILabel).
With iOS 8 we have self sizing table view cells. With it, there is no need to even implement heightForAtIndexPath.
Step 1) Set up constraints for your table cell
Step 2) In viewDidLoad:
self.tableView.estimatedRowHeight = 100.0; //or whatever value you need
self.tableView.rowHeight = UITableViewAutomaticDimension;
And that's it. I would also mention a few of us have ran into an issue where you might need to reload the table view's data in viewDidAppear.

Explicitly set row height for some rows but use estimatedRowHeight for others

I have a UITableViewCell which contains a UIWebView as a subview. The web view fills the entire cell. I am using estimatedRowHeight to calculate the row height. However, at the time the table view is built, the cell has no height because the web view has not loaded it's content, therefore the cell has no content. Because of this, estimatedRowHeight returns 44 instead of the correct height of the web view content.
Does anyone know how I can correctly calculate the height of a row when the content is not immediately set? Is there a way to estimate the height of some cells and explicitly set the heigh of other cells, in the same table view?
This is how I am using estimatedRowHeight:
self.tableView.estimatedRowHeight = 200;
self.tableView.rowHeight = UITableViewAutomaticDimension;
I am not using the delegate method. I have tried it, but it does not change the result. The cell I am using has a xib which also uses Auto Layout constraints. To be clear, the cell does appear and the web view does get added to the cell. The problem is that the height of the cell is not big enough to show the entire web view. The web view loads an HTML embed code for an audio player.
I did some fiddling around and found out that you can use UITableViewAutomaticDimension in the following way:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
TableSection *tableSection = (self.tableModel.sections)[indexPath.section];
if (tableSection.sectionType == TableSectionTypeWebView)
{
return 120;
}
else
{
return UITableViewAutomaticDimension;
}
}
This basically says, use a height of 120 for any WebView sections but for everything else I want you to figure out the height. I am using a my own custom table model here (i.e. TableSection, sectionType, etc...)
I had to add self.tableView.estimatedRowHeight = 200; to my init method for that to work.
Now I can provide an estimated row height but also explicitly set a row height for some sections, or even some rows if I wanted.
I haven't seen any documentation for this, but I tested it with variable length strings and it held up just fine.
You make your class a UIWebViewDelegate and then in and set your class as a delegate to every single UIWebView in your UITableViewCell
-(void)webViewDidFinishLoad:(UIWebView *)aWebView {
CGRect frame = aWebView.frame;
frame.size.height = 1;
aWebView.frame = frame;
//Asks the view to calculate and return the size that best fits //its subviews.
CGSize fittingSize = [aWebView sizeThatFits:CGSizeZero];
frame.size = fittingSize;
aWebView.frame = frame;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
Now you can get the height of the UIWebView and set it to the rows height, because the following method will be called again once a 'beginUpdates' is invoked
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath*)indexPath {
return _webView.frame.size.height;
}
Hope This helps

The best way to calculate UITableViewCell height without contentView frame

SUMMARY
Given that we don't always know what the frame of a cell or its content view is going to be (due to editing, rotation, accessory views etc.), what is the best way to calculate the height in tableView:heightForRowAtIndexPath: when the cell contains a variable height text field or label?
One of my UITableViewController's contains the following presentation:
UITableViewCell with UITextView.
UITextView should be the same width and height as UITableViewCell.
I created the UITableViewCell subclass, and then and initialized it with UITextView (UITextView is a private field of my UITableViewController)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"TextViewCell";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[BTExpandableTextViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier textView:_notesTextView] autorelease];
}
return cell;
}
I implemented the following method in my UITableViewCell subclass:
- (void)layoutSubviews{
[super layoutSubviews];
CGFloat height = [textView.text sizeWithFont:textView.font constrainedToSize:CGSizeMake(textView.frame.size.width, MAXFLOAT)].height + textView.font.lineHeight;
textView.frame = CGRectMake(0, 0, self.contentView.frame.size.width, (height < textView.font.lineHeight * 4) ? textView.font.lineHeight * 4 : height);
[self.contentView addSubview:textView];
}
and of course i implemented the following UITableViewDataSource method (look! I am using self.view.frame.size.width (but really i need UITableViewCell contentView frame width):
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
CGFloat height = [_notesTextView.text sizeWithFont:_notesTextView.font
constrainedToSize:CGSizeMake(self.view.frame.size.width, MAXFLOAT)].height;
CGFloat groupedCellCap = 20.0;
height += groupedCellCap;
if(height < [BTExpandableTextViewCell minimumTextViewHeightWithFont:_notesTextView.font]){
height = [BTExpandableTextViewCell minimumTextViewHeightWithFont:_notesTextView.font];
}
return height;
}
also I implemented the following method (thats not so important but ill post it anyway, just to explain that cell's height is dynamical, it will shrink or expand after changing text in UITextView)
- (void)textViewDidChange:(UITextView *)textView{
CGFloat height = [_notesTextView.text sizeWithFont:_notesTextView.font
constrainedToSize:CGSizeMake(_notesTextView.frame.size.width, MAXFLOAT)].height;
if(height > _notesTextView.frame.size.height){
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
}
And now, my question is:
After loading view, UITableViewController is calling methods in the following order: (ill remove some, like titleForHeaderInSection and etc for simplification)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
and only then
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Look! I should return the correct UITableViewCell height before cellForRowAtIndexPath!
That means: I don't know UITableViewCell contentView frame. And i can't get it programmatically.
This width can be one of:
iPhone plain table, portrait orientation
iPhone plain table, landscape orientation
iPhone grouped table, portrait orientation
iPhone grouped table, landscape orientation
and the same for the iPad ( another 4 values )
And don't forget that contentView frame can be smaller because of UITableViewCell accessoryType, or because of UITableView editing state. (for example if we have UITableViewCell with multiline UILabel of any height in any editing state and with any accessoryView)
So this problem is fundamental: I just can't get cell contentView frame width for constraining, because I should return this height before cell layouts contentView. (And this is pretty logical, by the way) But this contentView frame really matters.
Of course sometimes I can know this width exactly and "hardcode" it
(for example: UITableViewCellAccessoryDisclosureIndicator has 20 px width, and tableView cannot be in editing state, then I can write self.view.frame.size.width - 20 and the task is done)!
Or sometimes contentView is equal to UITableViewController's view frame!
Sometimes I'm using self.view.frame.width in -tableView:heightForRowAtIndexPath: method.. (like now, and it works pretty well, but not perfectly because of grouped UITableView, should subtract some constant values, and they are different for 2 devices * 2 orientations)
Sometimes I have some #defined constants in UITableViewCell (if I know width exactly)...
Sometimes I'm using some dummy pre-allocated UITableViewCell (what is just stupid, but sometimes is pretty elegant and easy for use)...
But I don't like anything of that.
What's the best decision?
Maybe i should create some helper class, that will be initialized with such parameters:
accessory views, device orientation, device type, table view editing state, table view style (plain, grouped), controller view frame, and some other, that will include some constants (like grouped tableView offset, etc) and use it to find the expected UITableViewCell contentView width? ;)
Thanks
Table view uses the tableView:heightForRowAtIndexPath: method to determine its contentSize before creating any UITableViewCellcells. If you stop and think about it, this makes sense, as the very first thing you would do with a UIScrollView is set its contentSize. I have run into a similar problem before, and what I've found is that it is best to have a helper function that can take the content going into the UITableViewCell and predict the height of that UITableViewCell. So I think you will want to create some sort of data structure that stores the text in each UITableViewCell, an NSDictionary with NSIndexPaths as keys and the text as values would do nicely. That way, you can find the height of the text needed without referencing the UITableViewCell.
Although you can calculate heights for labels contained in table view cells, truly dynamically, in '- layoutSubviews' of a UITableViewCell subclass, there's no similar way of doing this (that I know of) for cell heights in '- tableView:heightForRowAtIndexPath:' of a table view delegate.
Consider this:
- (void)layoutSubviews
{
[super layoutSubviews];
CGSize size = [self.textLabel.text sizeWithFont:self.textLabel.font
constrainedToSize:CGSizeMake(self.textLabel.$width, CGFLOAT_MAX)
lineBreakMode:self.textLabel.lineBreakMode];
self.textLabel.$height = size.height;
}
Unfortunately though, by the time '- tableView:heightForRowAtIndexPath:' is called, that is too early, because cell.textLabel.frame is yet set to CGRectZero = {{0, 0}, {0, 0}}.
AFAIK you won't be able to do this neither with content view's frame, nor summing up individual labels' frames...
The only way I can think of is to come up with a convenience class, methods, constants, or such that will try to cover up all possible width in any device orientation, on any device:
#interface UITableView (Additions)
#property (nonatomic, readonly) CGFloat padding;
#end
#implementation UITableView (Additions)
- (CGFloat)padding
{
if (self.formStyle == PTFormViewStylePlain) {
return 0;
}
if (self.$width < 20.0) {
return self.$width - 10.0;
}
if (self.$width < 400.0 || [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return 10.0;
}
return MAX(31.0, MIN(45.0, self.$width * 0.06));
}
#end
Also note that, recently we also have new iPhone 5's 4-inch width (568 instead of 480) in landscape orientation.
This whole thing is pretty disturbing, I know... Cheers.

Resources