I have a UIWebView and I make it load an HTML string. The string contains http urls to images. The images get loaded, but shouldStartLoadWithRequest: method is not called for them. It is called just once, for about : blank. However all the image requests are successfully passed into my NSURLCache subclass, into cachedResponseForRequest: method. But I would like to handle the requests in the shouldStartLoadWithRequest: method. Can anything be done about that? How can I make these requests go into the delegate method?
Here is the code:
#interface URLCache : NSURLCache
#end
#implementation URLCache
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
NSLog(#"REQUEST = %#", request.URL);
return [super cachedResponseForRequest:request];
}
#end
#interface ViewController ()<UIWebViewDelegate>
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[NSURLCache setSharedURLCache:[URLCache new]];
NSString* path = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"html"];
NSString* html = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:nil];
self.webView.delegate = self;
[self.webView loadHTMLString:html baseURL:nil];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"URL = %#", request.URL);
return YES;
}
#end
The shouldStartLoadWithRequest method is called only for page loads (that is, when the page as a whole gets loaded or when a frame's contents get loaded), not when loading individual resources such as images, JavaScript, or CSS.
If you want to manipulate all HTTP/HTTPS requests from a UIWebView, the only way I'm aware of to do so is by using a custom NSURLProtocol subclass. See Apple's Custom HTTP Protocol sample code project for an example of how to do this.
Related
This question already has an answer here:
Async request does not enter completion block
(1 answer)
Closed 5 years ago.
I want to use UIWebView in the console, so I define a delegate:
#interface webViewDelegate : NSObject <UIWebViewDelegate>
and write the protocol:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
and then loadRequest:
webView = [[UIWebView alloc] init];
webView.delegate = WVdelegate;
NSURL *htmlURL = [NSURL URLWithString:#"http://192.168.0.100"];
NSURLRequest *request = [NSURLRequest requestWithURL:htmlURL];
[webView loadRequest:request];
The problem is that this is a console, and I want to finish the console after the delegate has been invoked. What should I do to wait for the delegate after calling loadRequest?
The problem is that you have no runloop. Thus, when your code comes to an end, the command-line tool comes to an end; things do not persist long enough for the asynchronous code to run.
You can get HTML source in console:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// Check here if still webview is loding the content
if (webView.isLoading)
return;
NSString *fullHtml = [webView stringByEvaluatingJavaScriptFromString:#"document.getElementsByTagName('html')[0].outerHTML"];
NSLog(#"html:%#", fullHtml);
}
I have two webView, in the first i load an hostname and, when webViewDidFinishLoad i take a part of current url with this method:
//Method that take a part of currentUrl of a webView
- (NSString *) webViewGetLocation {
NSString *html = [dnsWebView stringByEvaluatingJavaScriptFromString:#"document.body.innerHTML"];
if ([html rangeOfString:#"Account"].location != NSNotFound) {
//Take loaded ip
NSString *currentURL = [dnsWebView stringByEvaluatingJavaScriptFromString:#"window.location.href"];
NSLog(#"IP TAKEN: %#", currentURL);
//Do substring to find part of ip, until the port
NSString *ip = [[self splitString: currentURL key: #"/login"] objectAtIndex: 0];
NSLog(#"IP AND PORT: %#", ip);
}
return ip;
}
and at this time all ok.
Now in the second webView, i load an url that should be composed by the ip, that i take in previous method, and a second part that not change.
My problem is: how can i take that url (return ip), since i have launched method "webViewGetLocation" in method "webViewDidFinishLoad" that do not have a return value???
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self webViewGetLocation];
}
I try to save returned ip of method "webViewGetLocation" in an appDelegate variable, but in viewDidLoad it is empty.
Thats easy, create a class property of type NSURL in your .h file
Then set it in - (void)webViewDidFinishLoad:(UIWebView *)webView {
NSURL *url = [NSURL URLWithString:#"folder/file.html" relativeToURL:baseURL];
It's possible, in the same class, call method "webViewDidFinishLoad" more times, one for each webview for example???
How to make this statement into an actual "if statement" in Xcode?
This is what i want in the form of an "if statement" :
"if a certain link is shown in UIWebView, then image.hidden = NO;"
The UIWebView is currently showing another website, but it has a page in that website that is the certain link I mentioned in the if statement.
The code for the UIWebView is this:
.h:
IBOutlet UIWebView *Name;
NSURL *NameURL;
.m
NameURL = [NSURL URLWithString:#"mywebname.com"];
NSURLRequest *Request = [NSURLRequest requestWithURL: NameURL];
[Name loadRequest: Request];
To access the actual page content within UIWebView, you need to use
stringByEvaluatingJavaScriptFromString: to invoke some JavaScript snippet. Once the page finishes loading, the idea is to loop through all a elements and check for its href attribute, comparing it to the 'certain site' that you mentioned.
Rough implementation:
JS Code
for (var i = 0; i < document.getElementsByTagName('a').length; i++)
{
if (document.getElementsByTagName('a')[i].href.match(/google/) return 'YES';
}
return 'NO';
Obj-C Code
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *test = [webView stringByEvaluatingJavaScriptFromString:aboveJSString];
if ([test isEqualToString:#"YES"]) image.hidden = NO;
}
Hope it helps.
According to Anh Do's answer stringByEvaluatingJavaScriptFromString isn't reliable for me.
Instead you should use:
currentURL = currentWebView.request.URL.absoluteString;
So the code will be like….
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"HELLO DID GET CALLELDLLL LL LL LL ");
//get the url you webView called "Name" is displaying...
NSString *currentURL = Name.request.URL.absoluteString;
//Check if that url is "mywebname.com"
if ([currentURL isEqualToString:#"mywebname.com"])
{
image.hidden = NO;
}
}
PS. Don't for get to add delegate to you webview
By setting delegate do this..
In ViewDidLoad:
[Name setDelegate:self];
In .h file
#interface "nameofyourviewcontroller" : UIViewController<UIWebViewDelegate>
I have a problem I have discovered in my app that has a UIWebView. iOS 7 caches a blank body 304 response, resulting in blank pages being shown when the user refreshes the UIWebView. This is not good user expierience and I'm trying to figure out how to solve this on the iOS side, as I do not have control over how Amazon S3 responds to headers (that's who I use for my resource hosting).
More details of this bug were found by these people: http://tech.vg.no/2013/10/02/ios7-bug-shows-white-page-when-getting-304-not-modified-from-server/
I'd appreciate any help offered to how I can solve this on the app side and not the server side.
Thank you.
Update: fixed this bug using the bounty's suggestion as a guideline:
#property (nonatomic, strong) NSString *lastURL;
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if ([self.webView stringByEvaluatingJavaScriptFromString:#"document.body.innerHTML"].length < 1)
{
NSLog(#"Reconstructing request...");
NSString *uniqueURL = [NSString stringWithFormat:#"%#?t=%#", self.lastURL, [[NSProcessInfo processInfo] globallyUniqueString]];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:uniqueURL] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:5.0]];
}
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
self.lastURL = [request.URL absoluteString];
return YES;
}
You can implement a NSURLProtocol, and then in +canonicalRequestForRequest: modify the request to override the cache policy.
This will work for all requests made, including for static resources in the web view which are not normally consulted with the public API delegate.
This is very powerful, and yet, rather easy to implement.
Here is more information:
http://nshipster.com/nsurlprotocol/
Reference:
https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Classes/NSURLProtocol_Class/Reference/Reference.html
Here is an example:
#interface NoCacheProtocol : NSURLProtocol
#end
#implementation NoCacheProtocol
+ (void)load
{
[NSURLProtocol registerClass:[NoCacheProtocol class]];
}
+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
{
if ([NSURLProtocol propertyForKey:#“ProtocolRequest” inRequest:theRequest] == nil) {
return YES;
}
return NO;
}
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)theRequest
{
NSMutableURLRequest* request = [theRequest mutableCopy];
[request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
//Prevent infinite recursion:
[NSURLProtocol setProperty:#YES forKey:#"ProtocolRequest" inRequest:request];
return request;
}
- (void)startLoading
{
//This is an example and very simple load..
[NSURLConnection sendAsynchronousRequest:self.request queue:[NSOperationQueue currentQueue] completionHandler:^ (NSURLResponse* response, NSData* data, NSError* error) {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}];
}
- (void)stopLoading
{
NSLog(#"something went wrong!");
}
#end
As the other questions are to use the NSURLConnection every time, which seems like a bit of an overhead:
Why don't you execute a small javascript after the page was loaded (complete or incomplete) that can tell you if the page is actually showing? Query for a tag that should be there (say your content div) and give a true/false back using the
[UIWebView stringByEvaluatingJavaScriptFromString:#"document.getElementById('adcHeader')!=null"]
And then, should that return false, you can reload the URL manually using the cache-breaker technique you described yourself:
NSString *uniqueURL = [NSString stringWithFormat:#"%#?t=%d", self.url, [[NSDate date] timeIntervalSince1970]];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:uniqueURL] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:5.0]];
[edit]
based on the discussion in the comments and some of the other answers, I think you might have the best solution manually changing the NSURLCache.
From what I gathered, you're mainly trying to solve a reload/reshow scenario. In that case, query the NSURLCache if a correct response is there, and if not delete the storedvalue before reloading the UIWebView.
[edit 2]
based on your new results, try to delete the NSURLCache when it is corrupted:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache]cachedResponseForRequest:request];
if (cachedResponse != nil && [[cachedResponse data] length] > 0)
{
NSLog(#"%#",cachedResponse.response);
} else {
[[NSURLCache sharedURLCache] removeCachedResponseForRequest:request];
}
return YES;
}
We might have to refine the check if the cache is invalid again, but in theory this should do the trick!
NSURL *URL = [NSURL URLWithString:#"http://mywebsite.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30.0];
[myWebView loadRequest: request];
When creating NSURLRequest instance, you can set Cache Policy.
Hope it works!
I am having difficulties gettin a webpage to load in my app. I think the issue has to do with it having back to back / in it at one point, but am not sure how to work around this. The URL I want it to visit is http://kaiopublications.org/content//iLuminateVol1.1/index.html
Here is my code:
- (void)viewWillAppear:(BOOL)animated {
NSString *html = _entry.articleUrl;
NSURL *url = [NSURL URLWithString:html];
NSLog(#"URL%#", html);
[_webView loadRequest:[NSURLRequest requestWithURL:url]];
}
The log for html comes back with the correct address, but if I run a log on url, it comes back null.
It must be something else or you may just need to wait. I tried it quickly myself and I can load the site with this URL in my test app. But I realize the site did load very slowly even on the simulator with a regular internet connection. If you try it on your device with a poor mobile bandwidth it maybe just takes very long.
One more thought. Is there any "noise" character at the end of the string?
Try this to see if it is that:
NSURL *url = [NSURL URLWithString:#"http://kaiopublications.org/content//iLuminateVol1.1/index.html"];
[_webView loadRequest:[NSURLRequest requestWithURL:url]];
Add some delegate methods to help you diagnose this. So you can display an activityIndicatorView while waiting for loads. It could be a slow site or it could be the delegate not setup.
#pragma mark - UIWebView delegate methods
- (void)webViewDidStartLoad:(UIWebView *)webView
{
if (!_activity.isAnimating)
[_activity startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[_activity stopAnimating];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (!_activity.isAnimating)
[_activity startAnimating];
return YES;
}