Link to css file in documents from UIWebView without using Base Path - html

I have loaded HTML string (after downloading the css file to documents) like this:
/* the result is HTML file with references to local resources like
<link href="file:///Users/user/Library/Developer/CoreSimulator/Devices/123/data/Containers/Data/Application/123/Documents/gen1.css" rel="stylesheet" type="text/css">
and to external resources like
<link href="http://api.tiles.mapbox.com/mapbox.js/v1.6.4/mapbox.css" rel="stylesheet" />
*/
PageGenerator *generator = [PageGenerator sharedGenerator];
NSString *result = [generator generatePageWithOptions:options];
// filesDir is file:// + [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] + /
[self.webView loadHTMLString:result baseURL:[NSURL URLWithString:[generator filesDir]]];
Now, the problem is that The external resources contain url's like "//www.site.com/img.png" and the UIWebView rewrites them to "file://www.site.com/img.png", and gets 404 error.
How can I access a local resource without the need to set the base URL?
Or how can I rewrite "//" to "http://" instead to "file://"?
Thanks a lot.

What you want to do is install a custom NSURLProtocol by creating a class that subclasses the NSURLProtocol type and implements the NSURLConnectionDelegate and registering it using [NSURLProtocol registerClass:[MyURLProtocol class]];
This is a comprehensive tutorial on doing what you want to do.
Anytime your webview makes a request for an asset, be it a css file or an image, it's going to call + (BOOL)canInitWithRequest:(NSURLRequest *)request with the request. You can inspect the NSURLRequest to see if the asset has a certain scheme using [request.URL.scheme isEqualToString:#"file']. In this scenario, you can then return YES so that your protocol delegate can take over the request.
You'll want to override the URL used however, (or at least the result of what happens with it).
I think you should be able to use something like this:
- (void)startLoading {
NSURLRequest* defaultRequest = self.request; // This is the request for file://...
NSMutableURLRequest* request = [defaultRequest mutableCopy];
// Change the URL or whatever you want on your request here.
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
}
If that doesn't work, you can always look at changing + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request to return your modified request. Note that the result returned should always be identical for a given request as iOS caching mechanisms use the NSURLRequest you return.
You can then add in the rest of the code as shown in the link above to your delegate:
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)stopLoading {
[self.connection cancel];
self.connection = nil;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
This is another example where someone has overridden image requests to always return pictures of David Hasselhoff.
This mechanism is very powerful. For instance, you could use it to theme an app by substituting every css file requested with another at runtime without changing any html.

Related

UIWebView opening the camera and images when a button is clicked

I am currently wrapping a webApp up as an app. With in the web app there is a button that when clicked automatically asks if you want to go to the camera of the stored images and when selected will upload the image for you. In the web app from the phone browser this works as expected.
Now I have wrapped the webApp up in a UIWebView this function no longer works. It actually appears to close the UIWebview when selecting either of the above options. Does anyone know what the procedure is to get this to work through the UIWebView.
Do I have to give permission? Or is there more to it like detecting the HTML when the button is clicked and handling it from the UIWebView.
The HTML on the button looks like this.
<input type="file" required id="file" name="file" accept="image/*" multiple>
ADDED CODE
WebViewController.h
#import <UIKit/UIKit.h>
#interface WebViewController : UIViewController <UIWebViewDelegate,UINavigationControllerDelegate, UIImagePickerControllerDelegate>
{
UIViewController *viewController;
}
........
WebViewController.m
#import "WebViewController.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import "AppDelegate.h"
#end
#implementation WebViewController
- (void)viewDidLoad
{
[super viewDidLoad];
viewController = [[UIViewController alloc] init];
NSString *fullURL = self.mainURL; // get this from defualts - have removed code for this
NSURL *url = [NSURL URLWithString:fullURL];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_viewWeb loadRequest:requestObj];
ALAssetsLibrary *lib = [[ALAssetsLibrary alloc] init];
[lib enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
NSLog(#"%i",[group numberOfAssets]);
} failureBlock:^(NSError *error) {
if (error.code == ALAssetsLibraryAccessUserDeniedError) {
NSLog(#"user denied access, code: %i",error.code);
}else{
NSLog(#"Other error code: %i",error.code);
}
}];
}
-(void)webViewDidStartLoad:(UIWebView *)viewWeb
{
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
}
- (void)webViewDidFinishLoad:(UIWebView *)viewWeb
{
}
//Note: I check to make sure the camera is available. If it is not (iPod touch or Simulator) I show the photo library instead.
-(void) showCamera
{
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
}
else
{
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
}
imagePicker.delegate = self;
[viewController presentViewController:imagePicker animated:YES completion:nil];
}
//Here we intercept any time the webview tries to load a document. When the user hits our "showcamera: url" we go to work.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:
(UIWebViewNavigationType)navigationType
{
if ([request.URL.scheme isEqualToString:#"myApp"])
{
if ([request.URL.host isEqualToString:#"myFunction"])
{
[self showCamera];
}
return NO;
}
return YES;
}
//After the imagepicker has done it's thing, we pass the data back to the webview to be displayed.
//If we wanted to be fancy here, we could have done this via Javascript so we could dynamically insert an image without reloading the page.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[viewController dismissViewControllerAnimated:YES completion:nil];
UIImage* image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *imageData = UIImageJPEGRepresentation (image, 0.5);
NSURL *tmp = [NSURL URLWithString:#"ignore"];
[_viewWeb loadData:imageData MIMEType:#"image/jpeg" textEncodingName:#"UTF-8" baseURL:tmp];
}
#end
I have removed some code that was irrelevant
I have managed to fix the issue and it goes way back to bug that was found in iOS 8 that still hasn't been fixed. Its to do with how the UIWebView is being presented.
Adding this code fixed it.
-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
if ( self.presentedViewController)
{
[super dismissViewControllerAnimated:flag completion:completion];
}
}
For any one else confused or struggling with this Note that you do not need to start messing around with UIImagePicker or UIWebView delegates. Your web view should take care of opening the camera library or camera. This was a real pain of an issue so hopefully this may help others in my position. you can also see THIS Question as this is where i got my answer from. Thanks Andrey for helping too
As an alternative you can try to use UIWebViewDelegate's method webView:shouldStartLoadWithRequest:navigationType: to catch custom events:
In your HTML:
Do something
<button onclick="window.location.href='myApp://myFunction'">Do some action</button>
And then:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if ([request.URL.scheme isEqualToString:#"myApp"]) {
if ([request.URL.host isEqualToString:#"myFunction"]) {
// do something
}
return NO;
}
return YES;
}

File extension change

How can I have a UIWebView load a file that is really an HTML file but has it's extension changed to .wxyz ? However there are a group of files that contain hyperlinks between the files. The hyperlinks in the documents point to files with the .html extension.
So, what I need to have happen is for the UIWebView to recognize that .wxyz == .html and will open the file and when a link like this is clicked A Link will open the file myHTML.wxyz .
Should I subclass UIWebView? Or is there another way to do this?
All of these .wxyz files will be contained within the app and not on a server.
I think you can do this with UIWebViewDelegate webView:shouldStartLoadWithRequest:navigationType: - just detect requests for html that also have a wxyz resource equivalent, return NO, and make a request for the wxyz type instead. The code below should give the gist of it:
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
// if it's not a link click, return YES
if (navigationType != UIWebViewNavigationTypeLinkClicked) {
return YES;
}
NSString *ext= [[request URL] pathExtension];
if ([#"html" isEqualToString:ext]) {
if (/* filename matches one of your wxyz resources */) {
// user clicked an html file that should be loaded via wxyz:
NSURL *newURL = [[request URL] URLByDeletingPathExtension];
newURL = [newURL URLByAppendingPathExtension:#"wxyz"];
NSURLRequest *newRequest = [NSURLRequest requestWithURL:newURL];
[webView loadRequest:newRequest];
return NO;
}
}
}

Injecting local files into UIWebView without breaking links

I am working on an iOS app that needs to display webpages from a server inside a UIWebView while injecting relevant local png and css files as needed in order to speed up load time. Here is the code I am using to try to do this:
NSData *myFileData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.example.com/index.html"]]];
NSString* myFileHtml = [[NSString alloc] initWithData:myFileData encoding:NSASCIIStringEncoding];
[myWebView loadHTMLString:myFileHtml baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
My problem is that some of the webpages have buttons in them that link to other webpages on the server, and because the UIWebView is only loading a string, the buttons when tapped don't cause the UIWebView to load the new webpage URL like it would if I had used the loadRequest method.
My question is how can I get the the UIWebView to behave like it is loading a request while still injecting local files from the baseurl?
Thanks
The relative links in the button cannot work, because the linked pages are on a remote server and not on the device's file system. However you can use a UIWebViewDelegate method to make it work:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
NSString *localRootPath = [NSString stringWithFormat:#"file://%#", [[NSBundle mainBundle] bundlePath]];
NSString *remoteRootPath = #"http://yourdomain.com";
NSString *remotePath = [[request.URL absoluteString] stringByReplacingOccurrencesOfString:localRootPath withString:remoteRootPath];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:remotePath]]];
// or you can use your own loading mechanism here
return NO;
}
return YES;
}
This method intercepts all requests from your WebView. If the request was triggered by a user tap / click the URL gets modified from a relative URL to a absolute URL so it can be loaded from the server. Don't forget to set the delegate on the WebView or this method will not be called.
NSURLPRotocol is a handler for NSURLConnection and will give you the opportunity to intercept the calls to the server and substitute your own content.
1) Derive a class from NSURlProtocol
2) Call NSURLProtocol registerClass: in your application:didFinishLaunchingWithOption
3) Read the documentation on implement these methods as necessary:
initWithRequest:cachedResponse:client:,
startLoading,
URLProtocol:didReceiveResponse:cacheStoragePolicy:
URLProtocolDidFinishLoading:

How to make links shown in phonegaps childbrowser open in Safar?

I have a phone gap app which loads an HTML 5 app in a child browser. Links which are on the pages loaded in the child browser also load in the child browser. I would like these links to open in Safari. How could i do that?
Thanks
You just need to add this in your AppDelegate.m
- (BOOL)webView:(UIWebView *)theWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *url = [request URL];
// Intercept the external http requests and forward to Safari.app
// Otherwise forward to the PhoneGap WebView
if ([[url scheme] isEqualToString:#"http"] || [[url scheme] isEqualToString:#"https"]) {
[[UIApplication sharedApplication] openURL:url];
return NO;
}
else {
return [ super webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType ];
}
}
Hope this helps

Reading HTML content from a UIWebView

Is it possible to read the raw HTML content of a web page that has been loaded into a UIWebView?
If not, is there another way to pull raw HTML content from a web page in the iPhone SDK (such as an equivalent of the .NET WebClient::openRead)?
The second question is actually easier to answer. Look at the stringWithContentsOfURL:encoding:error: method of NSString - it lets you pass in a URL as an instance of NSURL (which can easily be instantiated from NSString) and returns a string with the complete contents of the page at that URL. For example:
NSString *googleString = #"http://www.google.com";
NSURL *googleURL = [NSURL URLWithString:googleString];
NSError *error;
NSString *googlePage = [NSString stringWithContentsOfURL:googleURL
encoding:NSASCIIStringEncoding
error:&error];
After running this code, googlePage will contain the HTML for www.google.com, and error will contain any errors encountered in the fetch. (You should check the contents of error after the fetch.)
Going the other way (from a UIWebView) is a bit trickier, but is basically the same concept. You'll have to pull the request from the view, then do the fetch as before:
NSURL *requestURL = [[yourWebView request] URL];
NSError *error;
NSString *page = [NSString stringWithContentsOfURL:requestURL
encoding:NSASCIIStringEncoding
error:&error];
EDIT: Both these methods take a performance hit, however, since they do the request twice. You can get around this by grabbing the content from a currently-loaded UIWebView using its stringByEvaluatingJavascriptFromString: method, as such:
NSString *html = [yourWebView stringByEvaluatingJavaScriptFromString:
#"document.body.innerHTML"];
This will grab the current HTML contents of the view using the Document Object Model, parse the JavaScript, then give it to you as an NSString* of HTML.
Another way is to do your request programmatically first, then load the UIWebView from what you requested. Let's say you take the second example above, where you have NSString *page as the result of a call to stringWithContentsOfURL:encoding:error:. You can then push that string into the web view using loadHTMLString:baseURL:, assuming you also held on to the NSURL you requested:
[yourWebView loadHTMLString:page baseURL:requestURL];
I'm not sure, however, if this will run JavaScript found in the page you load (the method name, loadHTMLString, is somewhat ambiguous, and the docs don't say much about it).
For more info:
UIWebView class reference
NSString class reference
NSURL class reference
if you want to extract the contents of an already-loaded UIWebView, -stringByEvaluatingJavaScriptFromString. For example:
NSString *html = [webView stringByEvaluatingJavaScriptFromString: #"document.body.innerHTML"];
To get the whole HTML raw data (with <head> and <body>):
NSString *html = [webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.outerHTML"];
Note that the NSString stringWithContentsOfURL will report a totally different user-agent string than the UIWebView making the same request. So if your server is user-agent aware, and sending back different html depending on who is asking for it, you may not get correct results this way.
Also note that the #"document.body.innerHTML" mentioned above will only display what is in the body tag. If you use #"document.all[0].innerHTML" you will get both head and body. Which is still not the complete contents of the UIWebView, since it will not get back the !doctype or html tags, but it is a lot closer.
To read:-
NSString *html = [myWebView stringByEvaluatingJavaScriptFromString: #"document.getElementById('your div id').textContent"];
NSLog(html);
To modify:-
html = [myWebView stringByEvaluatingJavaScriptFromString: #"document.getElementById('your div id').textContent=''"];
In Swift v3:
let doc = webView.stringByEvaluatingJavaScript(from: "document.documentElement.outerHTML")
(Xcode 5 iOS 7) Universal App example for iOS 7 and Xcode 5. It is an open source project / example located here: Link to SimpleWebView (Project Zip and Source Code Example)
I use a swift extension like this:
extension UIWebView {
var htmlContent:String? {
return self.stringByEvaluatingJavaScript(from: "document.documentElement.outerHTML")
}
}
you should try this:
document.documentElement.outerHTML
UIWebView
get HTML from UIWebView`
let content = uiWebView.stringByEvaluatingJavaScript(from: "document.body.innerHTML")
set HTML into UIWebView
//Do not forget to extend a class from `UIWebViewDelegate` and nil the delegate
func someFunction() {
let uiWebView = UIWebView()
uiWebView.loadHTMLString("<html><body></body></html>", baseURL: nil)
uiWebView.delegate = self as? UIWebViewDelegate
}
func webViewDidFinishLoad(_ webView: UIWebView) {
//ready to be processed
}
[get/set HTML from WKWebView]