I have a WKWebView that loads a local set of webpages using WKWebViewConfiguration to set the configuration for #"allowFileAccessFromFileURLs" to be true.
The request is set up with something like this:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"testPage" withExtension:#"html" subdirectory:#"html/pages"];
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10.0f];
Then the request is sent using the following WKWebview method:
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
my problem is where the device has a current connection but there is no network traffic. The webview as an element on the screen will be added to the screen and the request will be made but the webview will show a white screen for about 50 seconds before displaying the local content.
Everything in the webview loads regardless of the network status as its loaded locally when there is no link conditioner set.
For example if the device is connected to wireless but the network link conditioner is set to 100% loss. The webview is created and the request is sent to load the local content triggering the hang of the load.
I had a thought that it might be the WKWebView trying to do some kind of validation in the background that requires a network transaction but I did some network profiling with instruments and also some timeline recording in the safari webview and I couldnt see anything that would cause it to hang.
The only reason I can think of it loading local content after 50 seconds or so is that its hit some sort of WKWebView timeout to load a network connection.
Any help would be greatly appreciated, thanks.
Okay so for anyone else who stumbles across this I have found what I was doing wrong.
The issue was not actually the WKWebview or the web content itself it was how I was handling the completion of the webview loading.
in the method:
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
I was listening for a completion of events by evaluating some JS like so:
-(void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
{
[webView evaluateJavaScript:#"document.body.innerHTML" completionHandler:^(id result, NSError *error)
{
if (result != nil) {
[self doCertainNetworkEvent]; //here another method is called with a networking function inside of it.
}
if(error)
{
NSLog(#"evaluateJavaScript error : %#", error.localizedDescription);
}
}];
}
The completion block of course couldnt finish until the network function within the didFinishNavigation method call was finished (which it couldnt because there was no traffic.)
Related
I have a webview that can be cycled through different URLs. When I switch from one to the other I want the old web page to disappear before loading the next one. How can I do this without re allocating the webview?
If I try to [self.webView loadHTMLString:#"" baseURL:nil]; and load my URL in the same function the previous web page still remains:
[self.webView loadHTMLString:#"" baseURL:nil];
[self.webView loadRequest:[NSURLRequest requestWithURL:self.pageURL]];
EDIT: this apparently isn't clear enough for you. the below code doesn't clear the webview, it displays the previous page until the new one is loaded:
- (void) startLoadOfNextURL:(NSURL*)url
{
// clear:
[self.webView loadHTMLString:#"" baseURL:nil]; //DOESNT WORK
// Load real next URL
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
You can write the code below while your controller dismisses.
webView.load(URLRequest(url: URL(string:"about:blank")!))
You can make it load a blank page
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
Use JavaScript instead.
webView.evaluateJavaScript("document.body.remove()")
The following code clears the screen and then navigates
Swift
webView.evaluateJavaScript("document.documentElement.remove()") { (_, _) in
self.webView.load(urlRequest)
}
To clear old contents of webview
When you call - loadHTMLString:baseURL: it doesn't block until the load is complete. Once it records your request, it returns and loads in the background.
As a result, you would need to wait for the first load to finish before kicking off a new load request.
With UIWebView you would use UIWebViewDelegate's
- webViewDidFinishLoad:.
With WKWebView you would use WKNavigationDelegate's
- webView:didFinishNavigation:
Another approach if you really wanted to clear the contents without a delegate method would be to use JavaScript (eg https://stackoverflow.com/a/4241420/3352624). Then for UIWebView you could invoke - stringByEvaluatingJavaScriptFromString:. That method will block execution until the JavaScript executes and returns.
For WKWebView, you would need to do something like https://stackoverflow.com/a/30894786/3352624 since its - evaluateJavaScript:completionHandler: doesn't block.
To make old contents "disappear"
If you really just want to make "the old web page to disappear", you could cover the content area of the webview with a blank UIView temporarily. You could hide the contents when you initiate the load and then show the contents using the delegate methods above after the load completes.
Swift
webView.load(URLRequest(url: URL(string: "about:blank")!))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
//Load request or write code here
})
I set up 2 UIWebViews, the first is controlling the second. They are communicating though ajax requests.
I want to load a website in the second WebView and then proceed with other tasks. Unfortunately this is crashing. It is crashing because the Web Thread is being occupied by the first right after it gets a response. The second has no time to load the web page and causes a deadlock.
I want to delay the response until the second WebView has fully loaded the web page. Currently the second WebView starts loading right after the first WebView gets and response (thats when the Web Thread is being released).
Is it possible to "suspend"/"pause" the current (first WebView) execution until the second WebView has finished loading? This means to start the execution of the second WebView as well.
events:
First WebView sends command to load web page (using synchronous AJAX command)
Web Thread blocked by task of first WebView
Execution of command and computation of Response
Returning Response
Second WebView starts Loading of web page
deadlock
I want event 5 to be before event 4. Is this possible?
Solution:
As you can read in the comments I've solved my problem by making then work concurrently. Basically I had to make use of the Grand Central Dispatch (GCD). Another option would be to implement it with NSOperationQueues which gives you more control about the flow of execution, but tends to be more complicated to implement.
helpful literature:
Apple: Concurrency Programming Guide
Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial
How To Use NSOperations and NSOperationQueues
Now, this is may require some tweaking, but it should give you a good place to start.
Basically, we create a concurrent GCD queue and dispatch 2 async calls to load HTML strings with the contents of your 2 different URLS.
When the requests complete they will load their html strings into your web views. Note that the first UIWebView will only load its data if the second UIWebView has already been loaded.
__weak ViewController *bSelf = self;
dispatch_queue_t webQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(webQueue, ^{
NSError *error;
bSelf.html1 = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://google.com"] encoding:NSASCIIStringEncoding error:&error];
if( !bSelf.secondLoaded)
{
dispatch_sync(dispatch_get_main_queue(), ^{
[bSelf.webView1 loadHTMLString:bSelf.html1 baseURL:nil];
});
}
});
dispatch_async(webQueue, ^{
NSError *error;
bSelf.html2 = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://amazon.com"] encoding:NSASCIIStringEncoding error:&error];
bSelf.secondLoaded = YES;
dispatch_sync(dispatch_get_main_queue(), ^{
[bSelf.webView2 loadHTMLString:bSelf.html2 baseURL:nil];
if( bSelf.html1 != nil )
{
[bSelf.webView1 loadHTMLString:bSelf.html1 baseURL:nil];
}
});
});
Yes, the two best ways to do this would be to use either Grand Central Dispatching (GCD) or NSOperation and NSOperationQueue.
The explanation of this is quite long, but I would direct you to read something like this. You can find a lot of other resources if you search for these terms in google.
Have you tried something like this?
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView.delegate = self;
self.webView2.delegate = self;
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"yourURL"]]];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
if (webView == self.webView)
{
if (!self.webView.isLoading)
{
[self.webView2 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"yourURL"]]];
}
}
}
I've got a UIWebView that's loading a simple request like so:
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"derp.com"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
[webView loadRequest:theRequest];
I have another method that executes some JavaScript on the webView. This method may be called multiple times from different sources (including webViewDidFinishLoad and viewDidAppear). To protect against errors I have wrapped this in an if statement like so:
if (!self.webView.loading) {
... do stuff....
}
The problem is self.webView.loading is ALWAYS 0. I have even tried to set up an observer (tried a few different variations.... not 100% sure of the sytnax):
[self addObserver:self forKeyPath:#"webView.loading" options:0 context:NULL];
But observeValueForKeyPath:ofObject:change:context: never gets called.
Better to implement the UIWebViewDelegate methods...
Set the delegate in viewDidLoad:
[webView setDelegate:self];
You can use
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//do things once loaded }`
To get a call back when the load has completed and it's much more reliable than messing with KVO.
by looking at UIWebView doc
isLoading = YES If the receiver is still loading content; otherwise, NO.
Then, is it possible that at this point the loading of your web view is already finished ?
How to determine progress in UIWebView?
Few Observations:
1. To determine the progress of downloaded content, we need to make a NSURLConnection object and fetch data twice: one with the UIWebView and the other with NSURLConnection
2. If we just fetch data once using NSURLConnection and load the webview with that data text/html then that data renders poorly
Difficulties:
a) As fetching data twice can largely slow down the process, is it feasible (appstore safe?) to use private api's like the one given here: https://github.com/petr-inmite/imtwebview
b) If we cannot, then how we may display a progress bar?
c) Also will downloading the data asynchronously using NSURLConnection mirror the progress of UIWebView loading? How bad the performance of fetching data twice would be?
There are some browsers like safari, dolphin which are displaying progress bar...any ideas on how to do this???
Use the UIWebView delegates for it.First set your webview's delegate to be self and then utilize these methods.Prior to this create and add a spinner to the view which shall indicate the progress of uiwebview e.g UIActivityIndicatorView *spinner
- (void)webViewDidStartLoad:(UIWebView *)localWebView {
[ spinner performSelectorInBackground: #selector(startAnimating) withObject: nil];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[ spinner performSelectorInBackground: #selector(stopAnimating) withObject: nil];
}
- (void)webViewDidFinishLoad:(UIWebView *)localWebView {
[ spinner performSelectorInBackground: #selector(stopAnimating) withObject: nil];
}
I am very new to the whole programming business, and was wondering if there is any way to clear the contents of a UIWebView in iphone programming, so that the loading symbol for the next view is not showing up in front of the last view.
Many Thanks,
Thomas
Try setting the URL to about:blank and reload the page.
Just load an empty html string into it
[self.webView loadHTMLString:#"" baseURL:nil];
Answer extension for documentation purposes to maybe help someone else:
I had the same desire (clear content before loading next url) but had a UIWebView delegate set to receive webviewDidFinishLoad:(UIWebView)webview message and update another part of UI in response.
Problem: the call to clear the content to also called delegate method, so getting false-hits (that is, getting call when clear is done, too, but delegate is coded to expect call only when real content is loaded).
Solution: use a known URL for clear, and have webviewDidFinishLoad: ignore calls made when that URL is finished:
- (void) startLoadOfNextURL:(NSURL*)url
{
// clear:
[self.webView loadHTMLString:#"" baseURL:nil];
// Load real next URL
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(#"WebView finished loading: %#", webView);
if ([self.webView.request.URL.absoluteString isEqualToString:#"about:blank"]) {
NSLog(#" This is Blank. Ignoring as false event.");
}
else {
NSLog(#" This is a real url");
[self updateUIInSomeWay];
}
}
Note: using this:
[self.webView loadHTMLString:#"about:blank" baseURL:nil];
actually causes the words "about:blank" to appear as text in the webview's content pane!
Final complication: In practice, with my two [webview load...] calls so close together, I was finding that instead of a "loaded" event for the clear, the webview was actually canceling it in favor of the second request and calling webView: didFailLoadWithError: for the first load request. Thus, I had to put similar code in that event:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"WebView error on: %#", webView);
NSLog(#"Error is: %#", error);
NSURL* failingURL = [error.userInfo objectForKey:#"NSErrorFailingURLKey"];
if ([failingURL.absoluteString isEqualToString:#"about:blank"]) {
NSLog(#" This is Blank. Ignoring.");
}
else {
NSLog(#" This is a real URL.");
[self doSomethingAboutError];
}
}
Swift, Xcode 7 beta 5
webView.loadRequest(NSURLRequest(URL: NSURL(string: "about:blank")!))
Swift 4.0 , XCODE 9
webView.loadRequest(URLRequest.init(url: URL.init(string: "about:blank")!))
Same answer in Swift 4.2, xCode 10
if let clearURL = URL(string: "about:blank") {
myWebView.loadRequest(URLRequest(url: clearURL))
}