Access requested URL in webViewDidFinishLoad? - ios

I'm very new to iOS development so please be gentle.
I understand that webViewDidFinishLoad will fire for each <iframe> plus the original html page.
I'm trying to find the URL of the request that resulted in that webViewDidFinishLoad. As far as I can tell, I can only access webView.request.mainDocumentURL.
I find this perplexing. Inside the shouldStartLoadWithRequest method, which fires also for each <iframe> and the original html request, you can access the URL in question, be it the iframe or the parent page.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"Parent page request: %#", request.mainDocumentURL);
NSLog(#"Actual URL request: %#", [request URL]); // This returns what I want.
return YES;
}
That first log statement will output the url.
However, inside webViewDidFinishLoad
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSURLRequest* request = [webView request];
NSLog(#"Parent page request: %#", request.mainDocumentURL);
NSLog(#"Request outputs the parent, not the iframe: %#", [request URL]);
// how do I access the <iframe> url?
}
Help me StackOverflow. My google-fu is weak and your wisdom is strong.
EDIT: I should be clear on the goal of this. I want to Do Stuff™ when the load event of the parent page fires, not for iframes. I want this to happen as soon as possible, so I don't want to wait until everything finishes.

Think of the UIWebView as your browser. The scope of the object you are interrogating is at the browser level, the iFrames are more granular than that. The URL that populates the iFrame is used to stream content for that section of the page, but for the purposes of the UIWebView, you only need to remember the main URL so the page can be managed (reloaded, etc...) as that URL clearly brings with it instructions as to how to manage the iFrame's content.
If you really need to those iFrame source URLS, simply extendeding UIWebView to hold an array of links (populate the array inside "shouldStartLoadWithRequest"). You can then do whatever you want with that list of links.
Good Luck.

Related

How to get a final URL after t.co redirects in WKWebView

I know that I can generally get the current URL of WKWebView by using the URL property. However I have discovered that when there is a redirect, it will not give me the proper URL.
For example, if I go to http://twitter.com and then click on a link to some other company (ex: http://mycompany.com), then I see a t.co/XXX URL which eventually redirects me to mycompany.com.
However, when I look at WKWebView's URL property, I am seeing "t.co" instead of "mycompany.com".
Strangely, I am never seeing didReceiveServerRedirectForProvisionalNavigation:... called, and when I check URL at didStartProvisionalNavigation:... and decidePolicyForNavigationAction:... I just see the "t.co" URL instead of the "mycompany.com" one.
Also, I will need to know the domain in order to make some adjustments to the body, so I am not sure if I can use JS here.
Please let me know if you have any ideas.
UPDATE: I realized this only happens when I use a custom URL scheme set via setURLSchemeHandler:, which I had omitted from the question originally since I didn't think it was related.
After some playing around, I was able to get things working with the following change in willPerformHTTPRedirection:...:
NSMutableURLRequest *newRequest =
[NSMutableURLRequest requestWithURL:request.URL];
dispatch_async(dispatch_get_main_queue(), ^{
[_webView loadRequest:newRequest];
});
completionHandler(nil);
UPDATE: This has a serious drawback because URLs that are not for the main frame will come into willPerformHTTPRedirection:..., and if the request is re-loaded for all of these the page gets messed up.
I need a way to determine if the URL is from the main frame and only do the reload for those.
Alright, so i made it. https://imgur.com/a/eBSaNn7
The solution is to cancel the navigation and load the request with loadRequest: again.
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame?.isMainFrame != true {
webView.load(navigationAction.request)
}
return nil
}

How can I get response from UIWebView page to an iOS app?

I'm using UIWebView as a subview in a UIViewController. What this WebView does is it allows user to submit a form and that form responses as JSON object. How can I get this response to an app? I'm not able to find what response comes. All I know is that response is JSON. I even tried using shouldStartLoadWithRequest(). But it only shows URL. Any help will be appreciated.
I use the 'window.location'. A little uncomfortable but fine.
You can try implementing this method may be it helps you
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
and check for the navigation types out of these
typedef NS_ENUM(NSInteger, UIWebViewNavigationType) {
UIWebViewNavigationTypeLinkClicked,
UIWebViewNavigationTypeFormSubmitted,
UIWebViewNavigationTypeBackForward,
UIWebViewNavigationTypeReload,
UIWebViewNavigationTypeFormResubmitted,
UIWebViewNavigationTypeOther
}

