webView loadHTMLString completion? - ios5

There are a few question about this, which has a solution regarding the script embedded in html string. Not in my case.
I have a ios5/6 app.
I have tryed to put the loading code into initWithNibName bad idea, viewDidLoad is deprecated , so viewWillAppear left.
- (void) viewWillAppear:(BOOL)animated
{
NSString* detailHtml = [Storage getDetailHtml: -1];// get the last one
//NSLog(#"detailHtml: %#", detailHtml);
if(detailHtml == nil){
NSLog(#"Can't load the detail");
detailHtml = #"<html><body>Some text here</body></html>";
}
[webView loadHTMLString:detailHtml baseURL:nil];
[super viewWillAppear:animated];
}
The problem with this code is flashing. It appears the UIViewContoller, with a white webView than after 1-2 seconds it is displayed the html. The HTML has some texts, but has base64 encoded images too, sometimes it is around 3Mb even 5MB content. No javascript is here, just inline CSS ( no external file ), text and base 64 encoded images.
I could pre-load into detailHtml the text, but I think the displaying takes more time.
Is that possible to do not display the ViewController, until the webView hasn't finished loading the html ? - some callback?
Any suggestions?

That would take little bit time to load the HTML string default.But if everything works fine the why don't you put UIActivityIndicator so that would keep user engaged with the app.Even much better than keeping it blank
Here is one the delegate method of UIWebView :
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//And stop your indicator over here
[indicator stopAnimating];
}

Related

UIWebView Still load the URL even though returning NO

I am handling a UIWebView so that i can control which URLs should be loaded within or not, but some how even though it is retuning the NO , it still load the page. Although documentation clearly says that if you return NO, the UIWebView wont load the page.
When i debug,i can see it is returning NO but still UIWebView does load the URL.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(#"%#", [[request URL] absoluteString]);
NSString *fullURL = [[request URL] absoluteString];
NSRange range = [fullURL rangeOfString:#"#"];
if (range.length != 0) {
NSLog(#"We need to show the other view");
return NO;
}
return YES;
}
I solved the issue, documenting here so it may help someone else. Actually, the HTML, which we were loading using some javascript which was causing this issue. I found out by just using few plain html and testing with them. Once , we know the HTML is issue, we fixed the html and its working now.
Set a breakpoint or NSLog right before the return YES part. Maybe your method gets called twice for whatever reason, and it returns NO on one, and YES on the other.
First, make sure that you are setting the delegate in viewDidLoad with
webView.delegate = self;
(take care of not setting it 2 times, in a xib file and in viewDidLoad, it has caused me problems before)
Make sure you implement the UIWebViewDelegate in your class, something like this:
#interface RootViewController : UIViewController<UIwebViewDelegate>
Assuming you have taken care of all this and still you face problems. Also since you debugged and are SURE that the delegate method is returning NO. One reason I can think of as to why this is happening is that you are not loading a new page but using something like AJAX.
I tested the following code on 2 kinds of pages:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"THE loadCount is %d", self.loadCount);
if (self.loadCount > 1){
return NO;
}
self.loadCount++;
return YES;
}
Case 1: A webpage where every URL loads a new page. The above code works in this case, and I cannot load any pages after the first load, as required.
Case 2: A webpage in which the first load is a complete new web page. But everything else is loaded with AJAX, in that case, my loadCount does not increase and the pages load fine.
That's all I can think of with the data provided. :)
Same issue here, just to extend the answer above be careful when using rails 4 as backend because turbolinks add javascript to every link and then you can get wrong behavior on your delegate, happened to me returning NO on shouldStartLoadWithRequest and still see the request on my server.

Reused UIWebView showing previous loaded content for a brief second on iPhone

