Why is WKWebView not opening a telephone link in ios 9? - ios

I have a WKWebView that loads a webpage with some telephone links on the webpage.
Currently i have this code to handle clicks to those links.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.request.url?.scheme == "tel" {
if #available(iOS 10.0, *) {
UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(navigationAction.request.url!)
}
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
This works fine on any device with ios 10 installed, i am prompted with an alert box asking to either cancel or make a call. but on ios 9 devices the telephone app screen flashes (no prompt) and it nothing happens after.

Key concepts before proceed :
By default, a web view automatically converts telephone numbers that appear in web content to Phone links. When a Phone link is tapped, the Phone app launches and dials the number. The tel URL scheme is used to launch the Phone app on iOS devices and initiate dialing of the specified phone number.
from the html side the web page showing phone numbers must be like this :
<body>
<!-- Then use phone links to explicitly create a link. -->
<p>A phone number: 1-408-555-5555</p>
<!-- Otherwise, numbers that look like phone numbers are not links. -->
<p>Not a phone number: 408-555-5555</p>
</body>
After this short preamble, theres two behaviour for WKWebView , one for iOS 9 and other for iOS 10 and later.
on iOS 10 Apple introduced a property called dataDetectorTypes on WKWebViewConfiguration and by default this property have the value WKDataDetectorTypeNone wich means that does not detect nothing. So in the case you want to detect phone numbers and links (just an example) the configuration for WKWebView would look like this (suppose you have a variable _webView of type WKWebView):
WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
if ([configuration respondsToSelector:#selector(dataDetectorTypes)]) {
configuration.dataDetectorTypes = UIDataDetectorTypeLink | UIDataDetectorTypePhoneNumber;
}
_webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
with this configuration your webView will be able to handle everything under the hood.
Now on 9 > iOS < 10 (iOS between 9 and 10) your delegate decision handler would look like this :
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if ([navigationAction.request.URL.scheme isEqualToString:#"tel"]){
[UIApplication.sharedApplication openURL:navigationAction.request.URL];
decisionHandler(WKNavigationActionPolicyCancel);
}else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
and this will open the phone app. Keep in mind that:
iOS 10.3 and later displays an alert and requires user confirmation
before dialing. (When this scenario occurs in versions of iOS prior to
10.3, iOS initiates dialing without further prompting the user and does not display an alert, although a native app can be configured to
display its own alert.
Please do not follow any instruction talking about URL scheme whitelist , this is another story that apple introduce on iOS 9, but this is when you need to declare url scheme handlers, for now the tel scheme is handled by iOS.
Hope this help.

Swift 4:
webView.navigationDelegate = self
webView.configuration.dataDetectorTypes = [.link, .phoneNumber]
extension PDFWebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let requestUrl = navigationAction.request.url, requestUrl.scheme == "tel" {
UIApplication.shared.open(requestUrl, options: [:], completionHandler: nil)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}

Related

Select file button is not opening the files library in webView of iOS

I have select file button in website which is opening in webview in my iOS application but the button is not opening the files library of iOS device. but the same link is working fine for android and even in desktop browser
I just used the webView simply and tried some delegate methods to listen the click but I am not able to found any method to work in this situation.
if let myUrl: URL = URL(string: urlWeb){
webViewTerms.delegate = self
webViewTerms.loadRequest(URLRequest(url: myUrl))
}
I suppose you have the below code in your website to listen the click event.
Adding a href to your button event
"window.location.href = “ myapp://click”;"
then below code will work for you.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if((webView.url?.scheme == "myapp") && webView.url?.host == "click"){
print("user click the button")
// Write the code to open Files folder of device.
}
decisionHandler(WKNavigationActionPolicy.allow)
}
I hope this would work for you.

Opening external links in WKWebView (not externally)

My app contains HTML information pages opened in WKWebView, containing some hyperlinks. These used to work, but stopped working with recent iOS releases with error messages, containing words
Could not create sandbox extension
Connection to daemon was invalidated
When I apply WKNavigation delegate with the following code
extension InformationPageController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
var policy = WKNavigationActionPolicy.allow
if navigationAction.navigationType == .linkActivated {
// Neither of commented lines below works!
// webView.load(navigationAction.request)
// webView.loadHTMLString(navigationAction.request.url!.absoluteString, baseURL: nil)
UIApplication.shared.openURL(navigationAction.request.url!)
policy = .cancel
}
decisionHandler(policy)
}
}
the pages are opened in an external browser, but not inside the app.
Is there a way to open the links as before, within same WKWebView?
It looks like only https protocol is currently accepted by WKWebView. If you have just http, WKWebView will complain about insecure protocol and won't open.
After I changed http-s to https-s, the pages opened, but not AppStore pages. This leads to another issue.
When URL address starts with https://apps.apple.com/..., iOS (but not macOS) redirects it to itms-appss://apps.apple.com/... for opening in AppStore app, causing WKWebView to come up with the error: Redirection to URL with a scheme that is not HTTP(S). Similar problem with mailto urls.
To bypass these issues I modified code to the following:
extension InformationPageController: WKNavigationDelegate {
static let disallowedUrlPrefixes = ["https://apps.apple.com", "mailto:"]
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
var policy = WKNavigationActionPolicy.allow
if navigationAction.navigationType == .linkActivated,
let url = navigationAction.request.url {
let urlString = url.absoluteString
if InformationPageController.disallowedUrlPrefixes.first(where:{urlString.starts(with: $0)}) != nil {
UIApplication.shared.openURL(url)
policy = .cancel
}
}
decisionHandler(policy)
}
}
Now the 'troublesome' pages are opened with an external browser, all others are opened within WKWebView. Unfortunately that's the best I can currently think of.
Perhaps it's because an iOS app by default doesn't allow http requests. Try to allow it by adding "App Transport Security Settings" and add a sub key "Allow Arbitrary Loads" and set it to true in the app's info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