UIWebView not displaying my webpage

For some background info, the webpage I'm trying to display is a web app currently being hosted on AWS's EC2. The backend is Python w/ Flask and the frontend is just simple HTML/CSS. The URL has HTTP, as it isn't secured with HTTPS yet. When the url for this webpage is opened, the first thing the browser asks is for login credentials (the browser asks, not the website). This page does load in mobile Safari on my iPhone, and Safari does successfully ask for the credentials. If I enter them in correctly, it will correctly load the page.
So I've tried both Allow Arbitrary Loads under App Transport Security Settings as well as a customized Exception Domain with the following keys:
App Transport Security Settings Dictionary
Exception Domains Dictionary
my website URL Dictionary
NSIncludesSubdomains Boolean (YES)
NSExceptionAllowsInsecureHTTPLoads Boolean (YES)
NSThirdPartyExceptionAllowsInsecureHTTPLoads Boolean (YES)
NSExceptionMinimumTLSVersion String (TLSv1.0)
NSExceptionRequiresForwardSecrecy Boolean (YES)
However, whenever I launch the app on the simulator all I'm getting back is a white screen (can post screenshot if needed).
Here's my code in ViewController.swift:
import UIKit
class ViewController: UIViewController {
#IBOutlet var WebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "My URL inserted here")
let request = NSURLRequest(URL: url!)
WebView.loadRequest(request)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
If I use Allow Arbitrary Loads, when I look in the output box, it does not say "App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file." When I configure the Exception Domain correctly (with Allow Arbitrary Loads removed) it won't give me the message either. Only sometimes when I change around the settings using Exception Domain (again, with Allow Arbitrary Loads removed) do I get this output.
I'm starting to think the issue goes beyond security, and any advice or steps I could take to try and fix this issue would be much appreciated, thanks!
The white screen is a bit odd, assuming that a 401 would result in a standard error page, but maybe the server set up a white page for this.
My guess is that setting username and password directly in the URL doesn't work, you shouldn't do that anyways, but instead rely on WKWebView's webView:didReceiveAuthenticationChallenge: delegate method.
Here's some sample code hopefully working/helping:
#import "ViewController.h"
#import WebKit;
#interface ViewController () <WKNavigationDelegate>
#property (nonatomic, strong) WKWebView *webView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:[WKWebViewConfiguration new]];
self.webView.navigationDelegate = self;
[self.view addSubview:self.webView];
[self.webView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[_webView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(_webView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[_webView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(_webView)]];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSURL *target = [NSURL URLWithString:#"http://yourhost.com/possiblePage.html"];
NSURLRequest *request = [NSURLRequest requestWithURL:target];
[self.webView loadRequest:request];
}
- (void)webView:(WKWebView *)webView
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
NSURLCredential *creds = [[NSURLCredential alloc] initWithUser:#"username"
password:#"password"
persistence:NSURLCredentialPersistenceForSession];
completionHandler(NSURLSessionAuthChallengeUseCredential, creds);
}
#end
This is basically the implementation file of a simple ViewController (like from the single view template of XCode). It also shows you how you can add a WKWebView. Definitely make sure to check out all the delegate methods and such so you know what the thing can do for you.
Obviously, password and username have to be set somehow, I guess you can use a simple alert popup to have the user enter this info (this would be similar to Safari in principle). For the first test you can just hardcode it. Also note I set a sample subpage there, just use the exact same URL you would usually use on a desktop browser. Oh, and since the server doesn't have SSL, you need to allow arbitrary loads.
Edit:
RPM gave a good related comment below (thanks) that I had not originally thought about. The method may (actually will very likely) be called multiple times. This ultimately also depends on the website you load. RPM's explanation for why a site may appear plain white is spot on.
In any way, the webView:didReceiveAuthenticationChallenge:completionHandler: method above is just a simple example assuming you know the PW and username. Generally it will be more complex and you shouldn't just open an input dialog every time it is called for the user to enter credentials. As a matter of fact, the provided challenge offers ways to set a specific call to this delegate method into relation to previous calls. For example, it has a proposedCredential property that may already have been set. (Whether that's the case for loading multiple resources I don't know on the top of my head, just try that out.) Also, check its previousFailureCount, etc. A lot of this may depend on the site you load and what it needs to get.

Error logging into Instagram in iOS app using UIWebView

I'm following Instagram authentication [recommended] steps using UIWebView on an iOS app.
After entering credentials, hitting login loads a page with following error.
This page could not be loaded. If you have cookies disabled in your
browser, or you are browsing in private mode, please try enabling
cookies or turning off private mode, and then retrying your action.
And, this only happens on first run through the steps of authentication; on the next attempt, everything works smooth as silk. I get the code suffixed to redirect url and I request access token using it.
Screenshot:
There's already another question here and it doesn't help.
EDIT:
It seems like Cookies issue. Though, I haven't been able to fix it yet.
I had a similar problem when I was deleting cookies to make sure the log in screen appeared and not just using the currently logged in user. Try (swift):
let storage = HTTPCookieStorage.shared
storage.cookieAcceptPolicy = .always
I managed to solve this problem about an hour ago.
First, DON'T use UIWebView or WebView; use a WKWebView instead.
You see, you need to implement the method
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
when instagram's login page forwards you to your redirect_url , it FAILS to navigate. Probably because we're not doing the server-side auth, but only the client-side auth, and the redirect_url is not a valid url.
We're expecting the page to REDIRECT to an invalid url, but when that happens, the WKWebView doesn't call the method
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
or the method
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
So. What you need to do is focus on that first method, check the error variable, and extract error.userInfo.
In my case, this is how I solved the problem:
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
{
NSURL *err = [error.userInfo objectForKey:#"NSErrorFailingURLKey"];
if([err.absoluteString hasPrefix:INSTA_REDIRECT_URL]){
NSString *token = [[err.absoluteString componentsSeparatedByString:#"#access_token="] objectAtIndex:1];
[Utils saveToken:token];
[self.webView setHidden:YES];
//open next view controller
}else{
//TODO: No internet? something else went wrong..
NSLog(#"Error: %#", error);
}
}
Hope it helps.
Best of luck! ;)

How to download a web page with it's inner resoures like images,css in iOS?

I'm trying to download the specified web pages with their inner page resources to disk.
When I send a NSURLRequest, only the initial URL would be request, but the resources as images,css,js would not be download.
Someone suggests to use cache, but I don't want to cache every web page browsed in the uiwebview, but only the one I specified.For example, when I input an url to the uiwebview addressbar then click on the "save" button, the page will be saved entire, but the other browsing in the webview will not be cached automatically.
Does there any way to download the entire webpages?
Thank you very much!
I ran in the same problem once, and I solved it using ASIWebPageRequest . It was old by the time, but still works.
I wrote a method (based on sample method) for download a webpage in a folder. You need to modify this to get it work.
- (IBAction)loadURL:(NSURL *)url inFolder:(NSString*)folderPath
{
// Assume request is a property of our controller
// First, we'll cancel any in-progress page load
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[[self request] setDelegate:nil];
[[self request] cancel];
[self setRequest:[ASIWebPageRequest requestWithURL:url]];
[[self request] setDelegate:self];
[[self request] setDidFailSelector:#selector(webPageFetchFailed:)];
[[self request] setDidFinishSelector:#selector(webPageFetchSucceeded:)];
// Tell the request to embed external resources directly in the page
[[self request] setUrlReplacementMode:ASIReplaceExternalResourcesWithData];
self.theCache.storagePath = folderPath;
// It is strongly recommended you use a download cache with ASIWebPageRequest
// When using a cache, external resources are automatically stored in the cache
// and can be pulled from the cache on subsequent page loads
self.request.cacheStoragePolicy = ASICachePermanentlyCacheStoragePolicy;
[[self request] setDownloadCache:self.theCache];
// Ask the download cache for a place to store the cached data
// This is the most efficient way for an ASIWebPageRequest to store a web page
//[[self request] setDownloadDestinationPath:
//[[ASIDownloadCache sharedCache] pathToStoreCachedResponseDataForRequest:[self request]]];
[[self request] setDownloadDestinationPath:[self.theCache pathToStoreCachedResponseDataForRequest:[self request]]];
[[self request] startAsynchronous];
}
I used ASIDownloadCache too.
...
ASIDownloadCache *localCache = [[ASIDownloadCache alloc] init];
self.theCache = localCache;
...
You could implement a custum NSUrlCache and save everything. Everything that is downloaded using a NSUrlRequest will use the NSUrlCache. Everything a UIWebView does uses the NSUrlRequest. If you need a sample, then have a look at https://github.com/evermeer/EVURLCache

Resources