5

When you long press on a hyperlink in a WkWebview you get an action sheet. I want to override that action sheet with my own set of options when it is long pressed, but behave normally otherwise. I can get the the long press event, but I don't know how to:

  1. Prevent the default actions sheet for showing
  2. Prevent the webview from navigating to the URL on long press end.
  3. Get the underlying href URL that the webview was being direct to or the html a tag itself and associated attributes such as an ID.
Jed Grant
  • 1,305
  • 1
  • 17
  • 47

2 Answers2

4

One simple (but radical) way to cancel an action sheet is to override presentViewController:animated:completion: in your app's root view controller.

Swift 3 sample

override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {

  guard let alertController = viewControllerToPresent as? UIAlertController,
  alertController.preferredStyle == .actionSheet else {
    // Not an alert controller, present normally
    super.present(viewControllerToPresent, animated: flag, completion: completion)
    return    
  }

  // Create and open your own custom alert sheet
  let customAlertController = UIAlertController(...)
  super.present(customAlertController, animated: flag, completion: completion)
}

You can retrieve the link's URL from alertController.title, and if you need to retrieve other attributes, I suggest you look at this commit to Firefox iOS, that uses a JS script handler to find the clicked element and send its attributes back to the app.

Also you'd need to write some logic to prevent cancelling any other alert sheet other than WKWebView's, that you app may use.

Greg de J
  • 320
  • 1
  • 2
  • 8
  • I tried that method but didn't work through since `WKActionSheet` wouldn't (for some reason) always get triggered by `present()`. I ended up using a duration technique [link](http://stackoverflow.com/questions/9419041/ios-how-to-get-duration-of-long-press-gesture) and implemented `gestureRecognizer(gestureRecognizer:shouldRecognizeSimultaneouslyWith:)` – gabuchan Dec 02 '16 at 04:49
1

Here's a working solution for overriding the menu. I would still like to get some html attributes, but don't know how to do that yet.

override func loadView() {
    super.loadView()

    let contentController = WKUserContentController();
    let userScript = WKUserScript(
        source: "redHeader()",
        injectionTime: WKUserScriptInjectionTime.AtDocumentEnd,
        forMainFrameOnly: true
    )
    contentController.addUserScript(userScript)
    contentController.addScriptMessageHandler(
        self,
        name: "callbackHandler"
    )
    let config = WKWebViewConfiguration()
    config.userContentController = contentController
    self.webView = WKWebView(frame: self.view.frame, configuration: config)
    self.webView.navigationDelegate = self
    self.view = self.webView
    let lpress = UILongPressGestureRecognizer(target: self, action: "webViewLongPressed:")
    lpress.delegate = self
    self.webView.scrollView.addGestureRecognizer(lpress)
}

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

func webViewLongPressed(sender:UILongPressGestureRecognizer!) {
    longpress = true
    if (sender.state == UIGestureRecognizerState.Ended) {
        print("Long press Ended")
        //This is where everything starts to happen
    } else if (sender.state == UIGestureRecognizerState.Began) {
        print("Long press detected.")

    }

}

func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
    if let myUrlStr : String = navigationAction.request.URL!.absoluteString {

        if myUrlStr.lowercaseString.rangeOfString("/book/") != nil {
            /* Do not allow links to be tapped */
            var parts = myUrlStr.componentsSeparatedByString("/")
            let id = Int(parts[4])
            if navigationAction.navigationType == .LinkActivated && longpress == true {

                decisionHandler(.Cancel)
                let ac = actionMenu(self, id: id!, user_id: 1)
                self.presentViewController(ac, animated: true) {

                }
                longpress = false
                return
            }
        }
    }
    decisionHandler(.Allow)

}
//Build action sheet
func actionMenu(sender: UIViewController, id: Int, user_id: Int) -> UIAlertController {

    let alertController = UIAlertController(title: "Title", message: "Some message.", preferredStyle: .ActionSheet)

    let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in

    }
    alertController.addAction(cancelAction)
    let someAction = UIAlertAction(title: "Some action", style: .Default) { (action) in
        //do something, call a function etc, when this action is selected
    }
    alertController.addAction(someAction)

    return alertController
}
Jed Grant
  • 1,305
  • 1
  • 17
  • 47
  • To get html attributes, put a javascript function that can do what you want on the page and call it from swift using `evaluateJavaScript:` http://stackoverflow.com/questions/24049343/call-javascript-function-from-native-code-in-wkwebview – Lou Franco Dec 15 '15 at 14:11
  • @LouFranco that only works if you know what element to get attributes from which requires a tap location. I supposed you could save that location on long press, the programmatically trigger a touch event at that location... then you would also have to create some custom javascript and pass details about the event... feels pretty complicated. =/ Such is life though. – Jed Grant Dec 15 '15 at 17:11
  • You can register swift functions that JavaScript triggered by the HTML interaction can call (the page can call you) -- see `WKScriptMessageHandler` – Lou Franco Dec 15 '15 at 17:18