I am working on an app which involves pulling course information from student's portal. On the user's side:
Student is brought to a webview showing the login page from university's website. (https://acorn.utoronto.ca/)
Student puts in the credentials (Utorid and password), clicks login, webview is closed, and student is brought back to the app and will see his/her courses nicely imported and arranged.
Meanwhile, the app should:
Go into user's personal portal page upon successful login and take the user back to app while process the following in background
Click on "Enrol & Manage" in the left sidebar (not sure if it is necessary, but when I view the source on my browser upon login, sometimes XHRs folder doesn't show unless click on that link)
Go to XHRs folder and find the json "enrolledCourses" (https://acorn.utoronto.ca/sws/rest/dashboard/courseRegistration/enrolledCourses)
Retrieve and parse the user's course info and make them available for the app
Source view when student just logged in
Source view when student click on "Enrol & Manage" in the left sidebar, as you can see now "XHRs" is displayed
The file "enrolledCourses" in "XHRs" which contains the course information
Here's my code for the ViewController so far:
import UIKit
import HTMLKit
import WebKit
class UtoridViewController: UIViewController, WKNavigationDelegate {
private let webView: WKWebView = {
let webView_result = WKWebView(frame: .zero)
return webView_result
}()
override func viewDidLoad() {
print("Acorn loading")
super.viewDidLoad()
view.addSubview(webView)
webView.frame = view.bounds
webView.navigationDelegate = self
// Covert URL_string into URL
guard let url = URL(string: Constants.ACORN_URL) else {
return
}
webView.load(URLRequest(url: url))
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
}
}
So far, I can get the user to the website, but I have no idea how to proceed...
Any help or guidance will be appreciated! Thanks in advance!!!
its impossible, data on another process...
why don't you want to send an http request to the server and get the required data? if I'm not mistaken, you can take cookies from the webView and use them already for the request
there is of course an option through getting html code or executing arbitrary JS code, but this is a very unstable solution
While using web view to convert my webpage to an app using Xcode the login button does not return to the designated page after login it kind of stays stuck in the login page(The behaviours works okay in a browser though)
Similar behaviour has been observed whoever there is a return to a different page
This is for making my website accessible as an iOS app using web view
I tried using this code snippet:
var backNavigation: WKNavigation?
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if backNavigation?.isEqual(navigation) ?? false {
webView.reload()
backNavigation = nil}
The idea is to allow the application get redirected to the correct page as in the website after login or say cancellation. (The functionality works ok on the website)
I think the solution might be to Allow Arbitrary Loads to yes under app transport security settings. kindly check the link below:
Transport security has blocked a cleartext HTTP
try adding this code
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
let webview = WKWebView(frame: .zero, configuration: configuration)
Now i'm using UIWebView and with canInitWithRequest: of NSURLProtocol i can intercept all requests and do with it what I want.
In the new WKWebView this method there isn't, and i not found something similar.
Has someone resolved this problem?
I see that after 5 years this question still generates curiosity, so I describe how I solved it and about some main problems I faced up.
As many who answered here, I have implemented WKURLSchemeHandler and used new schemes.
First of all the URL that wkwebview launches must not be HTTP (or HTTPS) but one of yours new schemes.
Example
mynewscheme://your-server-application.com
In you WKWebViewConfiguration conf, I set the handler:
[conf setURLSchemeHandler:[CustomSchemeHandler new] forURLScheme:#"mynewscheme"];
[conf setURLSchemeHandler:[CustomSchemeHandler new] forURLScheme:#"mynewschemesecure"];
In CustomSchemeHandler I have implemented webView:startURLSchemeTask: and webView:stopURLSchemeTask:.
In my case I check if the request is for a file that I just saved locally, otherwise I change actual protocol ("mynewscheme or "mynewschemesecure") with http (or https) and I make request by myself.
At this point I solved the "interception problem".
In this new way we have the webview "location" (location.href via javascript) with my new scheme and with it new problems started.
First problem is that my applications work mainly with javascript,
and document.cookie has stopped working. I'm using Cordova
framework, so I've develeped a plugin to set and get cookie to
replace document.cookie (I had to do this, because, obviously, I
have also http header set-cookie).
Second problem is that I've got a lot of "cross-origin" problems, then
I changed all my urls in relative url (or with new schemes)
Third problem is that browser automatically handle server port 80
and 443, omitting them, but has now stopped (maybe because of "not
http location"). In my server code I had to handle this.
Writing down these few rows I admit that it seems to was an easy problem to solve, but I ensure that find out a workaround, how to solve it and integrate with the infinite amount of code has been hard. Every step towards the solution corresponded to a new problem.
You can intercept requests on WKWebView since iOS 8.0 by implementing the decidePolicyFor: navigationAction: method for the WKNavigationDelegate
func webView(_ webView: WKWebView, decidePolicyFor
navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
//link to intercept www.example.com
// navigation types: linkActivated, formSubmitted,
// backForward, reload, formResubmitted, other
if navigationAction.navigationType == .linkActivated {
if navigationAction.request.url!.absoluteString == "http://www.example.com" {
//do stuff
//this tells the webview to cancel the request
decisionHandler(.cancel)
return
}
}
//this tells the webview to allow the request
decisionHandler(.allow)
}
there are many ways to implement intercepter request.
setup a local proxy, use WKNavigationDelegate
-[ViewController webView:decidePolicyForNavigationAction:decisionHandler:]
load new request and forward to local server, and at the local server side you can do something(eg. cache).
private api. use WKBrowsingContextController and custom URLProtocol
Class cls = NSClassFromString(#"WKBrowsingContextController");
SEL sel = NSSelectorFromString(#"registerSchemeForCustomProtocol:");
if ([(id)cls respondsToSelector:sel]) {
// 把 http 和 https 请求交给 NSURLProtocol 处理
[(id)cls performSelector:sel withObject:#"http"];
[(id)cls performSelector:sel withObject:#"https"];
}
use KVO to get system http/https handler.(ios 11, *)
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
URLSchemeHandler *schemeHandler = [[URLSchemeHandler alloc] init];
[configuration setURLSchemeHandler:schemeHandler forURLScheme:#"test"];
NSMutableDictionary *handlers = [configuration valueForKey:#"_urlSchemeHandlers"];
handlers[#"http"] = schemeHandler;
handlers[#"https"] = schemeHandler;
all the three ways you probably need handle CORS & post bodies to be stripped,you overwrite change browser`s option request(
Preflighted_requests),you can custom http header to replace http body for post bodies to be stripped.
in iOS 11 WKWebView has come up with Custom Scheme Handler called WKURLSchemeHandler, which you can use to intercept the custom events.
for more info check out this project.
https://github.com/BKRApps/KRWebView
I know I am late but I am able to solve this problem. I can intercept each and every request even your http/https call using below trick. I can also trace the call made from html to server calls. I can also use this to render html with offline content.
Download the html of the website that we want to render in offline or online to intercept the request.
Either place the html in document directory of the user or place it inside the archive. But we should know the path of the html file.
Place all your js, cs, woff, font of our website at the same level as our base html. We need to given permission while loading the web view.
Then we have to register our own custom handler scheme with WKWebView. When wkwebview see the pattern "myhandler-webview" then it will give you control and you will get the callback to 'func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask)' delegate implementation. You can play around with url in this delegate like mentioned in point 6.
let configuration = WKWebViewConfiguration();
configuration.setURLSchemeHandler(self, forURLScheme: "myhandler-webview");
webView = WKWebView(frame: view.bounds, configuration: configuration);
Convert file scheme to the custom scheme (myhandler-webview) then load it with WKWebView
let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
var htmlURL = URL(fileURLWithPath: htmlPath!, isDirectory: false)
htmlURL = self.changeURLScheme(newScheme: "myhandler-webview", forURL: htmlURL)
self.webView.load(URLRequest(url: htmlURL))
Implement below methods of WKURLSchemeHandler protocol and handle didReceiveResponse, didReceiveData, didFinish delegate methods of WKURLSchemeTask.
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
print("Function: \(#function), line: \(#line)")
print("==> \(urlSchemeTask.request.url?.absoluteString ?? "")\n")
// You can find the url pattern by using urlSchemeTask.request.url. and create NSData from your local resource and send the data using 3 delegate method like done below.
// You can also call server api from this native code and return the data to the task.
// You can also cache the data coming from server and use it during offline access of this html.
// When you are returning html the the mime type should be 'text/html'. When you are trying to return Json data then we should change the mime type to 'application/json'.
// For returning json data you need to return NSHTTPURLResponse which has base classs of NSURLResponse with status code 200.
// Handle WKURLSchemeTask delegate methods
let url = changeURLScheme(newScheme: "file", forURL: urlSchemeTask.request.url!)
do {
let data = try Data(contentsOf: url)
urlSchemeTask.didReceive(URLResponse(url: urlSchemeTask.request.url!, mimeType: "text/html", expectedContentLength: data.count, textEncodingName: nil))
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
} catch {
print("Unexpected error when get data from URL: \(url)")
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
print("Function: \(#function), line: \(#line)")
print("==> \(urlSchemeTask.request.url?.absoluteString ?? "")\n")
}
Let me know if this explanation is not enough.
Objective c example mentioned below
intercepting request with wkwebview
One can use WKURLSchemeHandler to intercept each and every request to be loaded in WKWebView,
Only disadvantage is that you cannot register http or https scheme for interception,
Solution over that is,
Replace your http/https scheme of url with custom scheme url like xyz://
for e.g. https://google.com can be loaded like xyz://google.com
Now you will get a callback in WKURLSchemeHandler there you again replace it back to https and load data programmatically and call urlSchemeTask.didReceive(response)
This way each and every https request will come to your handler.
I am blindly taking guesses since I only have my windows computer with me. By reading the Apple Developer documentation here is information I gathered that might lead to some ideas on how to solve the question.
Based on WKWebView,
Set the delegate property to an object conforming to the WKUIDelegate protocol to track the loading of web content.
Also, I see we can set our navigationDelegate with the,
weak var navigationDelegate: WKNavigationDelegate? { get set }
The methods of the WKNavigationDelegate protocol help you implement custom behaviors that are triggered during a web view's process of accepting, loading, and completing a navigation request.
Then after we create and set our custom WKNavigationDelegate, we would override some methods to intercept something we might be looking for. I found the Responding to Server Actions section of some interest since they receive a WKNavigation as parameter. Moreover, you might want to skim through WKNavigationAction and WKNavigationResponse see if there is perhaps something which might help us achieve our goal.
BTW, I am just giving some ideas on what to try so that we can solve this question, ideas which might be 100% wrong cause I have not tried them myself.
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.
I am creating an app that would first make people login through a UIWebView to access the rest of the content of the app. More specifically they would be logging in through a web email server set through the company I work with. I want to know if it's first possible to do this, and if so how to confirm whether the user log in was successful?
For example I have the following code:
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://exchsvr02.companyname.com/owa/")
let request = NSURLRequest(URL: url!)
webView.loadRequest(request)
// user entered valid credentials in the login screen
// so push next page
// invalid credentials, ask user to retry, do not push screen
}