UIWebView content doesn't load until after view is shown - iphone

I have a UIView which, on viewDidLoad creates and shows a UIWebView. The web view doesn't actually show its contents until the view has actually appeared though. The navigation bar appears with the view during its animation process but the web view is populated a second after the animation has finished. How can I make the web view load the contents BEFORE animation begins. Here is my code, thanks:
-(void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Help";
NSString *html = #"my html contents goes here, all local and all within a string - does contain one BG image though";
UIWebView *webView = [[UIWebView alloc] initWithFrame:_webFrame];
NSString *path = [[NSBundle mainBundle] bundlePath];
NSURL *baseURL = [NSURL fileURLWithpath:path];
[webView loadHTMLString:html baseURL:baseURL];
[self.view addSubView:webView];
[webView release];
}

I think a UIWebView needs some time to initialize, since it uses the WebKit engine.
You could try adding a simple timeout before actually pushing the new viewcontroller or show a placeholder during the 'push' animation.

As far as I know, a UIWebView has to be on screen in order to load and render.
i'd try the following:
create and add web view on the calling view controller.
set frame of the web view in a way only one pixel is shown on screen.
load html content.
push to new ViewController, remove Web View from old parent and add to new one.
might work, but it's really not worth the hassle, why not try to "solve" the issue by adding a load indicator or a placeholder text?

Related

Preloading a UIWebView, avoiding white screen flash

