UA-17470720-3

Jump to content


Photo
- - - - -

UIWebView Dynamic / Resize Height in Custom Cell to Fit Content

cells

  • Please log in to reply
12 replies to this topic

#1 wizgod

wizgod

    I'm what you guys call a User

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 575 posts
  • LocationThe Grid
Reputation: 149
Popular

Posted 15 April 2013 - 08:43 AM

Greetings Programs!

I love the cell auto-resize feature with the label but I need to display html in a webview within a cell.

Now there are a ton of solutions around (and I haven't found one that will work for me), many using the webViewDidFinishLoad, but with STV automatically resizing the cell with a label, I am hoping that it can be done with a webview.

What I am doing is:
1. Grabbing posts from a webservice.
2. Converting the message from BBCode to HTML.
3. Setting the loadHTMLString of the UIWebView in the custom cell with the converted HTML.

Then I would like:
4. Set the height of the UIWebView to fit the content.
5. Let STV re-size the cell to fit.

Thanks!

Wg

P.S. I love Swift... talk Swift.. Never too old school to learn yet another programming language. LOL! ;-)


#2 Dave Guerin

Dave Guerin

    Forum Master

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 541 posts
  • LocationIreland
Reputation: 134
Popular

Posted 15 April 2013 - 08:50 AM

Hi wizgod,

If you already know the height of your content in 4 why not just let STV know what it is?

cellActions.willConfigure = ^(SCTableViewCell *cell, NSIndexPath *indexPath)
{
    cell.height = 60; // height of your UIWebView
};

Cheers,

Dave

www.dgapps.ie

#3 wizgod

wizgod

    I'm what you guys call a User

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 575 posts
  • LocationThe Grid
Reputation: 149
Popular

Posted 15 April 2013 - 09:44 AM

Hi wizgod,

If you already know the height of your content in 4 why not just let STV know what it is?


Hi Dave!

That wouldn't work because the webview hasn't loaded yet but I was able to resize the UIWebView in the webViewDidFinishLoad and cheat a little to resize the cell (temporarly).

So far I have this in viewDidLoad:

self.tableViewModel.cellActions.willDisplay = ^(SCTableViewCell *cell, NSIndexPath *indexPath)
{
    if([cell isKindOfClass:[SCCustomCell class]])
    {
         NSString *messageText = [cell.boundObject valueForKey:@"messageText"];

        // ******************************************************************************************************
        // Cheat for now; set the UILabel underneath the UIWebView so the cell resizes that much at least :-).
        // ******************************************************************************************************
         UILabel *message = (UILabel *)[cell viewWithTag:3];
        message.text = messageText;
        // ******************************************************************************************************

        if (messageText != nil)
        {
             // Allocate a coder.
            BBCoder *coder = [[BBCoder alloc] init];

            // Convert a BBCode string to HTML.
            NSString *htmlMessageText = [coder bbCodeToHTML:messageText withUrl:siteUrl withUserId:[cell.boundObject valueForKey:@"userId"]];

            NSLog(@"HTML-ized message: '%@'\n", htmlMessageText);

            html = [html stringByReplacingOccurrencesOfString:@"{BODY}" withString:htmlMessageText];

            NSLog(@"HTML body: '%@'\n", html);

            UIWebView *webView = (UIWebView *)[cell viewWithTag:6];
            [webView loadHTMLString:html baseURL:nil];
            [webView setDelegate:self];
        }
	 }
};
[/code]

and then I have:

[code]
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    CGRect mWebViewFrame = webView.frame;
    mWebViewFrame.size.height = webView.scrollView.contentSize.height;
    webView.frame = mWebViewFrame;

    //Disable bouncing in webview
    for (id subview in webView.subviews)
    {
        if ([[subview class] isSubclassOfClass: [UIScrollView class]])
         {
	        [subview setBounces:NO];
         }
    }
}

Now if I can find out what cell the webView is located in inside webViewDidFinishLoad, I'm thinking I might be able to set the cell height in there.

Thanks!

Wg

* I don't know, I've tried to edit the post a couple of times to fix it but the CODE blocks are still not converting correctly... Warning the MCP may be on the loose ;-)

Edited by wizgod, 15 April 2013 - 09:57 AM.

P.S. I love Swift... talk Swift.. Never too old school to learn yet another programming language. LOL! ;-)


#4 wizgod

wizgod

    I'm what you guys call a User

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 575 posts
  • LocationThe Grid
Reputation: 149
Popular

