I'm developing an app that requires caching web pages (completely) along with their CSS files and images after saving the entire HTML of the page (going through the links to store each file along with the HTML file).
While viewing the HTML file offline, UIWebView takes a long time to load the page, given that I'm already offline, and the file is on disk along with its CSS and images.
I'm using this code to load the file:
NSData *htmlData = [NSData dataWithContentsOfFile:htmlFilePath];
[wView loadData:htmlData MIMEType:#"text/html" textEncodingName:#"UTF-8" baseURL:[NSURL fileURLWithPath:self.htmlFolderPath isDirectory:YES]];
Is there any other means of loading the file to the UIWebView that can load it faster?
P.S: it loads very fast on simulator (offline) but on device it takes a long time (considering its an already offline cached file)
Thanks for help.
Is your solution actually cache things other than the html file, meaning pictures, css, etc, AND relinking them in the html page? I am guessing that you are downloading and caching the html page and external resources then the UIWebView is loading that html and still going out to load the external resources. That would explain the fast performance on the simulator and the slower performance on device.
Here is a library designed to do exactly what you are trying to do: ASIWebPageRequest.
It should also be noted that it could simply be a case of disk i/o bottlenecking. I know on my project, I was loading large images in a uitableview and even after they were cached I noticed quite a bit of lag when pulling them off the disk because they were so big.
I've found certain kinds of CSS can grind WebView rendering to a halt. For example:
body { -webkit-box-shadow:inset 0 0 100px #222; }
This works great in the simulator, looks nice too. But on the phone (even the iPhone 4, iOS 4.2), a simple page will take 10sec to render.
Since this probably just takes time because it needs to parse and render the page, you may consider firing up the UIWebView in the background; i.e. added as a subview, but not visible.
Maybe the UIWebView is smart enough to know it doesn't need to do anything, but I suspect that at least html and css parsing is done right away.
If it doesn't do anything without being visible, reduce size to 1x1 and set opacity=0, and put that pixel some place where it can't interfere with touch event handling.
Not perfect but it may work.
I'm almost sure you will never be able to do this. The UIWebView just needs some time to process your webpage even when it's a local page.
Keeping that in mind you can try to preload the page before it's being shown. For example if you show it after a user presses a button, preload the page when you show the button instead of when the user actually presses the button. The user doesn't notice the slow loading, because it's being handled in the background so when the user presses the button the page is already been loaded.
I can give you an idea about alternative ways of loading HTML from file into the UIWebView. A small project I've got uses Objective-C as a pure wrapper for UIWebView
The project is here, but the main code is below: http://github.com/robb1e/iWeb
- (void)viewDidLoad {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"test" ofType:#"html" inDirectory:#"."]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
[super viewDidLoad];
}
I'm also exploring ways of improving the perceived performance by showing an image while the DOM is getting ready. Some answers I've had are here:
Displaying a background image on UIWebView
Just if in case someone happens the same to me...
I had a UIWebView that was loading a string html and every resource (js and css) was stored locally.
I've faced that loading the content with internet connection it was some kind of slow (1 or 2 seconds to load and appear the webview in my controller) but when I tried to load the same page WITHOUT internet connection it was fast, really fast.
I've remembered that my HTML template had this in the start <base href="http://somesite.com" /> that I've used to load some images whit relative paths in some contents.
Removing that work as charm. So that can be making your web view load slower even if you don't have any reference to extern content in your HTML.
Even in my case, while loading local html files to webview was taking too much time.
Try to load local html files as below, it worked for me:
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:#"website_and_mobile_tnc-1" ofType:#"html"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
[_TandCView loadHTMLString:[headerString stringByAppendingString:htmlString] baseURL:nil];
If you want to load using NSData, try to make baseUrl to "nil" in your code.
I have modified your code as below,
NSData *htmlData = [NSData dataWithContentsOfFile:htmlFilePath];
[wView loadData:htmlData MIMEType:#"text/html" textEncodingName:#"UTF-8" baseURL:nil];
I didn't tried this, but please try and let me know.
Related
I need an undefined amount of text and one or more pictures to be scrollable as one entity. I'm quite surprised that this doesn't seem to be provided by default, I thought I've seen that several times before... I tried to google, but all I find doesn't fit. The images won't be wider than the screen, but in between lines of text.
I need something that let's me do something like:
image
textA
textA goes on
__ screen ends here, content goes on
textA goes on
textA goes on
image
image
textB
textB goes on
image
textC
The content for the text would come out of a plist, but I THINK I can predict it will be REALLY static, so I could just set the Text in IB and create a view for every content -.-.
I've read about Web View, but as far as I got it, you'd need internet connection to make that work, and the app should work without any internet connection at all.
Any suggestions or experiences concerning that?
Thanks a lot!
There different way to get things done:
If you have static content and want a complicated layout and know how to do it in html you should go with UIWebview and a bundled html file and images and load it with something like:[webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"MyStuff" ofType:#"html"]]]];
You can also do a layout with UILabels and UIImageviews and arrange all of this onto one UIScrollview
If it's more dynamic you should go with the latter, but you need to program sort of layout algorithms that handle different number/sizes of images, number/length of test paragraphs and so on.
One option, as you mention, is UIWebView.
It does not require a connection, since you can load a static HTML into it executing:
– loadHTMLString:baseURL:
By specifying a baseURL that "points" to your bundle, you can also include images as resources in your Xcode project and have them displayed (by using <img src=... /img in your HTML):
NSString* basePath = [[NSBundle mainBundle] bundlePath];
[_label loadHTMLString:text baseURL:[NSURL fileURLWithPath:basePath]];
You don't need an internet connection to make a web view work. Look at this method on UIWebView:
- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL
I have an app that uses a Table View to show a list of stories a user can read and when they tap on a particular title a Detail View will open up and the story is displayed.
To begin with I had the app loading up the stories directly from the web and this worked perfectly. I used an array and to pass the details of the particular stories web address and used the following to load up the page
[detailWebView loadRequest:[NSURLRequest requestWithURL:detailURL]];
Now I want to load up files locally instead and from searching around I found the following
[detailWebView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:detailURL ofType:#"html"]isDirectory:NO]]];
and it does load up my local 'Story 1' HTML file but that same HTML file gets loaded up regardless of the file name being passed by detailURL in my Table View which takes the format of
[bookOne addObject:[[NSMutableDictionary alloc]
initWithObjectsAndKeys:#"Story One Title",#"name",
#"Story 1",#"url",nil]];
If I pass it a file name that doesn't exit the program quits so I'm pretty sure the different file names are being passed but the same HTML page always shows.
I've tried reboots etc but the same file always opens up, your help would be really appreciated.
Thanks
Kieron
Try to load the files with
loadHTMLString:baseURL:
it might be that NSURLRequest caches the file/response.
A different solution would to create an instance of the NSURLRequest with initWithURL:cachePolicy:timeoutInterval: and pass NSURLRequestReloadIgnoringLocalAndRemoteCacheData for the cachepolicy
Thanks Nick, while loadHTMLString didn't provide the answer it got me thinking differently and on the right track as to the real cause of the problem.
When using a web address in the first example detailURL had been set as NSURL and when I changed it to NSString my code worked fine. I don't understand why it would cause the loading of the HTML file(s) to act so weirdly but everything is running fine now, the different stories all load up as there should and there are no caching issues of any sort.
i've a problem.
I've an app that download rss feed from a website. It works all fine, but i want to set image in table view where the articles appear. How can i get image from RSS Feed? I need the code.
For title, description i've done it perfectly, but i'm not able to recover image.
P.S. : sorry for my english, i'm italian :)
Ok, so I presume you have the URL of the image. You can do this:
//Normal text setting code
//Normal text setting code
NSString *imageURLString = [rssFeed objectForKey:#img"];
NSURL *imageURL = [NSURL URLWithString:imageURLString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
cell.imageView.image = [UIImage imageWithData:imageData];
That is the quick and filthy method. It's running in your UI thread etc, so it's going to be a terrible user experience. You can fiddle with performance on your own terms, but I'd suggest one of the following:
Load all your data asynchronously and fill the data as it becomes available
Load all of your data in a secondary thread, but show a loading spinner
For both of the above, you would put your images into an NSArray and then call them by doing cell.imageView.image = [myImageArray objectAtIndex:indexPath.row];
The best solution however (and most complicated) though is called 'lazy loading'.
If you want to do lazy loading (the way facebook and twitter do it) you should check this out: LazyTableImages.
Happy coding, Zane
There is a plugin for it.
WP RSS Images Plugin
http://wordpress.org/extend/plugins/wp-rss-images/
After downloading it and succesfully installing it you need to parse the enclosure tag.
It will be in enclosure element with the attribute of "url". If you need I can post the code also.
There's a known problem with embedded UIWebViews that if you load data into them using loadHTMLString or loadData, the canGoBack/canGoForward properties and goBack/goForward methods don't work. These only work when using loadRequest.
Since Safari's normal app cache doesn't work in embedded UIWebViews, creating a native app that effectively caches otherwise live content becomes impossible/unusable. That is, I can cache the contents of the HTML, Javascript, images, etc. and load them via loadHTMLString or loadData, but then the back and forward buttons don't work.
I could also use loadRequest and specify a file URL, but that breaks when it comes to communicating with the live site -- even if I specify a tag (because of cookie domain issues).
I have a work around that involves basically re-implementing the app cache using local store (and not having the native app do any caching itself), which is OK, but not really ideal. Are there any other work arounds/something I missed?
I am using the UIWebView's canGoBack to check to see if I'm at the first page of the history. If I am then I just call the little method I used to load the first page (displayLocalResource sets up the HTMLString and loads it into the webView). Here is a snippet:
//Implementing a back button
- (void)backOne:(id)sender{
if ([webView canGoBack]) {
// There's a valid webpage to go back to, so go there
[webView goBack];
} else {
// You've reached the end of the line, so reload your own data
[self displayLocalResource];
}
}
So do you download the HTML yourself, then pass it to UIWebView as a string? Why so? Do you modify it on the fly or something?
Maybe a custom URL schema would help? You use loadRequest with a schema of your own, which in turn works with HTTP and then feeds the webview whatever data you want?
I had a same problem. I tried manage the history, but it is error prone. Now I have discovered a better solution of this.
What you want to do is simply add a loadRequest to about:blank and make that as a placeholder for you before you call loadHTMLString/loadData. Then you are totally free from monitoring the history. The webview.canGoBack and canGoForward will just work. Of course, you will need a hack to handle go back to the placeholder about:blank. You can do that in webViewDidFinishLoad. Here is the code highlight:
In the function when you call loadHTMLString:
[weakSelf.fbWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
[weakSelf.fbWebView loadHTMLString:stringResponse baseURL:url];
Code to handle goBack:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
if ([webView.request.URL.absoluteString isEqualToString:#"about:blank"]
&& ![webView canGoBack] && [webView canGoForward]) {
[weakSelf.fbWebView loadHTMLString:stringResponse baseURL:url];
}
}
I think it is also possible expand this solution to handle those loadHTMLString that is not the first load. Just by having a Stack to record all the string response and insert an about:blank on each loadHTMLString. And pop the stack when each time go back to about:blank.
Could you fetch the content, save it to the local filesystem, point the webview to the local filesystem using file:// URLs, then intercept the link follows with shouldStartLoadWithRequest to fetch more to local fs, point webview at new local content, etc?
I've had good luck with UIWebView and file:/// URLs. Basically you'd be intercepting load requests, fetching stuff yourself, writing it to the local filesystem with rewritten URLs, then loading that into the browser.
There seems to be no way to load/save the browser history.
Loading the string into a temp file and using that as a URL request seems to cure this. It's something about loading the string directly that causes UIWebView not to see it as the home page you can navigate back to. This code worked for me:
//If you load the string like this, then "webView.canGoBack" never returns YES. It's documented frequently on the web.
//Loading the string as a URL instead seems to work better.
//[self.myWebView loadHTMLString:str baseURL:nil];
//write the string to a temp file
NSString *fileName = #"homepage.html";
NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
[data writeToURL:fileURL atomically:NO];
//open that temp file in the UIWebView
[self.myWebView loadRequest:[NSURLRequest requestWithURL:fileURL]];
Use this to enable/disable the back button:
- (void)webViewDidFinishLoad:(UIWebView *)webView{
//this is to check if we're back at the root page.
if (webView.canGoBack == YES) {
self.backButton.enabled=YES;
}
else {
self.backButton.enabled=NO;
}
}
I was using NYTimes iPhone application, I become bit curious when I notice that it cache UIwebview even though I didn't open that article.Does anyone have idea how to do that?
How NYTimes iPhone application doing offline reading?
Any help greatly appreciated.
Thanks,
Amit
I wrote the NYTimes app. Here are some details that you could have gotten by looking inside the app bundle.
I download the HTML for the articles, strip out whatever unsupported HTML and JS crud the producers stuffed in it and cache it in the backing store.
The article content is contained in a series of P tags (HTML fragment). I stuff that into a special HTML skeleton page that ships with the app. The static wrapper page also contains CSS and JS used to properly display the article and lazily load the images.
The image loading is really cool. The web view is notified when the images are ready. layout is not affected because I already know the sizes of the missing images.
You can use UIWebView to load html files stored locally on the iPhone.
NYTimes app is probably caching html and images in local storage.
Search google for "UIWebView local" and you get several useful hits.
I tried it out and it works great:
First, create a "view based application" and add a UIWebView to the NIB.
Second, add this code to your UIViewController code:
- (void)viewDidLoad
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"index" ofType:#"html"];
NSURL *url = [NSURL fileURLWithPath:path isDirectory:NO];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
[super viewDidLoad];
}
Third, add a file called "index.html" to your "Resources" folder in xcode and it will be displayed.
UPDATE:
Indeed, the complicated part of this is downloading the images and stylesheets for the webpage. Doing this server side is easy with Simple HTML Parser (and PHP). Just package everything in a zip and download to your iPhone.
Alternatively, you could do it locally with a C/C++/OBJC HTML parser (libxml2.2 is available on iOS). See this SO question Parsing HTML on the iPhone.
It's going to a bit of a project, so good luck.