IOS - WKWebview gets null url in decidePolicy when second clicking the same button

I have a WKWebview with a button that downloads a pdf, the button link is as follows:
<img class= src="$imageurl">
I have managed to download the PDF through certain logic, that has a starting point in decidePolicyFor method:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.url
if openInDocumentPreview(url!) {
decisionHandler(.cancel)
executeDocumentDownloadScript(webView, forAbsoluteUrl: url!.absoluteString)
} else {
decisionHandler(.allow)
}
}
This works well, and the url is the one with the PDF. However, if I click the button again I only receive null urls, so I cannot open the document again. This is the action I receive:
<WKNavigationAction: 0x102d95fa0; navigationType = -1; syntheticClickType = 0; position x = 0.00 y = 0.00 request = <NSMutableURLRequest: 0x282e151d0> { URL: about:blank }; sourceFrame = <WKFrameInfo: 0x102d502f0; webView = 0x103048200; isMainFrame = YES; request = <NSMutableURLRequest: 0x282e116d0> { URL: https://mysite.appiancloud.com/suite/sites/home }>; targetFrame = <WKFrameInfo: 0x102d577a0; webView = 0x103048200; isMainFrame = NO; request = (null)>>
If I test this on Safari Desktop the file is downloaded ok every time, however I don't find the reason why WKWebView stops receiving the url. No other delegate method is called, not even the createWebViewWith which tends to be called when target="_blank" urls are invoked.
I wonder if WKWebview cachePolicy is taking effect, but I don't find the way to avoid that url caching, or if it is being cached, to receive the event on when it is trying to load it again.
Also, as a note, if I long-click the link, the preview element holds the URL correctly.
Ok, my theory is that by telling the handler that WKNavigationAction policy is .cancel, following requests won't be processed.
What I did is to process the URL download in the createWebViewWith method, and let the policy to be always .allow.

Itunes app links are not working with WkWebview

One of the webpage I load into Wkwebview has the following iTunes app link
https://itunes.apple.com/gb/app/xx-yy-zz/id435919263?mt=8
when it's opened I'm getting the following alert
and here's the error that I've got.
{
"[errorCode]" = 0;
"[errorDescription]" = "Redirection to URL with a scheme that is not HTTP(S)";
"[errordetail]" = "Con:myappxxxx:myorder:webview:networkerror";
"[localizedRecoverySuggestion]" = "";
"[url]" = "itms-appss://apps.apple.com/gb/app/xx-yy-zz/id435919263";
}
When the same iTunes link ( https://itunes.apple.com/gb/app/xx-yy-zz/id435919263?mt=8 )
is opened in UIWebview , I saw that URL gets redirected to following URL and app opens in appstore
itms-appss://itunes.apple.com/gb/app/xx-yy-zz/id435919263?mt=8
Whereas in Wkwebview , the URL gets redirected to following URL
itms-appss://apps.apple.com/gb/app/xx-yy-zz/id435919263
Any help is appreciated
Update
I even tried Arbitrary uploads to true for transport security and the problem is still there.
Error Domain= Code=0 "Redirection to URL with a scheme that is not
HTTP(S)"
UserInfo={_WKRecoveryAttempterErrorKey=,
NSErrorFailingURLStringKey=itms-appss://apps.apple.com/gb/app/xx-yy-zz/id435919263,
NSErrorFailingURLKey=itms-appss://apps.apple.com/gb/app/xx-yy-zz/id435919263,
NSLocalizedDescription=Redirection to URL with a scheme that is not
HTTP(S)}
I think you could try to intercept the itunes link in wkwebview's delegate methods and open the URL using openURL
The below source code will open any itms-appss links in wkwebview. Don't forget to conform to WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if ([webURL.scheme isEqualToString:#"itms-appss"])
{
UIApplication *app = [UIApplication sharedApplication];
if ([app canOpenURL:webURL])
{
[self.webviewObj stopLoading];
[app openURL:[NSURL URLWithString:[webURL absoluteString]]];
decisionHandler(WKNavigationActionPolicyCancel);
} else{
decisionHandler(WKNavigationActionPolicyCancel);
}
}
else
{
decisionHandler(WKNavigationActionPolicyAllow);
}
return;
}
WKWebView seems not to handle non-http(s) url schemas by default.
So, you have to catch the request using webView(_:decidePolicyFor:decisionHandler:), and check the url that can be loaded by WKWebView.
If the url is non-http(s) url, then you should open the url by open(_:options:completionHandler:).
Here the sample code.
Assign the navigationDelegate for your WKWebViewinstance.
webView.navigationDelegate = self
Implement WKNavigationDelegate method.
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
// if the url is not http(s) schema, then the UIApplication open the url
if let url = navigationAction.request.url,
!url.absoluteString.hasPrefix("http://"),
!url.absoluteString.hasPrefix("https://"),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
// cancel the request
decisionHandler(.cancel)
} else {
// allow the request
decisionHandler(.allow)
}
}
Try in your info.plist adding:
App transport Security settings
-- Allow Arbitrary loads = yes
<key>NSAppTransportSecurity</key>
<dict>
<!--Connect to anything (this is probably BAD)-->
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

