views:

4632

answers:

8

I'm building a table with some text that is HTML, so I am using a UIWebView as a subview of my custom table cells. I already ran into one problem - as I scrolled down in the table, it would take the UIWebViews a second to update. For example, I'd be viewing Cells at rows numbered 1, 2, and 3. I'd scroll down to say 8, 9, and 10. For a moment, the content of the UIWebView that was visible in cell #8 was the content from cell #1, the content in cell #9 was that from cell #2, and so on.

I learned that the problem was that UIWebViews simply render their text slowly. It was suggested to instead preload the content into the UIWebView as soon as I could instead of waiting until the table receives the cellForRowAtIndexPath. So now, I have a Domain Object which before just had the text content of the WebView - but now it actually has a reference to the UIWebView itself.

But now some of the content in the UIWebView renders, and when I scroll through the table the UIWebView shows only as a grey box. If I touch the grey box, it will actually receive the touch and update the WebView - for example if I touch a link (which I may or may not do, since the box is gray and it would be by a stroke of luck), the page that was linked to will be requested and displayed properly.

- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) {
            // I suppose this isn't necessary since I am just getting it from the
            // Domain Object anyway
 content = [[UIWebView alloc] initWithFrame:CGRectZero];
 content.backgroundColor = [UIColor blackColor];
 [self addSubview:content];
 [content release];
}
return self;
}

// called by cellForRowAtIndexPath 
- (void)setMyDomainObject:(MyDomainObject*)anObject {
UIWebView *contentWebView = anObject.contentWebView;
int contentIndex = [self.subviews indexOfObject:content];
[self insertSubview:contentWebView atIndex:contentIndex];
}
A: 

Have you looked into overriding the -prepareForReuse method on your table cell subclass? If the cells don't seem to update when you scroll, it's possible that the content of reusable cells isn't being cleared and reset.

Jasarien
+1  A: 

You said 'called by setRowAtIndexPath', you might mean 'cellForRowAtIndexPath' which is a UITableView method called when a row becomes visible and needs to create a cell. Make sure that in this method you are properly initializing and updating the cell contents.

Akusete
yeah, I was doing this from memory which isn't the best way to write questions.
bpapa
Can you clarify 'when I start scrolling the web views don't update'. Are you scrolling within the webview or scrolling down the table view? What content is in the webview, and what should it be changing to?
Akusete
Updated third paragraph of question better describe what I'm seeing.
bpapa
+2  A: 

This isn't answering the original question asked, but taking one step back and looking at the bigger picture, if you're trying to display a hyperlink in a table cell, does that mean when you click on it it opens a web browser? Would it be the same if you showed styled text in the table cell that looks like or hints at a link, but open a separate screen with a full-screen web view that lets you tap on the link?

+3  A: 

If the content in the web view is restricted to styled text or hyperlinks you might want to take a look at the Three20 project: http://github.com/joehewitt/three20/tree/master

Its TTStyledText class has support for <b>, <i>, <img>, and <a> tags in the content. Probably more lightweight than webviews.

Ramin
+3  A: 

One way to deal with this would be to use several UILabels, each with different fonts. Then strategically place them within the cell as to make them seem like contiguous text. I've seen this done in a UITableView with very good results.

Another performance problem may be that you are overiding UItableViewCell and it's init method. This almost always leads to poor performance in a UITableView. Instead just use the regular UITableViewCell instances and add subviews to it's contentView.

This has been covered extensively by Matt Gallagher's Easy Custom UITableView Drawing.

Another method described was to use Three20, which can give you styled text as well.

What you are attempting currently can't be done with UIWebview.

Preloading won't help: this would slow down the UI when UIWebviews are being rendered offscreen; you should always practice lazy loading on the iPhone.

Putting UIWebviews in a UITableView will come with a potentially unacceptable performance hit. You will need to use other means to accomplish your goal.

Unfortunately, NSAttributedString is unavailable in UIKit, which could easily solve your problem.

Corey Floyd
I wound up accepting this one b/c of the reality that this was not something that can be done with a UIWebView. Personally I wound up abandoning the feature but I will revisit soon - and what I'm going to do instead of using a TableView is just simulate it using HTML and CSS, and putting it into a single WebView. Then I'll use a WebViewDelegate to sort of "intercept" clicks on links and determine whether to show other web content or do some other action in my application.
bpapa
A: 

How did you put a UIWebView In anyway?

Will Evans
ps this should have been a new question (or comment), not a answer. I actually wound up abandoning that feature. however i'm going to revisit it eventually, and I'm considering not using a TableView at all and just putting all of the HTML into one WebView.
bpapa
A: 

My understanding is that a WebView won't render unless you initiate the loading of it AFTER viewDidLoad() happens.

juggleware
UIWebView seems to do its rendering after control returns back to the main RunLoop, so if you try to execute JavaScript, you can't do it in -[viewDidLoad] or -[viewWillAppear]. You might be able to do it in the web view delegate's method for when the web view is finished loading.
lucius
+2  A: 

So here's my updated answer: I've implemented a table using one big UIWebView to put styled text inside table cells in my own Twitter client app, HelTweetica (with source available). It works pretty well as long as you understand how to do layout in HTML and CSS.

You just create a NSMutableString and add the lines of HTML that make up your document. You can references external files such as CSS files that are in your app's sandbox. You can set custom URL actions such as "youraction://file.txt" that your web view delegate can intercept and process as a replacement IBActions:

- (void) refreshWebView {
    TwitterAccount *account = [twitter currentAccount];
    NSMutableString *html = [[NSMutableString alloc] init];
    // Open html and head tags
    [html appendString:@"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"&gt;\n"];
    [html appendString:@"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"];
    [html appendString:@"<head>\n"];
    [html appendString:@"<meta name='viewport' content='width=device-width' />"];
    [html appendString:@"<link href='style-ipad.css' rel='styleSheet' type='text/css' />"];
    [html appendString:@"<script language='JavaScript' src='functions.js'></script>"];

    // Body
    [html appendString:@"</head><body><div class='artboard'>"];

    // [...]

    // Close artboard div, body. and html tags
    [html appendString:@"</div></body></html>\n"];

    NSURL *baseURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] bundlePath]];
    [self.webView loadHTMLString:html baseURL:baseURL];
    [html release];
}

And you can reload parts of the page by using javascript:

- (void) refreshTabArea {
    NSString *html = [self stringByEscapingQuotes: [self tabAreaHTML]];
    NSString *js = [NSString stringWithFormat: @"document.getElementById(\"tab_area\").innerHTML = \"%@\";", html];
    [self.webView stringByEvaluatingJavaScriptFromString:js];
}
lucius