I'm working on an app that has table navigation to eventually drill down to UIWebView that displays various information. However, the transition from the slickness of UITableView to the slow wonkiness of UIWebView is jarring for the user so I want to improve that experience however I can.
Specifically, the background of both the tableViews and UIWebView pages have black backgrounds, but when I open the UIWebView it flashes empty white for about a second (this is the case for both local and remote HTML files.) How can I (ideally) preload this process, or (at least) make the "flash" be all black rather than all white? I tried making the view / webView's background black but that didn't seem to help.
In my app right now, when a user selects a cell, the app just loads up the UIWebView subclass and pushes it on the navigation stack. The UIWebView subclass has an activity indicator that starts & stops animating on WebViewDidStartLoad and WebViewDidFinishLoad, which works fine, but it doesn't do anything to help the "white flash."
I have tested it... I'm sending the two method that I have used...
- (void)viewDidLoad {
[super viewDidLoad]; //objWebView is the outlet of UIWebView
[objWebView loadHTMLString:#"<html><body style=\"background-color:black;\"></body></html>" baseURL:nil];
//the more the delay the errors will be less so within 0.1-0.3 would be fine
[self performSelector:#selector(loadURL:) withObject:nil afterDelay:0.1];
}
-(void)loadURL:(id)sender{
[objWebView stopLoading]; //added this line to stop the previous request
NSURL *url = [NSURL URLWithString:#"http://www.google.com"];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
[objWebView loadRequest:req];
}
here I'm performing the request after 0.1 sec, other wise it will look white as in your case. Or you can give your delay time depending upon the time.. Cheers :)
Try in your
-(void)viewDidLoad{
myWebView.hidden = YES;
Then in
-(void)loadURL:(id)sender{
myWebView.hidden = NO;
I used:
[webView setHidden:YES];
[webView setDelegate:self];
when creating my webView and making the request and then added this delegate method to handle completed requests:
- (void) webViewDidFinishLoad:(UIWebView*)webView{
[webView setHidden:NO];
}
Successfully done it.
You have to create a view in Interface Builder first.
Then load the html to the webview using a initWithFrame in the init of your ViewController that contains the webview(this is where the magic happens):
CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
webView = [[UIWebView alloc] initWithFrame:webFrame];
Then simply load the webView into the view in viewWillAppear:
[viewWeb addSubview:webView];
This is really a question of interface designing, which is faster paint directly on the view or paint in a subview and then paint that subview in the view?
I had solved this problem years ago, using the common method of hiding the UIWebView behind a UIImageView, then removing the UIImageView after a delay.
But it suddenly came back, I think on iOS 7.0.4. It was occurring on a brand new iPad Air, as well as an older iPad mini non-retina. After two days of hair-pulling, I finally found a work-around.
Let's say you have webview which is restricted to landscape orientation, initialized like this:
WebView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 1024.0f, 768.0f)];
And then you make it visible after pre-loading, with eg bringSubviewToFront or setHidden:NO on the webview (or alternatively with setHidden:YES or removeFromSuperview on the UIImageView). But instead of seamlessly switching the views, there's a flash and the background color blinks for about half a second.
The fix is to change the size of your webview, ever so slightly:
WebView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 1024.01f, 768.0f)];
The problem and fix is highly reproducible. It works with a change as slight as the fourth decimal place (1024.0001f). At the fifth decimal place (1024.00001f), the flash returns. The value of the height (768.0f) didn't seem to matter.
Or, instead of working around the problem, you could just set the background color of your webview to whatever background color you're using. Unless you're using an image of course.

How can I prevent delayed display of my UIWebView

I must include some 'rich text' instructions* preceding a form; and thought adding a UIWebView to the header of the tableview would work. The HTML is a small file in my resources folder, the CSS is in a style tag in the file; so it's all nice and self contained.
The problem is, the view transitions in; then after a small delay the contents of the webview appear. The effect is jarring, and I don't think hiding the view and fading it in when it's ready would be any more desirable.
I'm creating the view with the code below, in my viewDidLoad method.
UIWebView * wv = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, HEADER_HEIGHT)];
[wv setBackgroundColor:[UIColor clearColor]];
[wv setOpaque:NO];
NSString *path = [[NSBundle mainBundle] pathForResource:#"HeaderMsg" ofType:#"html"];
assert(path != nil);
NSData *htmlData = [NSData dataWithContentsOfFile:path];
[wv loadData:htmlData MIMEType:#"text/html" textEncodingName:#"utf-8" baseURL:nil];
[self.tableView setTableHeaderView:wv];
[wv release];
I found 2 ugly workarounds, but I'm hoping someone has a better solution; since my workarounds make a real mess out of the code:
On previous screen when you make the
UIMYSCREENViewController, call a
[vc
preloadWebViewWithDelegate:self];
method which will make the webview
using the caller as the delegate.
The caller then retains the vc and
waits for the webview to sent it a
webviewDidFinishLoad method, at
which time it can present the view
and release the vc.
The calling view can make the webview,
wait for it to finish, then create the
new view and pass the webview into it.
At any rate, both of those "solutions" make me gag a little, so I'm hoping others have found a better way.
(*The instructions are mostly simple, styled text with some bullet points (no images or overly aggressive styling); but it takes about 14 carefully aligned UILabel views to simulate this without a webview - and is subject to the whims of the customer wanting to change the message.)
I would create another independent model or controller object to create and retain the webview, hopefully at a higher level and before (maybe during app init) displaying the view with the UI that could bring up the webview.
Consider this the same as pre-staging resources for an action game so that they don't have to be loaded during the game loop, which is a common design pattern.
Consider using this
https://github.com/Cocoanetics/DTCoreText/

Save history of UIWebView and reload it

I have a view with a subview of UIWebView and the view is obviously managed by a view controller. Say, I browse with this UIWebView for sometime and then navigate to a different view managed by a different view controller and then return to the UIWebView view. It will load the parent URL and not the one where I had left it. How can I load the URL where I had browsed till along with the history i.e., ability to go back to the parent URL from the URL where I had switched views. The code might help.
NSString *string = [[NSString alloc] initWithString:#"http://www.google.com"];
NSURL *url =[[NSURL alloc] initWithString:string];
NSURLRequest *aRequest = [[NSURLRequest alloc] initWithURL:url];
[webView loadRequest:aRequest];
When we come back to this web view, it will load 'url' again. But, I want it to load the url where the UIWebView was last at.
Thanks.
It is really simple. On viewWillDisappear method, save the current URL of the webView.Request, on next launch, try to see if it has value and load the one existing, otherwise, load the default "google.com". Hope this helps.

UIWebView delay in loading local content

I'm displayed a small amount of local content in a UIWebView, roughly 3 paragraphs of text and one 100x100px image. When the UIWebView is pushed onto to the nav controller, it takes about half a second before the content is displayed. This occurs on the device, not in the simulator. Obviously the UIWebView needs to do some work before displaying the content, but is there some way I can do this before the pushed view appears? I have not had any luck doing this myself.
Here is where I'm creating and pushing the view controller which contains the UIWebView:
ArticleViewController* viewController = [[ArticleViewController alloc] init];
viewController.article = article;
[viewController view]; //touch the view so it gets loaded
[viewController loadWebView]; //load the web view content
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
And here is the loadWebView method:
-(void)loadWebView {
NSString *path = [[NSBundle mainBundle] bundlePath];
NSURL *baseURL = [NSURL fileURLWithPath:path];
NSString* content = article.content;
NSString *html = [NSString stringWithFormat:#"\
<html><body style='background-color: transparent'><style type=""text/css"">body {font-family: helvetica; color: black; font-size: 10pt; margin: 10px 10px 10px 10px}</style>\
%#<div style='text-align:center'><a href='13443574_9709e4cf37.jpg?photographer=test' style='-webkit-tap-highlight-color:rgba(0,0,0,0);'><img src='13443574_9709e4cf37.jpg' height='160' width='230'></a></div></body></html>", content];
[self.webView loadHTMLString:html baseURL:baseURL];
}
Previously I had [viewController loadWebView] in the viewDidLoad method, but the result seems to be the same. A blank screen when the viewController is pushed, followed by the content loading half a second later.
Any ideas?
Problem Confirmed
I too see a half-second or more delay with a white screen before my UIWebView content appears. This happens only on the first use of a UIWebView during that run of the app. Successive appearances of UIWebView are nearly instantaneous. So it seems to me and other folk that the delay must be due to WebKit libraries needing to load and initialize.
Warm-up WebKit
You cannot eliminate the delay. But you can move that delay to the start of your app, to lessen the annoying effect of "blank screen" to your users. The trick is to load UIWebView with a bogus page off-screen, during your app's launch. Build a minimal HTML5 web page in a hard-coded string. I use a correct and valid HTML5 content to minimize the time taken by UIWebView/WebKit to analyze.
This technique works noticeably well on real hardware (iPhone 3GS), not just the iOS Simulator.
In my app delegates didFinishLaunchingWithOptions method, the bottom of the ARC-compliant code looks like this:
…
[self.window makeKeyAndVisible];
// Performance optimization: Warm up the UIWebView widget and its related WebKit libraries.
// We are choosing the trade-off between (A) a slight delay here during app launch to create and abandon a bogus UIWebView instance, and
// (B) a flash of white and noticeable delay as the UINavigationController slides in from the right the first UIWebView we display during the app run.
UIWebView* bogusWebView = [[UIWebView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
NSString* html = #"<!doctype html><html lang=en><head><meta charset=utf-8><title>blah</title></head><body><p>bogus content</p></body></html>";
[bogusWebView loadHTMLString:html baseURL:nil]; // Load the page.
bogusWebView = nil; // Not really needed, but self-documents that we intend to discard this object.
return YES;
This technique seems to reduce most but not quite all of the approximate half-second delay during the user's first appearance of UIWebView on screen. I conclude that most of the delay is due to WebKit warming up, but there must be some overhead related to graphically presenting a UIWebView on-screen. If so, we cannot eliminate that remaining overhead with off-screen work. But nevertheless, this technique eliminates most of the initial delay. So the user's first impression of my app won't be "slow".
You're going to make the user wait, the only question is: is it before or after the webview appears? If you're set on "before", then you should create the controller, load the web view, but wait to push it until the -webViewDidFinishLoad: delegate method fires. Once you receive that, you can push the view.

How do I use a UIWebView in a Table Cell?

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];
}
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.
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\">\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];
}
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.
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?
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.
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.
My understanding is that a WebView won't render unless you initiate the loading of it AFTER viewDidLoad() happens.