WKWebview won't load certain websites properly - ios

I need to load a wide assortment of website URLS (which come from a webservice) into a WKWebview.
Some websites, behave quite well, and load properly just by replacing "http" for "https" in the URL:
http://www.yahoo.com/news -> loads blank
**https://www.yahoo.com/news -> loads OK!!**
URLs without the https:// prefix will NOT load at all.
http://www.google.com -> blank
**https://www.google.com -> Loads OK!!!**
http://www.bbc.co.uk/news (no .plist Exception) -> blank
https://www.bbc.co.uk/news (no .plist Exception) -> blank
http://www.bbc.co.uk/news (WITH .plist Exception) -> blank
https://www.bbc.co.uk/news (WITH .plist Exception) -> blank
(I pasted https://www.bbc.co.uk/news on MAC OS browser, and got redirected to https://www.bbc.co.uk/news, so I tried them as well:)
https://www.bbc.com/news (WITH .plist Exception) -> Images and text are loaded in a messy, unformatted, basic way.
http://www.bbc.com/news (WITH .plist Exception) -> Images and text are loaded in a messy, unformatted, basic way.
https://www.bbc.com/news (WITHOUT .plist Exception) -> blank
http://www.bbc.com/news (WITHOUT .plist Exception) -> blank
Other urls behave just like the BBC, or, Yahoo examples above.
This is the sort of exception I use on the .plist file.
This is my code:
- (void)viewDidLoad{
[super viewDidLoad];
CGRect wkFrame = _wios10container.frame;
NSLog(#"WPVC - viewDidLoad, wkFrame: %#",NSStringFromCGRect(wkFrame));
WKWebViewConfiguration *wkConfiguration = [[WKWebViewConfiguration alloc] init];
_webviewIOS10 = [[WKWebView alloc] initWithFrame:_wios10container.frame configuration:wkConfiguration];
_webviewIOS10.navigationDelegate = self;
[_wios10container addSubview:_webviewIOS10];
}
My questions are:
How can I properly load websites such as the BBC?
How can I prepare my app to load any URL without needing a huge list
of exceptions?
The app works fine if I set NSAppTransportSecurity.NSAllowsArbitraryLoads to YES, but I dont like that at all.
What alternative to that do I have for the sake of Security and App Store approval?

A more robust way to do this would be using the WKNavigationDelegate and in your case evaluate the URL. To handle with a huge list (without no more knowledge about that list) I would do a regular expression evaluation to perform based on it. Also you could create a redirection policy based on your needs.
For instance:
class WebNavigationHandler: NSObject, WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
//Based on the navigation action decide what to do.
if canINavigate {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
}
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Swift.Void) {
// Base on the navigationResponse
if canINavigate {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
}
}
}
let webview = WKWebView()
webview.navigationDelegate = WebNavigationHandler()
Hope it answer your question.

I think you're looking for the NSAllowArbitraryLoadsInWebContent setting for NSAppTransportSecurity. See this Apple document: https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html

