How to manipulate iFrames in a WKWebView? - ios

I want to inject custom headers into NSURLRequests made for top-level frames of WKWebView and all of its iFrames.
To achieve this I am listening to the
- webView:decidePolicyForNavigationAction:decisionHandler: method. In this method i want to cancel the existing page load and create a new NSURLRequest and reload the page.
Here's the code snippet I am using,
- (void)webView:(WKWebView*)webView
decidePolicyForNavigationAction:(WKNavigationAction*)action
decisionHandler:
(void (^)(WKNavigationActionPolicy))decisionHandler {
NSString* header = #"Custom-Header";
if([action.request.allHTTPHeaderFields objectForKey:header] ){
NSLog(#"Header already added for URL - %#",action.request.URL.absoluteString );
}
else{
decisionHandler(WKNavigationActionPolicyCancel);
NSLog(#"Adding header for URL %#",action.request.URL.absoluteString);
NSMutableURLRequest *mutableRequest = [action.request mutableCopy];
NSString* headerVal;
if(action.targetFrame.mainFrame)
{
headerVal = #"MAINFRAME";
}
else
{
headerVal = #"SUBFRAME";
}
[mutableRequest addValue:headerVal forHTTPHeaderField:header];
[action.targetFrame.webView loadRequest:mutableRequest];
return;
}
// further processing
}
However with this approach I face the following problem - If the callback is received for a iFrame, the entire page reloads with the iFrame URL. Instead I want only the iFrame to reload with the URL. Is there any way to solve this?
Note that at the end of this I want to be able to send a custom header indicating whether the request originated from the main frame or an iFrame.Please do suggest if there are better solutions to achieve this.

Related

Can we use HTML code in SWIFT?

I have some attractive graph which is made in HTML and javascript.
So my questions are:
Can I use this work in my swift app,
Can we use javascript and html code to make graph in app?using webview
Can we pass values from swift/objective c code to javascript/html code and vice-versa?
If yes , How can we pass variable value to html/javascript and how to to accept this value in HTML
html text can be embedded into labels as NSAttributedString instances
HTML content that has scripts and stuff can be put into a web view container: UIWebView or WKWebView
YES. Create a webview then load this html page.
YES. (same as answer 1)
YES .
To send to html page: call [webView stringByEvaluatingJavaScriptFromString:function];
To send to swift/objective-c : handle shouldStartLoadWithRequest of your UIwebview.
-(BOOL) webView : (UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) request navigationType : (UIWebViewNavigationType)navigationType {
if ([[[request URL] absoluteString] hasPrefix:#"yourPrefixe:"]) {
//do your works
//then cancel current request
return NO;
}
return YES;
}

Can I create a link from within my webview browser that will call a specific function

We are using the native geolocation data in the address string we use when we create a webview component (eg http://example.com?long=36.333&latt=-143.222). The reason we are doing this is that we want to check location but don't want to use the browser location facility which pops up an alert each day asking if we can use your location.
Ideally I want to be able to create a new webview component (with new location data) in response to a click on a link within the webview component. I know we can call the app from a link (eg myappname://) but can I add parameters to this call to trigger certain events such as a function that we reload the webview component?
If it cant be done from a link within the html, I assume I will have to place a native button on the page and call it from there.
You can do. First of all, set link like http://example.com?#SetCustomLocation in your HTML content.
Set WebView Delegate method like bellow :
-(BOOL) webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType
{
if ( inType == UIWebViewNavigationTypeLinkClicked )
{
NSString *url = [NSString stringWithFormat:#"%#", [inRequest URL]];
if([url hasSuffix:#"#SetCustomLocation"])
{
NSString *strURL = #"http://example.com?long=36.333&latt=-143.222"; //Set Custom URL with Lat/long Whatever you need
NSURL *cURL = [NSURL URLWithString:[strURL tringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
[[UIApplication sharedApplication] openURL:cURL];
}
}
return YES;
}
Regards.

Can a UIWebView interact (communicate) with the app?

I went to use UIWebView to display dynamic content, instead of doing it natively using UI-elements. Is it possible to trigger native app functions from simply hitting links inside the UIWebView? Example: hitting a link which then switches current View?
Yes, it's possible. In your html, you write a JS to load a URL with a fake scheme such as
window.location = "request_for_action://anything/that/is/a/valid/url/can/go/here";
Then, in your iOS code, assign a delegate to your webView, and in your delegate, handle
webView:shouldLoadWithRequest:navigationType
with something like
if( [request.URL.scheme isEqualToString: #"request_for_action"] )
{
// parse your custom URL to extract parameter, use URL parts or query string as you like
return NO; // return NO, so webView won't actually try to load this fake request
}
--
Just an aside, you can do the other way, let iOS code invoke some JS codes in your html by
using
NSString* returnValue = [self.webView stringByEvaluatingJavaScriptFromString: "someJSFunction()"];
Yes! When the user presses a link, you hear about it in the web view's delegate and can then do whatever you want. Powerful stuff can be done this way.
The web view's delegate is sent webView:shouldStartLoadWithRequest:navigationType:. You analyze what happened, and respond as you wish. To prevent the web view from trying to follow the link (which may be completely fake, after all), just return NO.
In this example from the TidBITS News app, I have a link in the Web page that uses a totally made-up play: scheme. I detect that in the delegate and play:
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)r
navigationType:(UIWebViewNavigationType)nt {
if ([r.URL.scheme isEqualToString: #"play"]) {
[self doPlay:nil];
return NO;
}
if (nt == UIWebViewNavigationTypeLinkClicked) {
[[UIApplication sharedApplication] openURL:r.URL];
return NO;
}
return YES;
}
Implement the UIWebViewDelegate method webView:shouldStartLoadWithRequest:navigationType:.
Handle navigationType and the request as needed.

Objective-C calling javascript in UIWebView calling back Objective-C; threading issues

I have a UIWebView-based application that is storing some state. I have a native tab bar and upper bar however. What I need is that when I click on the native bar, I persist the data stored in the UIWebView.
To do this, I called evaluateJavaScriptByString to create a JSON object and it does a callback to objective-C via the UIWebViewDelegate protocol.
However, I find that the callback is asynchronous and hence my main transaction happens before the data is loaded.
Any idea how this problem can be solved?
Update: To explain the problem better:-
I have an HTML page with a form that someone puts some data into.
My Tab bar is native.
On the click of a button on the tab bar, I want the data from HTML saved to an Objective-C model, and the view should change to some other view.
What is happening is that once I click the button, the javascript call happens to the page (to create a JSON string to send to Objective-C) and this data does get saved to the obj-c model. However, this happens asynchronously. So my screen changes before the data is loaded into the model. If I refresh the next screen it shows the correct data. I was wondering if there was a way around this.
Note, I know how to call OBjective-C functions from WebViews. We are using JSOBjBridge for that anyway.
Just add something like this #"some_var = MAKE_JSON(); window.location = \"myapp://callback/\" + escape(some_var)" at the end of your javascript code, that you transfer to evaluateJavaScriptByString:
In UIWebView's delegate implement webView:shouldStartLoadWithRequest:navigationType: and catch all urls with myapp:// prefix like this:
- (BOOL) webView: (UIWebView *) webView
shouldStartLoadWithRequest: (NSURLRequest *) request
navigationType: (UIWebViewNavigationType) navigationType {
NSString *url = #"myapp://";
NSString *path = request.mainDocumentURL.relativePath;
NSString *callbackURL = #"callback/";
if ( [path hasPrefix:url] ) {
path = [path substringFromIndex:[url length]];
if ( [relPath hasPrefix:callbackURL] ) {
NSString *json = [path substringFromIndex:[callbackURL length]];
//TODO: Work with json
}
return NO;
}
return [super webView:webView shouldStartLoadWithRequest:request navigationType: navigationType];
}
I didn't test this code, I just written it from scratch, but it should work, I already did it in this way.

How To Clear A UIWebView

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))
}

Resources