Posted 15 April 2013 - 10:48 AM

From some playing around, I found that cell is infact resizing automatically to the text in the UIWebView but it's to the text and not content.

If there's three lines of images tags, the height of the cell fits the the three lines and not the content size/height with the images.

I should add that if there is only one line of text, the converted HTML (with head, body, formatting/other tags etc) has more characters and so the height of the cell becomes more than the original one line.

Almost there though :-)


Didn't realize I had my label still being bound by:

section.sectionActions.cellForRowAtIndexPath = ^SCCustomCell*(SCArrayOfItemsSection *itemsSection, NSIndexPath *indexPath)
{
SCCustomCell *customCell = [SCCustomCell cellWithText:nil objectBindingsString:@"1:subject;2:creatorName;3:messageText;5:online;6:replies" nibName:@"PostCell"];

return customCell;
};


So the label was populating and the cell was resizing and I thought it was resizing to the webView [slaps forehead] doh!

Edited by wizgod, 15 April 2013 - 08:59 PM.

P.S. I love Swift... talk Swift.. Never too old school to learn yet another programming language. LOL! ;-)


#5 wizgod

wizgod

    I'm what you guys call a User

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 575 posts
  • LocationThe Grid
Reputation: 149
Popular

Posted 15 April 2013 - 02:58 PM

Ok, so I'm able to get the SCTableViewCell that the UIWebView is in (in webViewDidFinishLoad) and I set the height of the cell but it doesn't want to take.

Thoughts, suggestions greatly appreciated :-)

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    SCTableViewCell *cell = (SCTableViewCell *)[[webView superview] superview];
    //NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    
    if([cell isKindOfClass:[SCCustomCell class]])
    {
        NSLog(@"\n-------------------\nResizing Cell\n-------------------\n");

        // 120 offset from top of cell to webview.
        cell.height = webView.scrollView.contentSize.height + 120;
    }

    NSLog(@"\n-------------------\nResizing WebView\n-------------------\n");

    CGRect mWebViewFrame = webView.frame;
    mWebViewFrame.size.height = webView.scrollView.contentSize.height;
    webView.frame = mWebViewFrame;
    
    //Disable bouncing in webview
    for (id subview in webView.subviews)
    {
	    if ([[subview class] isSubclassOfClass: [UIScrollView class]])
	    {
		    [subview setBounces:NO];
	    }
    }
}

Thanks!

Wg

P.S. I love Swift... talk Swift.. Never too old school to learn yet another programming language. LOL! ;-)


#6 Dave Guerin

Dave Guerin

    Forum Master

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 541 posts
  • LocationIreland
Reputation: 134
Popular

Posted 15 April 2013 - 03:16 PM

Hi wizgod,

I think something like this will work:

Set the tag of the UIWebVIew to the indexPath.row

Initially in cellActions.willConfigure give a minimum height as you don't at that point know the actual height.

When the UIWebView webViewDidFinishLoad and you've calculated the actual height then do a [cell reloadBoundValue] on the cell (which you can find from the UIWebView tag number).

Then do [tableview reloadRowsAtIndexPaths:theCellIndexPath withRowAnimation:YES]

cellActions.willConfigure will have been called again in there, and the second time you give the height it's your calculated height.

I haven't actually tried it, but I think the concept will work. I think setting the height in cellActions.willConfigure is the important bit, especially as you scroll the table.

HTH
Cheers,

Dave

www.dgapps.ie

#7 wizgod

wizgod

    I'm what you guys call a User

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 575 posts
  • LocationThe Grid
Reputation: 149
Popular

Posted 15 April 2013 - 08:21 PM

When the UIWebView webViewDidFinishLoad and you've calculated the actual height then do a [cell reloadBoundValue] on the cell (which you can find from the UIWebView tag number).

Then do [tableview reloadRowsAtIndexPaths:theCellIndexPath withRowAnimation:YES]

cellActions.willConfigure will have been called again in there, and the second time you give the height it's your calculated height.

I haven't actually tried it, but I think the concept will work. I think setting the height in cellActions.willConfigure is the important bit, especially as you scroll the table.


Hi Dave!

I tried variations on what you suggested but they didn't work. I also tried variations from this post: http://sensiblecocoa...cell/#entry7781 (but it wasn't a webview)

In the webViewDidFinishLoad, I set the cell tag to the height hoping it would grab it in customHeightForRowAtIndexPath.

