I have a need to monitor all failed requests from a given web page loaded in WKWebKit. For this, I implemented a simple controller with WKWebView on it and also conformed that controller to WKNavigationDelegate:
- (void)viewDidLoad
{
[super viewDidLoad];
webView.navigationDelegate = self;
NSURL *url = [NSURL URLWithString: #"https://google.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[webView loadRequest:request];
}
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
NSLog(#"webView.decidePolicyForNavigationResponse %#", navigationResponse.response);
decisionHandler(WKNavigationResponsePolicyAllow);
}
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSLog(#"webView.decidePolicyForNavigationAction %#", navigationAction.request);
decisionHandler(WKNavigationActionPolicyAllow);
}
I've also tried to implement custom NSURLProtocol extension but can see there just the initial request as with navigationDelegate:
[NSURLProtocol registerClass:[TrackingNSURLProtocol class]];
I can see my primary request to the google.com webpage in my output but the children's requests kicked off by that page are not tracked even though they are executed and downloaded by the same WKWebView when the page is loaded (css, js, images, etc).
Is it possible to achieve this kind of tracking with WKWebView (or in general with iOS)?
Depending on what "failed" means, you might prefer to implement the methods with fail in their names:
func webView(WKWebView, didFail: WKNavigation!, withError: Error)
Called when an error occurs during navigation.
func webView(WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error)
Called when an error occurs while the web view is loading content.
Related
I have an app with UIWebView, which loads a web site https://app.bridallive.com/. Previously it was working well in iPad, but now it does not.
It loads neither in UIWebView (in a simplest possible app) and in iOS Safari. Works well in Mac OS.
I tried to diagnose it with:
-(void) loadWebSite {
NSURL * url = [NSURL URLWithString:#"https://app.bridallive.com/#/dashboard?iPadApp=true"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest: request];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self loadWebSite];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSLog(#"Error : %#",error);
}
NSAppTransportSecurity is set to YES and the web site has valid and trusted certificate.
this is what I used in futher experiments:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadWebSite];
}
but this does not give any errors.
Can anyone suggest a way to diagnose this in iOS?
Best regards,
Andrey
UIWebview has a protocol, UIWebViewDelegate, it helps you to observe changes in your
It implements a few method using what you can observe the changes in your UIWebview loading lifecyclye:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(#"shouldStart");
return true;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
NSLog(#"webViewDidStartLoad");
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"webViewDidFinishLoad");
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSLog(#"%#", error);
NSLog(#"didFailLoadWithError");
}
In order to receive the callback on this functions, sign up for the delegate on your classes interface in .h for example.
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIWebViewDelegate>
#end
I have set up a sample project, and upon opening the URL, i receive the following error in the didFailLoadWithError callback:
Error Domain=WebKitErrorDomain Code=101 "(null)"
It seems you are not encoding you request properly. You should encode the string the following way:
NSString *encodedString=[[NSString stringWithFormat:#"https://app.bridallive.com/#/dashboard?iPadApp=true"] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
NSURL *weburl = [NSURL URLWithString:encodedString];
When I click a pop up in wkWebView, I get that action via
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
if (!navigationAction.targetFrame.isMainFrame) {
//[webView loadRequest:navigationAction.request];
[self showInternalWeb:navigationAction.request.URL.absoluteString];
}
return nil;
}
and displaying the link in a different controller.
I would like to dismiss that controller automatically when the window is closed. In my laptop browser, the controller is automatically closed after I press a button on the new window. I am wondering how code this functionality myself.
I have tried calling:
[userContentController addScriptMessageHandler:self name:#"window.close()"];
[self.webView evaluateJavaScript:#"window.close()" completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
NSLog(#"suces");
}];
This calls webViewDidClose normally, but not when the window becomes blank.
Please check that, did you set your UIDelegate for the webView..
webView.UIDelegate = self;
I have authorization logic managed via WebView:
user click on login button in app
I open WebView with loading login webpage (opened by url address)
user provide their login and password > click on login button on page
next page is opened in WebView - authorize access confirmation page
user has to click on authorize access button on webpage
after this redirect_uri URL will be tried to be opened in WebView
I catch this redirect_uri in request and close WebView
I inspect all requests (in order to catch redirect_uri) in WebView delegate method shouldStartLoadWithRequest:.
Question: how can I manage server response for each request for page opened in WebView?
The point is that we have some problems with auth server and time to time it shows error pages. So I want to catch such pages and close WebView for such cases.
found the solution via WKWebView.
I connected it the same way as UIWebView worked in my project.
And then I actually use 2 methods from WKNavigationDelegate protocol:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
and
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
Example of code:
first method is to check whether current request is actually our redirect_uri that we should handle:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSLog(#"request: %#", navigationAction.request);
// check whether this request should be stopped - if this is redirect_uri (like user is already authorized)
if (...) {
self.webView.navigationDelegate = nil;
// do what is needed to send authorization data back
self.completionBlock(...);
// close current view controller
[self dismissViewControllerAnimated:YES completion:nil];
// stop executing current request
decisionHandler(WKNavigationActionPolicyCancel);
} else {
// otherwise allow current request
decisionHandler(WKNavigationActionPolicyAllow);
}
}
second method is to analyse response from server, and here we can verify the status code in order to handle any errors
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
// filter the responses, in order to verify only the response from specific domain
if ([[NSString stringWithFormat:[[response URL] absoluteString]] containsString:#"some.domain.com/"]) {
NSInteger statusCode = [response statusCode];
// check what is the status code
if (statusCode == 200 ||
statusCode == 301 ||
statusCode == 302) {
// allow response as everything is ok
decisionHandler(WKNavigationResponsePolicyAllow);
} else {
// handle the error (e.g. any of 4xx or any others)
self.webView.navigationDelegate = nil;
// send needed info back
self.completionBlock(...);
// close current view controller
[self dismissViewControllerAnimated:YES completion:nil];
// stop current response
decisionHandler(WKNavigationResponsePolicyCancel);
}
} else {
// current response is not from our domain, so allow it
decisionHandler(WKNavigationResponsePolicyAllow);
}
}
Implement below delegate method might be work for you.
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSCachedURLResponse *urlResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:webView.request];
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) urlResponse.response;
NSInteger statusCode = httpResponse.statusCode;
NSLog(#"%#",statusCode);
}
In swift, you can try and cast the URLResponse to HTTPURLResponse to get the statusCode field.
extension MyController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
if let httpResponse = navigationResponse.response as? HTTPURLResponse {
let statusCode = httpResponse.statusCode
//....
}
decisionHandler(.allow)
}
}
I am trying to build a filter for a UIWebView and I am struggiling to detect when the UIWebView has completely finished loading. I have used the following two methods
– webView:shouldStartLoadWithRequest:navigationType:
– webViewDidFinishLoad:
but the issue is that these will be called multiple times when a page has frames and additional content to load.
What I need is to know when the view has completely loaded and there is no more content to fetch. Then when the content has loaded I can check to URL of the page against a list of approved URLS.
ANy ideas?
Use the UIWebViewDelegate protocol method webViewDidFinishLoad and webView's isLoading property
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//Check here if still webview is loding the content
if (webView.isLoading)
return;
//after code when webview finishes
NSLog(#"Webview loding finished");
}
Swift 3 version:
func webViewDidFinishLoad(_ webView: UIWebView) {
if webView.isLoading{
return
}
print("Done loading")
}
Try use:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)requestURL navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = [requestURL URL];
NSLog(#"##### url = %#",[url absoluteString]);
return YES;
}
don't forget to set your UIWebView Delegate
or add statement,
NSRange range = [[url absoluteString] rangeOfString:#"https://www.google.com"];
if (range.location != NSNotFound)
{}
hope to help you.
It is true that the original question was posted many years ago. Recently I had to find a reliable solution to this issue.
Here is the solution that worked for me:
Replaced UIWebView with WKWebWiew.
Added 'evaluateJavaScript' code while handling the 'didFinishNavigation' delegate method.
The complete code is:
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
[webView evaluateJavaScript:#"document.body.innerHTML" completionHandler:^(id result, NSError *error) {
if (result != nil) {
// Call your method here
}
if(error) {
NSLog(#"evaluateJavaScript error : %#", error.localizedDescription);
}
}];
}
If you aren't an owner of UIWebView and as a result you don't have an access to webViewDidFinishLoad but you have a reference to UIWebView you can use the next code
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { (timer) in
guard uiWebView.isLoading else {
return
}
timer.invalidate()
//Add your logic here
}
I have a code which places a phone call using below code :
// Make a call to given phone number
- (void)callPhoneNumber:(NSString *)phoneNumber
{
if (!self.webview)
self.webview = [[UIWebView alloc] init];
self.webview.delegate = self;
// Remove non-digits from phone number
phoneNumber = [[phoneNumber componentsSeparatedByCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:#""];
// Make a call
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:#"tel:%#", phoneNumber]];
[self.webview loadRequest:[NSURLRequest requestWithURL:url]];
[self.view addSubview:self.webview];
}
This makes a call. What I want is, I want to know when user ends a call. I have to perform an operation when user ends a call. Is there any way for it?
What I tried is, I set delegate of webview to current controller. But none of the delegate methods is called.
- (void)webViewDidStartLoad:(UIWebView *)webView
{
DLog(#"Start Loading");
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
DLog(#"Finish Loading");
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
DLog(#"Did fail with error : %#", error);
}
I don't know if you need this info but I use webview so that when phone call is made, flow remains within the app and on call end, app screen is displayed rather than user manually coming to app from native contact app.
CoreTelephony framework has a CTCallCenter Class with callEventHandler property.
#property (nonatomic, copy) void (^callEventHandler)(CTCall*);
You will have to define a handler block in your application and assign it to this property.
If your application is active when a call event takes place, the system dispatches the event to your handler immediately when call state changes. Refer apple documents found here.
I used below code to get notified of call events.
// Create CTCallCenter object
callCenter = [[CTCallCenter alloc] init];
// Assign event handler. This will be called on each call event
self.callCenter.callEventHandler = ^(CTCall* call) {
// If call ended
if (call.callState == CTCallStateDisconnected)
{
NSLog(#"Call ended.");
}
};
For other call states check out Call States. For more info on CTCallCenter look at Apple doc for CTCallCenter.
You should be implementing this delegate method of UIWebView
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
on end call operation your webview will notify this delegate method about your action perform and you can handle it in there for example
-(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked )
{
NSURL *url = [request URL];
NSLog(#"URL ===== %#",url);
// you can check your end call operation URL and handle it accordingly
if ([[url absoluteString] rangeOfString:#"#"].location == NSNotFound)
{
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
//return NO;
}
return YES;
}