I'm loading an HTML string of arbitrary length into a UIWebView which is then displayed in a UITableViewCell. I do not want this UIWebView to scroll independently of the UITableView so I must resize its height to fit the content.
This UITableViewCell is also collapsable, not showing the UIWebView when it's in its collapsed state.
The rub is, how do I know what this height is so that I can have heightForRowAtIndexPath return a proper value and allow the table to look and scroll correctly?
Here's some example code:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0)
return 286;
if (indexPath.row == 1) {
if (analysisExpanded)
return analysisWebViewHeight + 39;
else
return 52;
}
if (sourcesExpanded)
return sourcesWebViewHeight + 39;
return 53;
}
Pretty simple. The 39 is the height of some header stuff I have in the cell with the webview.
I am loading the "expanded view" of the cell from a nib, so to get the webview I'm calling viewForTag:
UIWebView* sourcesWebView = (UIWebView*)[cell viewWithTag:1];
[sourcesWebView setBackgroundColor:[UIColor clearColor]];
[sourcesWebView loadHTMLString:placeholder baseURL:[NSURL URLWithString:#"http://example.com/"]];
sourcesWebViewHeight = [[sourcesWebView stringByEvaluatingJavaScriptFromString:#"document.documentElement.scrollHeight"] floatValue];
[sourcesWebView setFrame:CGRectMake(sourcesWebView.frame.origin.x, sourcesWebView.frame.origin.y, sourcesWebView.frame.size.width, sourcesWebViewHeight)];
sourcesWebViewHeight is an iVar that is updated by the above code in cellForRowAtIndexPath. If the user taps the cell three times, expand-collapse-expand, and does a little scrolling, eventually it gets the correct height.
I tried "Preloading" the height by using a memory-only UIWebView object, but that never returned correct numbers for some reason.
The issue was that I was attempting to get the scroll size before the web view had a chance to render it.
After implementing the didFinishLoad delegate method, my original javascript worked fine to get the height.
For iOS Safari you need to use the following line:
NSInteger height = [[sourcesWebView stringByEvaluatingJavaScriptFromString:#"document.body.offsetHeight;"] integerValue];
document.documentElement.scrollHeight is not working for this browser.
Just to be sure everything is right, you need to call this after your web-view has finished loading =)
UPDATE: another option, that will work only for iOS5, they've added scrollView property to the UIWebView, where you can get contentSize.
Since webViewDelegate can get the height of webView when HTML code is loaded,
the tableview:heightForRowAtIndexPath: method will enter an infinite loop for
trying to retrieve the webView located in the tableView Cell:
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
float height;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIWebView *webContent = (UIWebView *) [cell viewWithTag:tagTableSingleContentWeb];
CGRect frame = webContent.frame;
height = frame.origin.y + frame.size.height + 30;
return height;
}
The above is the code I tried to change the tableView cell when I insert HTML code into the webView inside tableView cell, but it will cause infinite loop....
I am still trying to find out some other way.
p.s. WebViewDelegate way is ok when webView is located in ScrollView, but I do not how to make it work for inside TableView cell yet.
More and better answers to pretty much same question can be found here as well. Just thought it might be helpful for someone.
Related
I create custom cells within my tableview some have images and are tall some are just text. The height of the cells are calculated in heightForRowAtIndexPath, which I beleive is done before cellForRowAtIndexPath is called. I want to place an imageview at the bottom of the cell regardless of heigh, but I am not sure how to get the calculated height from within cellForRowAtIndexPath?
Too late for an answer..
But, like #user216661 pointed out, the problem with taking the height of the Cell or the ContentView is that it returns the cells original height. Incase of rows with Variable height, this is an issue.
A better solution is to get the Rect of the Cell (rectForRowAtIndexPath) and then get the Height from it.
- (UITableViewCell *)tableView:(UITableView *)iTableView cellForRowAtIndexPath:(NSIndexPath *)iIndexPath {
UITableViewCell *aCell = (UITableViewCell *)[iTableView dequeueReusableCellWithIdentifier:aCellIdentifier];
if (aCell == nil) {
CGFloat aHeight = [iTableView rectForRowAtIndexPath:iIndexPath].size.height;
// Use Height as per your requirement.
}
return aCell;
}
You can ask the delegate, but you'll be asking twice since the tableView already asks and sizes the cell accordingly. It's better to find out from the cell itself...
// in cellForRowAtIndexPath:, deque or create UITableViewCell *cell
// this makes the call to heightForRow... and sizes the cell
CGFloat cellHeight = cell.contentView.bounds.size.height;
// alter the imageView y position (assuming the rest of the frame is correct)
CGRect imageFrame = myImageView.frame;
imageFrame.y = cellHeight - imageFrame.size.height; // place the bottom edge against the cell bottom
myImageView.frame = imageFrame;
You are allowed to call heightForRowAtIndexPath yourself! Just pass the indexPath from cellForRowAtIndexPath as an argument and you can know the height of the cell you are setting up.
Assuming you are using a UITableViewController, just use this inside cellForRowAtIndexPath...
float height = [self heightForRowAtIndexPath:indexPath]
Im using a tableview to display some information in a quiz app that Im working on. My question is how do i make the tableview only show the number of cells that I need. Ive set the number of rows delegate method like this:
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
but at the bottom of the table view are empty cells that are not needed. If I set the tableview style to grouped I get 5 cells and no empty ones below them. Ive seen that other people have done this but cant seem to work it out. I was wondering if they have somehow added a custom view to the table footer to cancel the empty cells out?
Any ideas or help appreciated.
If you do want to keep the separator, you could insert a dummy footer view. This will limit the tableview to only show the amount of cells you returned in tableView:numberOfRowsInSection:
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
In swift:
self.tableView.tableFooterView = UIView(frame: CGRectZero)
A much nicer method which doesn't require cell resizing is to turn off the default separator (set the style to none) and then have a separator line in the cell itself.
I was having a similar problem, how to show only separators for the cells that contain data.
What I did was the following:
Disable separators for the whole tableView. You can do that in the
inspector for the tableview in Interface builder or by calling
[yourTableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];.
Inside your cellForRowAtIndexPath where you populate your tableview with cells create a new UIView and set it as a subview to the cell. Have the background of this view lightgray and slightly transparent. You can do that with the following:
UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake:
(0, cell.frame.size.height-1,
cell.frame.size.width, 1)];
[separatorView setBackgroundColor:[UIColor lightGrayColor]];
[separatorView setAlpha:0.8f];
[cell addSubView:separatorView];
The width of this view is 1 pixel which is the same as the default separator, it runs the length of the cell, at the bottom.
Since cellForRowAtIndexPath is only called as often as you have specified in numberOfRowsInSection these subviews will only be created for the cells that possess data and should have a separator.
Hope this helps.
This worked for me - I had extra empty rows at the bottom of the screen on an iphone 5 -
In my case I needed 9 rows
- (CGFloat)tableView:(UITableView *)tabelView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return self.tableView.frame.size.height / 9;
}
You can implement heightForRowAtIndexPath: and compute the correct height to only show 5 cells on the screen.
Are you always going to have 5 rows? If it's a dynamic situation you should set the number of rows according to the datasource of the tableview. For example:
return [postListData count];
This returns the count of the records in the array holding the content.
The tableview is only going to display the number of rows and sections that you tell it to. If you're always going to have just a single section, DON'T implement the method below.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
Without this the tableview will only have 1 section. With it, as you would imagine, you can specify the number of sections.
It is quite Simple. Just set the size of the popover like this:
self.optionPickerPopOver.popoverContentSize = CGSizeMake(200, 200);
Certainly you can adjust the size (200,200) depending upon the size of contents and number if rows.
Easy way would be to shrink tableView size. I.e. 5 cells 20 points each gives 100.0f, setting height to 100.0f will cause only 5 rows will be visible. Another way would be to return more rows, but rows 6,7 and so would be some views with alpha 0, but that seems cumbersome. Have you tried to return some clerColor view as footerView?
I think u can try changing the frame of the table view, if you want to adjust with the number of cells.
Try something like this:
[table setFrame:CGRectMake(x, y, width, height*[list count])];
height refers to height of the cell
As Nyx0uf said, limiting the size of the cell can accomplish this. For example:
- (CGFloat)tableView:(UITableView *)tabelView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat result;
result = 100;
return result;
}
implement these two methods in your UITableViewController:
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
if (section == tableView.numberOfSections - 1) {
return [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
}
return nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
if (section == tableView.numberOfSections - 1) {
return 1;
}
return 0;
}
In fact, these codes are telling tableview that you don't need to render the seperator line for me anymore, so that it looks the empty cells won't be displayed(in fact , the empty cell can not be selected too)
I want to make a UITableView which automatically adjust it's cell size by it's contents.
The contents are from XML file and the data is just text.
I want to put the text in a cell and it should be automatically does word-wrap and support multi-line.
So each cell can have different height. Is this possible?? or any idea?
Thank you ;)
Yes it is possible. Make use of the following method for doing it,
- (CGFloat)tableView:(UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *)indexPath {
CGSize cellHeight;
if (indexPath.row == 0) {
cellHeight = [yourXMLContents sizeWithFont:[UIFont systemFontOfSize:13.0f] constrainedToSize:CGSizeMake(300.0, 1000.0) lineBreakMode:UILineBreakModeWordWrap];
return cellHeight.height + 20;
} else
return 30;
}
Have you looked at the three20 library? It'll resize the cells to fit the content. In the very least, you can look at how the code is implemented to resize cells.
I have implemented a custom UITableViewCell which includes a UITextView that auto-resizes as the user types, similar to the "Notes" field in the Contacts app. It is working properly on my iPhone, but when I am testing it in the iPad, I am getting some very strange behavior: When you get to the end of a line, the keyboard hides for a millisecond and then shows itself again immediately. I would write it off as just a quirky bug, but it actually causes some data loss since if you are typing, it loses a character or two. Here's my code:
The Code
// returns the proper height/size for the UITextView based on the string it contains.
// If no string, it assumes a space so that it will always have one line.
- (CGSize)textViewSize:(UITextView*)textView {
float fudgeFactor = 16.0;
CGSize tallerSize = CGSizeMake(textView.frame.size.width-fudgeFactor, kMaxFieldHeight);
NSString *testString = #" ";
if ([textView.text length] > 0) {
testString = textView.text;
}
CGSize stringSize = [testString sizeWithFont:textView.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
return stringSize;
}
// based on the proper text view size, sets the UITextView's frame
- (void) setTextViewSize:(UITextView*)textView {
CGSize stringSize = [self textViewSize:textView];
if (stringSize.height != textView.frame.size.height) {
[textView setFrame:CGRectMake(textView.frame.origin.x,
textView.frame.origin.y,
textView.frame.size.width,
stringSize.height+10)]; // +10 to allow for the space above the text itself
}
}
// as per: https://stackoverflow.com/questions/3749746/uitextview-in-a-uitableviewcell-smooth-auto-resize
- (void)textViewDidChange:(UITextView *)textView {
[self setTextViewSize:textView]; // set proper text view size
UIView *contentView = textView.superview;
// (1) the padding above and below the UITextView should each be 6px, so UITextView's
// height + 12 should equal the height of the UITableViewCell
// (2) if they are not equal, then update the height of the UITableViewCell
if ((textView.frame.size.height + 12.0f) != contentView.frame.size.height) {
[myTableView beginUpdates];
[myTableView endUpdates];
[contentView setFrame:CGRectMake(0,
0,
contentView.frame.size.width,
(textView.frame.size.height+12.0f))];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
int height;
UITextView *textView = myTextView;
[self setTextViewSize:textView];
height = textView.frame.size.height + 12;
if (height < 44) { // minimum height of 44
height = 44;
[textView setFrame:CGRectMake(textView.frame.origin.x,
textView.frame.origin.y,
textView.frame.size.width,
44-12)];
}
return (CGFloat)height;
}
The Problems
So, here's what's happening
This code is working 100% properly on my iPhone and in the iPhone simulator. As I type the text, the UITextView grows smoothly, and the UITableViewCell along with it.
On the iPad simulator, however, it gets screwy. It works fine while you are typing on the first line, but when you get to the end of a line, the keyboard disappears and then reappears immediately, so that if the user continues typing the app misses a character or two.
Here are some additional notes on the weird behaviors that I have noticed which may help explain it:
Also, I have found that removing the lines [myTableView beginUpdates]; [myTableView endUpdates]; in the function textViewDidChange:(UITextView *)textView makes the UITextView grow properly and also doesn't show and hide the keyboard, but unfortunately, then the UITableViewCell doesn't grow to the proper height.
UPDATE: Following these instructions, I am now able to stop the strange movement of the text; but the keyboard is still hiding and showing, which is very strange.
Does anyone have any ideas as to how to get the keyboard to continually show, rather than hide and show when you get to the end of the line on the iPad?
P.S.: I am not interested in using ThreeTwenty.
you should return NO in:
-(BOOL) textViewShouldEndEditing:(UITextView *)textView
if you would like to show keyboard at all times. You should handle cases, which keyboard should be hidden, by returning YES to this delegate function.
edit:
I dug a little more, when [tableView endUpdates] called, it basically does 3 things :
Disables user interaction on the tableView
Updates cell changes
Enables user interaction on the tableView
The difference between SDKs(platforms) is at [UIView setUserInteractionEnabled] method. As UITableView does not overrite setUserInteractionEnabled method, it is called from super (UIView).
iPhone when setUserInteractionEnabled called, looks for a private field _shouldResignFirstResponderWithInteractionDisabled which returns NO as default, so does not resign the first responder (UITextView)
But on iPad there is no such check AFAIK, so it resignes UITextView on step 1, and sets focus and makes it first responder on step 3
Basically, textViewShouldEndEditing, which allows you to keep focus, according to SDK docs, is your only option ATM.
This method is called when the text
view is asked to resign the first
responder status. This might occur
when the user tries to change the
editing focus to another control.
Before the focus actually changes,
however, the text view calls this
method to give your delegate a chance
to decide whether it should.
I had the same issue for an iPad app and came up with another solution without having calculating the height of the text itself.
First create a custom UITableViewCell in IB with an UITextField placed in the cell's contentView. It's important to set the text view's scrollEnabled to NO and the autoresizingMask to flexibleWidth and flexibleHeight.
In the ViewController implement the text view's delegate method -textViewDidChanged: as followed, where textHeight is a instance variable with type CGFloat and -tableViewNeedsToUpdateHeight is a custom method we will define in the next step.
- (void)textViewDidChange:(UITextView *)textView
{
CGFloat newTextHeight = [textView contentSize].height;
if (newTextHeight != textHeight)
{
textHeight = newTextHeight;
[self tableViewNeedsToUpdateHeight];
}
}
The method -tableViewNeedsToUpdateHeight calls the table view's beginUpdates and endUpdates, so the table view itself will call the -tableView:heightForRowAtIndexPath: delegate method.
- (void)tableViewNeedsToUpdateHeight
{
BOOL animationsEnabled = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:NO];
[table beginUpdates];
[table endUpdates];
[UIView setAnimationsEnabled:animationsEnabled];
}
In the table view's -tableView:heightForRowAtIndexPath: delegate method we need to calculate the new height for the text view's cell based on the textHeight.
First we need to resize the text view cells height to the maximum available height (after subtracting the height of all other cells in the table view). Then we check if the textHeight is bigger than the calculated height.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat heightForRow = 44.0;
if ([indexPath row] == kRowWithTextViewEmbedded)
{
CGFloat tableViewHeight = [tableView bounds].size.height;
heightForRow = tableViewHeight - ((kYourTableViewsNumberOfRows - 1) * heightForRow);
if (heightForRow < textHeight)
{
heightForRow = textHeight;
}
}
return heightForRow;
}
For a better user experience set the table view's content insets for bottom to e.g. 50.0.
I've tested it on the iPad with iOS 4.2.1 and works as expected.
Florian
I'm try to emulated something just like the "new message" page in Apple's mail app on the iphone. I've implemented it with a tableview and I've successfully gotten the "To", "CC", and "Subject" rows to behave correctly, but I'm not sure how to implement the actual message portion of the page.
There are several issues that I'm having. I'm currently trying to implement it by placing a UITextView in the cell (I turn off the scroll bars on the text view). I have the text view resize itself when it is changed, by modifying its frame to the new height of the content. The first problem is that I also need to do this for the cell height itself. Since heightForRowAtIndexPath seems to only get called when the row is first loaded, I can't modify the height there. I suppose I could call reload data on the table but this seems like it would be really inefficient to do on the whole table every time text is entered. What is the best way to get the table cell to autoresize as the user types? I've found lots of examples on how to do it on lone table views and how to resize table cells at initialization but I can't find any that let you do both at the same time.
Finally, I would like the bottom border of the table cell to be invisible. If you look at the mail app, you'll notice there is no line at the bottom of the message space, implying that you can just keep typing. I always have one in my table view (even when I add a footer) and I can't figure out how to get rid of it. (Perhaps should I make my message body be the footer itself?)
I would recommend using a UIScrollView yourself instead of a UITableView. UITableView isn't really built to support such a thing.
Mail.app doesn't seem to use UITableView.
It looks like there custom items (labels and text fields) with UITextView on bottom.
You could try my answer to a question similar to this...the key is to use
[self.tableView beginUpdates];
[self.tableView endUpdates];
To do this without reloading the data.
First off, of course, you're going to want to create your UITextView and add it to your cell's contentView. I created an instance variable of UITextView called "cellTextView" Here is the code that I used:
- (UITableViewCell *)tableView:(UITableView *)tableView fileNameCellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
if (!cellTextView) {
cellTextView = [[UITextView alloc] initWithFrame:CGRectMake(5.0, 5.0, cell.bounds.size.width - 30.0, cell.bounds.size.height - 10.0)]; // I use these x and y values plus the height value for padding purposes.
}
[cellTextView setBackgroundColor:[UIColor clearColor]];
[cellTextView setScrollEnabled:FALSE];
[cellTextView setFont:[UIFont boldSystemFontOfSize:13.0]];
[cellTextView setDelegate:self];
[cellTextView setTextColor:[UIColor blackColor]];
[cellTextView setContentInset:UIEdgeInsetsZero];
[cell.contentView addSubview:cellTextView];
return cell;
}
Then, create an int variable called numberOfLines and set the variable to 1 in your init method. Afterwards, in your textViewDelegate's textViewDidChange method, use this code:
- (void)textViewDidChange:(UITextView *)textView
{
numberOfLines = (textView.contentSize.height / textView.font.lineHeight) - 1;
float height = 44.0;
height += (textView.font.lineHeight * (numberOfLines - 1));
CGRect textViewFrame = [textView frame];
textViewFrame.size.height = height - 10.0; //The 10 value is to retrieve the same height padding I inputed earlier when I initialized the UITextView
[textView setFrame:textViewFrame];
[self.tableView beginUpdates];
[self.tableView endUpdates];
[cellTextView setContentInset:UIEdgeInsetsZero];
}
Finally, paste this code into your heightForRowAtIndexPath method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
float height = 44.0;
if (cellTextView) {
height += (cellTextView.font.lineHeight * (numberOfLines - 1));
}
return height;
}