How would I grab the cell in the webViewDidFinishLoad with the indexPath.Row? I had set the tag in the self.tableViewModel.cellActions.willDisplay but couldn't find a way to retrieve it. I was using SCTableViewCell *cell = (SCTableViewCell *)[[webView superview] superview]; to grab it.

Thanks!

wg

Edited by wizgod, 15 April 2013 - 08:23 PM.

P.S. I love Swift... talk Swift.. Never too old school to learn yet another programming language. LOL! ;-)


#8 Dave Guerin

Dave Guerin

    Forum Master

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 541 posts
  • LocationIreland
Reputation: 134
Popular

Posted 16 April 2013 - 02:05 PM

Hi wizgod,

SCTableViewCell *cell = [section cellAtIndex:webView.tag];
would be one way of getting the cell.

And you can get the section from:
SCTableViewSection *section = [self.tableViewModel sectionAtIndex:yourSectionIndex];

The tableViewModel you need might be self.tableViewModel or self.tableViewModel.activeDetailModel or even self.tableViewModel.activeDetailModel.activeDetailModel depending on how many levels of drill down you have.

HTH

Edited by Dave Guerin, 16 April 2013 - 02:07 PM.

  • Tarek likes this
Cheers,

Dave

www.dgapps.ie

#9 Tarek

Tarek

    Forum Admin

  • Administrators
  • 3670 posts
Reputation: 452
Popular

Posted 16 April 2013 - 05:51 PM

I like @Dave's solution. Also, a bit more robust solution might be to create your own SCTableViewCell subclass that automatically resizes according to the contents of its UIWebView. Here is some sample code to help you get started (not tested!):

@interface SCWebViewCell : SCControlCell <UIWebViewDelegate>
  @property (nonatomic, readonly) UIWebView *webView;
  @property (nonatomic, readwrite) CGFloat maximumHeight;
@end

@implementation SCWebViewCell
//overrides superclass
- (void)performInitialization
{
  [super performInitialization];

  UIWebView *webView = [[UIWebView alloc] init];
  webView.delegate = self;
  control = webView;
  [self.contentView addSubview:control];

  _maximumHeight = 300;
}
- (UIWebView *)webView
{
  return (UIWebView *)control;
}
// overrides superclass
- (CGFloat)height
{
  CGFloat webViewHeight = 0;
  // use Javascript to determine current document height
  if(!self.webView.loading && self.webView.frame.size.width)
       webViewHeight = [[self.webView stringByEvaluatingJavascriptFromString: @"document.documentElement.clientHeight"] integerValue];
  if(!webViewHeight)
       webViewHeight = [super height];
  if(webViewHeight > self.maximumHeight)
       webViewHeight = self.maximumHeight;

  return webViewHeight + 2*self.controlMargin;
}
// overrides superclass
- (void)layoutSubviews
{
  [super layoutSubviews];

  // Set the webView height
  CGRect frame = self.webView.frame;
  frame.size.height = self.contentView.frame.size.height - 2*self.controlMargin;
  self.webView.frame = frame;
}
// implements UIWebViewDelegate webViewDidFinishLoad
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
  // reload the cell to update height
  [self.ownerTableViewModel.tableView reloadData];
}
@end

And this is how to use the new cell in your main code:

- (void)viewDidLoad
{
  ...
  SCTableViewSection *section =[[SCTableViewSection alloc]init];

  SCWebViewCell *webViewCell = [SCWebViewCell cell];
  [webViewCell.webView loadHTMLString:@"<div style='background:yellow'>Testing how the new shiny <strong>SCWebViewCell</strong> automatically resizes to accomodate content!</div>" baseURL:nil];

  [section addCell:webViewCell];
  [self.tableViewModel addSection:section];
...
}

I am sure the code still needs some tweaks, so please kindly post your final version of the cell once everything is working. Also, if you're still having problems, I can do some testing myself after we're done with our upcoming STV 3.2.0 release. Hope this helps!

#10 wizgod

wizgod

    I'm what you guys call a User

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 575 posts
  • LocationThe Grid
Reputation: 149
Popular

Posted 16 April 2013 - 10:35 PM

I like @Dave's solution. Also, a bit more robust solution might be to create your own SCTableViewCell subclass that automatically resizes according to the contents of its UIWebView. Here is some sample code to help you get started (not tested!):

I am sure the code still needs some tweaks, so please kindly post your final version of the cell once everything is working. Also, if you're still having problems, I can do some testing myself after we're done with our upcoming STV 3.2.0 release. Hope this helps!