After much trial and error, this is the formula that worked for me:
Do NOT replace http: for https: in the URL string
Add individual domain exceptions for each domain you want to
support. My app requires to display only 8 websites, so this
combination works fine!:
<key>bbc.co.uk</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
<false/>
<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
<true/>
<key>NSThirdPartyExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSRequiresCertificateTransparency</key>
<false/>
</dict>
Optional, the former also applies for this other approach, loading the URL on a SFSafariViewController. I will settle for this approach, which is quite reliable and even has back, next, refresh, and share buttons in a built in nav. bar. Here is the code:
NSURL *wkURL = [NSURL URLWithString:wkURLstring];
SFSafariViewController *safariVC = [[SFSafariViewController alloc] initWithURL:wkURL];
[self.view.window.rootViewController presentViewController:safariVC animated:YES completion:^{
NSLog(#"Full Safari Webview Launched");
}];

Related

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>

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>

How do I keep a WKWebView object from crashing?

Scenario
I'm building an iOS application in Swift. One feature is to have a live video feed as the application background. The video feed is originating from a Raspberry Pi on a local network using sudo motion. Motion is successfully hosting the feed on default port 8081.
The Swift app has a WKWebView object with the source pointing to my Raspberry Pi's motion port.
Suspected Issue
The webpage at port 8081 is constantly refreshing to load the most recent frame from the camera.
Problem
When running the app, the feed connects successfully and loads the first frame, and occasionally a second but then cuts off.
On a few occasions I received the following error in the terminal: [ProcessSuspension] 0x282022a80 - ProcessAssertion() Unable to acquire assertion for process with PID 0 leading me to believe that it is a memory management issue related to the constantly refreshing nature of the webpage.
Current Configuration
Currently, my call to .load() the WKWebView object is in ViewController.swift > override func viewDidLoad().
Proposed Resolution
Do I need to build some form of loop structure where I load a frame, pause execution and then call the WKWebView to reload a new frame a few seconds later.
I'm very new to Swift so patience with my question format is highly appreciated.
The WkWebView and motion loading worked in Xcode 9 with iOS 11 versions but doesn't seem to work anymore with iOS 12. You are correct that the webkit is crashing on the second image.
Due to you being new to Swift I would advise reading this link on delegates because this solution I am providing will make more sense to you.
Swift Delegates
In summary, "Delegates are a design pattern that allows one object to send messages to another object when a specific event happens."
With this solution/hack we are going to use several of the WKNavigationDelegates to inform us when the WkWebView is doing specific tasks and inject our solution to the problem. You can find out all of the delegates the WKWebKit has here WKNavigationDelegates.
The following code can be used in a brand new iOS project and replace the code in the ViewController.swift. It requires no interface builder or IBOutlet connections. It will create a single web view on the view and point to the address 192.168.2.75:6789. I have included inline comments to attempt to explain what the code is doing.
We are loading the HTTP Response twice from motion in decidePolicyFor navigationResponse delegate and keeping track with a counter. I have left some print statements so you can see what the response is. The first is a header and the second is the image information.
When our counter gets to 3 items (ie the second image) we are forcing the wkWebView to cancel all navigation (ie stop loading) in the decidePolicyFor navigationResponse delegate. See the line with decisionHandler(.cancel). This is what is stopping the crash.
This leads us to receive the callback from the wkwebview delegate WebView didFail navigation. At this point we want load our motion/pi url again and start the loading process again.
We must then reset our counter so we can repeat this process until someone else comes up with a better solution.
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
// Memeber variables
var m_responseCount = 0; /* Counter to keep track of how many loads the webview has done.
this is a complete hack to get around the webkit crashing on
the second image load */
let m_urlRequest = URLRequest(url: URL(string: "http://192.168.2.75:6789")!) //Enter your pi ip:motionPort
var m_webView:WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
m_webView = WKWebView(frame: self.view.frame) // Create our webview the same size as the viewcontroller
m_webView.navigationDelegate = self // Subscribe to the webview navigation delegate
}
override func viewDidAppear(_ animated: Bool) {
m_webView.load(m_urlRequest) // Load our first request
self.view.addSubview(m_webView) // Add our webview to the view controller view so we can see it
}
// MARK: - WKNavigation Delegates
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
print("decidePolicyFor navigationAction")
print(navigationAction.request) //This is the request to connect to the motion/pi server http::/192.168.2.75:6789
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
print("decidePolicyFor navigationResponse")
print(navigationResponse.response) // This is HTML from the motion/rpi
/* We only want to load the html header and the first image
Loading the second image is causing the crash
m_responseCount = 0 - Header
m_responseCount = 1 - First Image
m_responseCount >= 2 - Second Image
*/
if(m_responseCount < 2)
{
decisionHandler(.allow)
}
else{
decisionHandler(.cancel) // This leads to webView::didFail Navigation Delegate to be called
}
m_responseCount += 1; // Incriment our counter
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
/*
We have forced this failure in webView decidePolicyFor navigationResponse
by setting decisionHandler(.cancel)
*/
print("didFail navigation")
m_webView.load(m_urlRequest) //Lets load our webview again
m_responseCount = 0 /* We need to reset our counter so we can load the next header and image again
repeating the process forever
*/
}
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
// If you find your wkwebview is still crashing break here for
// a stack trace
print("webViewWebContentProcessDidTerminate")
}
}
Note: You are also required to add the following to your info.plist file due to the motion/pi server response being http and not https
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
I encourage you to use this basic example and modify it to suit your application requirements. I also encourage you to post any findings of your own because I am having the exact same problem using the exact same hardware as you and this is a hack more than a solution.

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

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