Is there a way to determined what element is at a point in a WKWebView or SFSafariViewController view?

I would like to know what element is "under" a particular point in a web view. For example, in the screenshot below, I would like to know what element is at point 550x, 275y. I'm hoping to get the result:
<img alt=​"Typhoon Mangkhut approaching the Philippines on September 14" src=​"/​/​upload.wikimedia.org/​wikipedia/​commons/​thumb/​3/​30/​Mangkhut_2018-09-14_0750Z.jpg/​140px-Mangkhut_2018-09-14_0750Z.jpg" width=​"140" height=​"111" srcset=​"/​/​upload.wikimedia.org/​wikipedia/​commons/​thumb/​3/​30/​Mangkhut_2018-09-14_0750Z.jpg/​210px-Mangkhut_2018-09-14_0750Z.jpg 1.5x, /​/​upload.wikimedia.org/​wikipedia/​commons/​thumb/​3/​30/​Mangkhut_2018-09-14_0750Z.jpg/​280px-Mangkhut_2018-09-14_0750Z.jpg 2x" data-file-width=​"1219" data-file-height=​"963">​
If the webview supports it, you could have javascript determine what's there, and report it to the webview. I apologize but I haven't run this code, I don't have the means to test it at the moment.
If you don't have the ability to add javascript to the page then you'll have to do it all in WKWebView .evaluateJavaScript
find the element value with:
var elem = document.elementFromPoint(550, 275);
Then tell your webview about it by creating a custom scheme request. you can do it this way:
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "customScheme://" + JSON.stringify(elem);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
Intercept it in the webview delegate.
public func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.url
if(url.range(of:"customScheme://") != nil){
// this is your reported info
}
return true
}
This approach will only tell you the element as it relates to the web browser coordinates, not it's position on the device's screen. If you want to do it via device position you'll have to translate that and pass the coordinates over to the webview by invoking some javascript on the device.

Resources