Thanks Tarek!

I've been trying to get it working all night (the way I'm trying to use it).

I should say that it does work right off the bat the way you coded it (after changing the case for stringByEvaluatingJavascriptFromString :)) but I'm using a nib with other controls. I got around that by changing the offset for the UIWebView to be lower than the other controls and I set the class to the SCWebViewCell in the nib.

If I set the html in the willDisplay or willConfigure, webview would show up completely black - if I set it like in your example, it worked.

Also, if I set it in these, the [self.ownerTableViewModel.tableView reloadData] in the SCWebViewCell, it would go into an infinite loop of redrawing :-)

Thanks!

wg

Edited by wizgod, 16 April 2013 - 10:35 PM.

P.S. I love Swift... talk Swift.. Never too old school to learn yet another programming language. LOL! ;-)


#11 wizgod

wizgod

    I'm what you guys call a User

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 575 posts
  • LocationThe Grid
Reputation: 149
Popular

Posted 16 April 2013 - 10:39 PM

Hi Dave!

Hi wizgod,

SCTableViewCell *cell = [section cellAtIndex:webView.tag];
would be one way of getting the cell.

And you can get the section from:
SCTableViewSection *section = [self.tableViewModel sectionAtIndex:yourSectionIndex];

The tableViewModel you need might be self.tableViewModel or self.tableViewModel.activeDetailModel or even self.tableViewModel.activeDetailModel.activeDetailModel depending on how many levels of drill down you have.

HTH


I used it but still ran into the infinite redraw when I went to reload in webViewDidFinishLoad.

I don't think I'll be able to do it; I might just end up using the label with stripped text from the html.

Thanks!

wg

P.S. I love Swift... talk Swift.. Never too old school to learn yet another programming language. LOL! ;-)


#12 wizgod

wizgod

    I'm what you guys call a User

  • STV 5.0 Pro
  • PipPipPipPipPipPipPip
  • 575 posts
  • LocationThe Grid
Reputation: 149
Popular

Posted 18 April 2013 - 08:24 PM

I got it working!

Basically
1. I saved the height on the first load of the webview into a dictionary in webViewDidFinishLoad.
2. Returned it from the dictionary on subsequent loads from customHeightForRowAtIndexPath (plus additional height for other controls in the custom cell).
3. Only ran reloadRowsAtIndexPaths on the first load of the webview in webViewDidFinishLoad after the height was stored in the dictionary (to resize the cell).

Here is how I integrated it:
* (I had the whole code here but it looked confusing, even for me, so I cut it down to the important bits).

// PostsViewController.h

#import <SensibleTableView/SensibleTableView.h>

@interface PostsViewController : SCTableViewController <UITableViewDelegate, UIWebViewDelegate>
{
	 // Dictionary to hold the heights of all UIWebViews.
	 NSMutableDictionary *messageHeightDictionary;
}

@property (nonatomic, retain) NSMutableDictionary *messageHeightDictionary;

@end

and

// PostsViewController.m

#import "PostsViewController.h"

@interface PostsViewController ()
@end

@implementation PostsViewController
@synthesize messageHeightDictionary;