Unsupported URL error clicking a Whatsapp scheme with an URL in a iOS WKWebView

I'm using iOS 9.3.3 and clicking a Whatsapp link in a website displayed in a WKWebView.
Whenever I try to send a message to the whatsapp url scheme with a URL that includes the http:// or https:// part of the link as part of the message I get an "Unsupported URL" error.
[self.webView1 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"whatsapp://send?text=this%20is%20a%20test%20http://https://www.usa-brands.net/collections/new-arrivals/products/dacey-cap-sleeve-drop-waist-wool-sweater-dress"]]];
When I take out the http:// or https:// it works but now I don't get a Rich Preview (https://www.macstories.net/ios/whatsapp-adds-rich-previews-for-web-links/).
I've tried encoding the url but this doesn't work either. When I remove all other potential problems it comes down to the protocol in the url link.
The full error message is:
Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={_WKRecoveryAttempterErrorKey=, NSErrorFailingURLStringKey=whatsapp://send?text=Check%20this%20out%20'Dacey'%20Cap%20Sleeve%20Drop%20Waist%20Wool%20Sweater%20Dress,%20348.00%20USD:%20https://www.usa-brands.net/products/dacey-cap-sleeve-drop-waist-wool-sweater-dress, NSErrorFailingURLKey=whatsapp://send?text=Check%20this%20out%20'Dacey'%20Cap%20Sleeve%20Drop%20Waist%20Wool%20Sweater%20Dress,%20348.00%20USD:%20https://www.usa-brands.net/products/dacey-cap-sleeve-drop-waist-wool-sweater-dress, NSUnderlyingError=0x137dd1380 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "unsupported URL" UserInfo={NSErrorFailingURLStringKey=whatsapp://send?text=Check%20this%20out%20'Dacey'%20Cap%20Sleeve%20Drop%20Waist%20Wool%20Sweater%20Dress,%20348.00%20USD:%20https://www.usa-brands.net/products/dacey-cap-sleeve-drop-waist-wool-sweater-dress, NSLocalizedDescription=unsupported URL, NSErrorFailingURLKey=whatsapp://send?text=Check%20this%20out%20'Dacey'%20Cap%20Sleeve%20Drop%20Waist%20Wool%20Sweater%20Dress,%20348.00%20USD:%20https://www.usa-brands.net/products/dacey-cap-sleeve-drop-waist-wool-sweater-dress}}, NSLocalizedDescription=unsupported URL}
Does anyone have any idea why this doesn't work. It works for Android.
You should implement the WKWebView's WKNavigationDelegate function:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (![url.scheme isEqualToString:#"http"] && ![url.scheme isEqualToString:#"https"]) {
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url];
} else {
NSLog(#"Sorry, you haven't install the %#", url.scheme);
}
decisionHandler(NO);
}
decisionHandler(YES);
}
And don't forget add whatsapp's white list at Info.plist:
<dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>whatsapp</string>
</array>
......
</dict>
Below method:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void)
Will be invoked multiple times (depending on the website) and inside this method you will need to check the url request of the navigationItem, in other words, we are giving the opportunity to intercept a request before it is being send and decide if we can continue with it or not:
if let requestURL = navigationAction.request.url?.absoluteString,
!requestURL.contains("https") && !requestURL.contains("http") {
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
If the request contains https or http it means we can safely navigate wherever we want. If not, you will have to add multiple validation like in #广锅锅 answer for example.

Resources