In one of my apps I reuse a webview. Each time the user enters a certain view on reload cached data to the webview using the method :-
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName baseURL:(NSURL *)baseURL
and I wait for the callback call
- (void) webViewDidFinishLoad:(UIWebView *)webView.
In the mean time I hide the webview and show a 'loading' label.
Only when I receive webViewDidFinishLoad do I show the webview.
Many times what happens I see the previous data that was loaded to the webview for a brief second before the new data I loaded kicks in.
I already added a delay of 0.2 seconds before showing the webview but it didn't help.
Instead of solving this by adding more time to the delay does anyone know how to solve this issue or maybe clear old data from a webview without release and allocating it every time?
Thanks malaki1974, in my case I wasn't using a modal view.
When I sat with an Apple engineer on WWDC 2010 and asked him this question his answer was simply: "Don't reuse UIWebViews, that's not how they were ment to be used."
Since then I make sure to calls this set of lines before allocating a new UIWebView
[self.myWebView removeFromSuperview];
self.myWebView.delegate = nil;
[self.myWebView stopLoading];
[self.myWebView release];
That solved the issue.
Clear the contents of the webview before you try to load new content
[self loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
First, the UIWebView renders it contents in a background thread. Even when you receive webViewDidFinishLoad: it might not be completely done. Specially if it is an ajax-intense page that comes from the network.
You say you are hiding the view. I wonder if that means that the webview delays its drawing completely. What you could try is to move the UIWebView offscreen or obscure it with another view. Maybe that will change it's drawing behaviour.
If you do not need an interactive UIWebView then you can also consider to do it completely offscreen in a separate UIWindow and then create an image from that UIWebView's layer.
That's what I do, and it works:
[_webView stringByEvaluatingJavaScriptFromString:#"document.open();document.close();"];
Try loading a local file that is blank or has a loading graphic when you hide it, rather than just loading new content when you show it. Since the file is local it will be quick and even if the new page takes a while to load it will have either blank or loading expected behavior.
If you got controll over the html. You can communicate back to objective-c when the document is ready. Like so in jQuery:
function messageNative (name, string) {
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "appscheme://" + name + "/" + string);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
$(function() {
messageNative('webview', 'ready');
});
And then in UIWebView's delegate method webView:shouldStartLoadWithRequest:navigationType: wait for the request with url equal to "appscheme://webview/ready". Then you should know: the document is loaded and ready for display. Then all that is missing is a simple fade-in or something like that :)

UIWebView didFinishLoading fires multiple times

I have some code that needs to run after the a UIWebView finishes loading a document. For that I've set the UIWebView's delegate to my controller, and implemented the webViewDidFinishLoading method.
This gets called multiple times, depending on the type of page to load. I'm not sure if it's because of ajax requests, requests for images, or maybe even iframes.
Is there a way to tell that the main request has finished, meaning the HTML is completely loaded?
Or perhaps delay my code from firing until all of those events are done firing?
You can do something like this to check when loading is finished. Because you can have a lot of content on the same page you need it.
- (void)webViewDidFinishLoad:(UIWebView *)webview {
if (webview.isLoading)
return;
// do some work
}
It could be enlightening (if you haven't gone this far yet) to NSLog a trace of load starts and finishes.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(#"Loading: %#", [request URL]);
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"didFinish: %#; stillLoading: %#", [[webView request]URL],
(webView.loading?#"YES":#"NO"));
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSLog(#"didFail: %#; stillLoading: %#", [[webView request]URL],
(webView.loading?#"YES":#"NO"));
}
I just watched the calls to all three in one of my projects which loads a help page from my bundle and contains embedded resources (external css, YUI!, images). The only request that comes through is the initial page load, shouldStartLoadWithRequest isn't called for any of the dependencies. So it is curious why your didFinishLoad is called multiple times.
Perhaps what you're seeing is due to redirects, or as mentioned, ajax calls within a loaded page. But you at least should be able balance calls to shouldStartLoad and either of the other two delegate functions and be able to determine when the loading is finished.
Check this one it so simply and easy way to achieve no need to write too much code:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if ([[webView stringByEvaluatingJavaScriptFromString:#"document.readyState"] isEqualToString:#"complete"]) {
// UIWebView object has fully loaded.
}
}
This question is already solved, but I see it lacks an answer that actually explains why multiple calls to webViewDidFinishLoad are actually expected behavior
The aforementioned method is called every time the webview finishes loading a frame. From the UIWebViewDelegate protocol documentation:
webViewDidFinishLoad:
Sent after a web view finishes loading a frame.
In fact, this is also true for all the other methods that comprise the UIWebViewDelegate protocol.
Try this it will work fine
-(void)webViewDidFinishLoad:(UIWebView *)webview
{
if (webview.isLoading)
return;
else
{
// Use the code here which ever you need to run after webview loaded
}
}
This happens because the callback method is called every time a frame is done loading. In order to prevent this set the "suppressesIncrementalRendering" property of the webview to true. this will prevent the webview from rendering until the entire data is loaded into the memory. This did the trick for me
I have notice something similar and it was a confusion: I have a UITabBarController, it seems to preload all ViewControllers linked to its tabs on launching the App (in spite of showing just first_Tab_ViewController), so when several tabs have ViewController with WebView their respective webViewDidFinishLoad are called and if I have copied pasted:
NSLog(#"size width %0.0f height %0.0f", fitingSize.width, fittingSize.height);
in several, I get several output in console that appears to be a double calling when they really are single calling in two different UIWebViews.
You could check the loading and request properties in the webViewDidFinishLoad method
Possibly related to this issue is a property on UIWebView introduced in iOS6: suppressesIncrementalRendering.

webViewDidFinishLoad: Firing too soon?

I'm trying to transition between loading of different web pages by hiding the webView while it is loading a page. However, I'm seeing that some image intensive websites are causing webViewDidFinishLoading to fire too soon and when I show the webView at that point then for a split second you get a view of the previous page. Any ideas on how to resolve this?
If there's Javascript on the page, you may need to wait for it to finish. The easiest way seems to be to send some javascript to the page to be executed:
-(void) webViewDidFinishLoad:(UIWebView *)webView
{
NSString *javaScript = #"<script type=\"text/javascript\">function myFunction(){return 1+1;}</script>";
[webView stringByEvaluatingJavaScriptFromString:javaScript];
// done here
}
Having said that, I seem to still see cases where the webview isn't quite updated within webViewDidFinishLoad.
I've encountered this problem as well. Although I haven't found a solution, I've worked around the problem by introducing a 0.5 second delay before showing the UIWebView once the webViewDidFinishLoading delegate method is called.
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[self performSelector:#selector(displayWebView) withObject:nil afterDelay:0.5];
}

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.