- (void)viewDidLoad
{
	 [super viewDidLoad];

	 // Create the web service definition for retrieving posts.
	 SCWebServiceDefinition *topicDef = [SCWebServiceDefinition
				 definitionWithBaseURL:WEB_SERVICE_URL
				 fetchObjectsAPI:[NSString stringWithFormat:@"posts/%@",topicId] resultsKeyName:@"post"
				 resultsDictionaryKeyNamesString:@"topicId, categoryId, userId, subject, creatorName, dateCreated, messageText, avatar, posts, 	 	 karma, personalText, websiteName, websiteUrl, rank, online"];

	 // Create the the sites section.
	 SCArrayOfObjectsSection *section = [SCArrayOfObjectsSection sectionWithHeaderTitle:topicName webServiceDefinition:topicDef];

	 section.sectionActions.cellForRowAtIndexPath = ^SCCustomCell*(SCArrayOfItemsSection *itemsSection, NSIndexPath *indexPath)
	 {
	 	 SCCustomCell *customCell = [SCCustomCell cellWithText:nil
			 objectBindingsString:@"1:subject;2:creatorName;5:online;6:replies;7:topicId"
		 nibName:@"PostCell"];

	 	 return customCell;
	 };

	 section.sectionActions.customHeightForRowAtIndexPath = ^CGFloat(SCArrayOfItemsSection *itemsSection, NSIndexPath *indexPath)
	 {
	 	 // Get the cell object.
	 	 SCTableViewCell *cell = [itemsSection.ownerTableViewModel cellAtIndexPath:indexPath];

	 	 // Get the postId for the dictionary key containing our message height.
	 	 NSString *postId = [cell.boundObject valueForKey:@"topicId"];

	 	 // Get the height of the UIWebView containing the HTML message.
	 	 NSString *messageHeight = [messageHeightDictionary objectForKey:postId];

	 	 // We need to add 100 more to the size of the webview to account for the height
	 	 // taken up by the other controls above it in the custom cell.
	 	 return (messageHeight == nil ? 120 : [messageHeight floatValue] + 100);
	 };

	 self.tableViewModel.cellActions.willDisplay = ^(SCTableViewCell *cell, NSIndexPath *indexPath)
	 {
	 	 if([cell isKindOfClass:[SCCustomCell class]])
	 	 {
	 			 // The post text in BBCode (if used).
	 			 NSString *messageText = [cell.boundObject valueForKey:@"messageText"];
	 			 NSLog(@"\n-------------------\nMessage: %@\n-------------------\n", messageText);

	 			 // Convert the message from BBCode to HTML and load the web view with the converted HTML.
	 			 UIWebView *webView = (UIWebView *)[cell viewWithTag:6];
	 		 messageText = [self convertBBCodeToHTML :messageText :[cell.boundObject valueForKey:@"userId"]]
	 		 [webView loadHTMLString:messageText baseURL:nil];
	 		 [webView setDelegate:self];
	 	 }
	 };

	 [self.tableViewModel addSection:section];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
	 NSLog(@"\n-------------------\nResizing WebView\n-------------------\n");

	 // Get the cell object.
	 SCTableViewCell *cell = (SCTableViewCell *)[[webView superview] superview];

	 // Get the postId which we will use as the key for our messageHeightDictionary.
	 NSString *postId = [cell.boundObject valueForKey:@"topicId"];

	 // Get the indexPath for the cell so we can reload the row.
	 NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
	
	 // Create a new UIWebView frame.
	 CGRect mWebViewFrame = webView.frame;

	 //Set the height to the content height of the web view containing the HTML message.
	 mWebViewFrame.size.height = webView.scrollView.contentSize.height;

	 // Set the new height.
	 webView.frame = mWebViewFrame;

	 // Disable bouncing in webview
	 for (id subview in webView.subviews)
	 {
		  if ([[subview class] isSubclassOfClass: [UIScrollView class]])
		  {
		  	 [subview setBounces:NO];
		  }
	 }

	 // We should only go here if this is the very first time the webview is being displayed (objectForKey:postId == null).
	 // Do not go if it's not the right class, there is no indexPath or no postId for the dictionary key.
	 if([cell isKindOfClass:[SCCustomCell class]] && indexPath != nil && postId != nil && [messageHeightDictionary objectForKey:postId] == nil)
	 {
	 	 NSLog(@"\n-------------------\nSetting message height: %f\n-------------------\n", webView.frame.size.height);
	 	
	 	 // Create a new dictionary if none exists.
	 	 if (messageHeightDictionary == nil)
	 	 messageHeightDictionary = [NSMutableDictionary new];

	 	 // Store the height of the webview in the dictionary for this postId.
	 	 [messageHeightDictionary setObject:[NSString stringWithFormat:@"%f", webView.frame.size.height] forKey:postId];

	 	 // Reload the cell; do not animate the reload.
	 	 NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
	 	 [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:NO];
	 }
	 else
	 {
	 	 // By default, the webview is hidden (alpha == 0); so as not to make the population of the
	 	 // webview "jumpy" or "choppy" as the view populates for the first time and the cell is resized.
	 	 //
	 	 // By now, the webview height is stored in the dictionary and is returned in customHeightForRowAtIndexPath.
	 	 // So for subsequent loads, we can make the webview visible once it's loaded.
			 webView.alpha = 1;
	 }
}

@end

Edited by wizgod, 19 April 2013 - 07:32 AM.

  • Tarek likes this

P.S. I love Swift... talk Swift.. Never too old school to learn yet another programming language. LOL! ;-)


#13 Tarek

Tarek

    Forum Admin

  • Administrators
  • 3670 posts
Reputation: 452
Popular

Posted 24 April 2013 - 10:07 PM

This is awesome! :)





Also tagged with one or more of these keywords: cells

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users