Opening URL in an Xcode build

I'm the last stages of QA for my universal app and everything is almost ready. The only problem I have now is that URLs are not working when previewing app in any simulator, unless I remove the Open in New Window option, then it works, but I can't go back to the app.

I believe the problem comes from the fact that since app itself opens on a webview it cannot open a new window, unlike a native app that opens safari and you can just go back to the app.

Anyone knows of a way around this, to be able to open Safari separately?

There's this old post from 2016 about it, but the solution is not working anymore:

I imagine you will need to setup a WKUIDelegate that can handle this. See:

Thanks Jonathan. I tried several solutions posted there but none worked.

I think it is a bit hard for us to help you without seeing what you are actually doing.

I did have a quick look around and found you can use the navigation policies from the WKNavigationDelegate

I looked at and adapted this

You will need to write this correctly , I just hacked the below to get it to work

  func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
 
        if navigationAction.navigationType == WKNavigationType.linkActivated {
          
            let url = navigationAction.request.url
            let shared = UIApplication.shared
 
            if shared.canOpenURL(url!) {
               // shared.openURL(url!)
                shared.open((url ?? URL(string: "\(url?.absoluteString ?? "")"))!, options: [:], completionHandler: nil)
            }

          decisionHandler(WKNavigationActionPolicy.cancel)
            return
        }

        decisionHandler(WKNavigationActionPolicy.allow)
    }

xcode
openWindowTest.zip (350.2 KB)

hype
OpenUrlsTest1.hype.zip (13.3 KB)


But for iOS I would not use this in the first place. I would use the postMessage from the Hype page to the iOS app.

Also to be able to get back to the app rather than be taken out of it I would use SafariServices.

xcode
OpenUrlsTest2.zip (356.2 KB)

hype
OpenUrlsTest2.hype.zip (13.6 KB)

1 Like

I can’ thank you enough! Using PostMessage worked like a charm! Your examples were incredible useful and save me a lot of time.

Thank you again!

2 Likes

In case anyone has this same issue, this is my WKWebViewController:

It’s full size view on iPhone X, disables double tap, disables zooming, can play sounds without first input, allows for external URL in a separate Safari Window.

//
//  WKWebViewController.swift
//  AlphaPod

import UIKit
import WebKit
import SafariServices

class FullScreenWKWebView: WKWebView {
    override var safeAreaInsets: UIEdgeInsets {
        return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }
}
class WKWebViewController: UIViewController, WKNavigationDelegate ,WKScriptMessageHandler  {
    
    var webView: WKWebView!
    
    //-- declare a message name to use later
       let messageName = "openStore"
       
       //-- declare a your html file  name to use later ( do not include the .html )
       let htmlName = "index"
    
    override var prefersStatusBarHidden: Bool {
        return true
    }
    
    // MARK: - WKUserContentController:  -> webkit Posted messages handler delegate //-- handles messages from hype page
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
       
          if message.name ==  "openStore" {
          print("\(message.body )")
          //-- Get the message passed to app
        let mssg    =  message.body as! String
         
             // anURL  = URL.init(string: message.body as! String)!
            if let url =  URL(string: "\(mssg)")  {
            //URL(string: "\(message.body)") else {
                let config = SFSafariViewController.Configuration()
                                   config.entersReaderIfAvailable = true
                
                let vc = SFSafariViewController(url: url, configuration: config)
                present(vc, animated: true)
                
                  return //be safe
              }
              
              
            
          }
        
        
        
    }

    
    override func viewDidLoad() {
        super.viewDidLoad()

        let wconfiguration = WKWebViewConfiguration()
        
        /* false = Play Media playback with native device player ; true =  inline */
          wconfiguration.allowsInlineMediaPlayback = false
         
         let wcontroller = WKUserContentController()
        
//MARK: WKUserContentController Init :  We must add the webkit scripts Posted messages we expect to get from the Hype Page to the controller
          wcontroller.add(self, name:  messageName )
         
         //  attach controller, config to WKWebview
         wconfiguration.userContentController = wcontroller;

        
        /* MARK: Does not require user interaction for .ie sound auto playback  */
        
        wconfiguration.mediaTypesRequiringUserActionForPlayback = []
        wconfiguration.setValue(true, forKey: "_allowUniversalAccessFromFileURLs")
        
        webView = FullScreenWKWebView(frame: .zero, configuration: wconfiguration)

        
        let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "website")!
        webView.loadFileURL(url, allowingReadAccessTo: url)
        var request = URLRequest(url: url)
        request.addValue("*", forHTTPHeaderField: "Access-Control-Allow-Origin")
        webView.load(request)
        
        webView.navigationDelegate = self
        webView.scrollView.delegate = self
        
        webView.scrollView.bounces = false
        webView.scrollView.isScrollEnabled = false
        webView.isOpaque = false
        
        ///Start in hidden mode
        webView.isHidden = true
        
        ///Add as subView, so we have original views bacground in blue
        view.addSubview(webView)
        
        ///Anchor webKit to view parrent view's edges
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }
}

//MARK: - Disable zooming

extension WKWebViewController: UIScrollViewDelegate {
    func viewForZooming(in: UIScrollView) -> UIView? {
        return nil
    }
    
    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
        scrollView.pinchGestureRecognizer?.isEnabled = false
    }
}

//URL


//MARK: - Delete double tap

extension WKWebViewController {
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        //deleteDoubleTap(web: webView)
    }
    
    func deleteDoubleTap(web: WKWebView) {
        for subview in web.scrollView.subviews {
            for recognizer in subview.gestureRecognizers ?? [] {
                if recognizer.isKind(of: UITapGestureRecognizer.self) {
                    let tapRecognizer = recognizer as! UITapGestureRecognizer
                    if tapRecognizer.numberOfTapsRequired == 2 && tapRecognizer.numberOfTouchesRequired == 1 {
                        subview.removeGestureRecognizer(recognizer)
                    }
                }
            }
        }
    }
}

//MARK: - WebKit loaded

extension WKWebViewController {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        ///On webKit finished loading, unhide it
        webView.isHidden = false
    }
}
2 Likes