fraxool
fraxool

Reputation: 3219

Catch Javascript Event in iOS WKWebview with Swift

I am building an app with web programming languages and want to start the camera when the user clicks on an HTML button. Since I want my camera view to be a custom one, I need to design it with Swift. So when the user clicks on this HTML button, I want to "catch" this click in Swift so I can start my native camera view.

I know it can be done with the WKWebview, but I don't really know how to do that.

For example, my Javascript (jQuery) code could look like that :

// User clicks to start the native camera with Swift
$(".camera_button").click(function() {
    // Function to call the camera view from JS to Swift
});

Can you help me to do that?

Thanks.

Upvotes: 29

Views: 36890

Answers (3)

Pedro Menezes
Pedro Menezes

Reputation: 51

First, set the html string:

var playerURL = """"
    https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/\(trackId)%3Fsecret_token%3D\(secretToken)&auto_play=false&hide_related=true&show_comments=false&show_user=false&show_reposts=false&show_teaser=false&visual=false
"""

Second, create your WKWebView and config it

private var webView: WKWebView?

override func awakeFromNib() {
    super.awakeFromNib()
    
    ...(1)...
    
    self.webView?.navigationDelegate = self
    self.webView?.isUserInteractionEnabled = true
    self.webView?.scrollView.isScrollEnabled = false
    self.webView?.allowsLinkPreview  = false
    self.webView?.contentMode = .scaleAspectFit
    self.webView?.loadHTMLString(self.playerUrl, baseURL: nil)

    DispatchQueue.main.async {
        self.webView = WKWebView(frame: self.viewSoundclound.bounds, configuration: config)
        guard let webView = self.webView else { return }
        self.viewSoundclound.addSubview(webView)
    }
}

Third, set the configuration of your JavasScript script that you are gonna add to your WKWebView

 override func awakeFromNib() {
    super.awakeFromNib()
    let config = WKWebViewConfiguration()
    let script = WKUserScript(source: self.eventListenerScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
    config.userContentController.addUserScript(script)
    config.userContentController.add(self, name: "soundCloundEvents")

    ...(2)...
}

After, create your script. IMPORTANT: the name in 'messageHandler.(name).postMessage' must be equals to name in this line:

'config.userContentController.add(self, name: "soundCloundEvents")'

private var eventListenerScript = """
    var iframe = document.querySelector('iframe');
    widget = SC.Widget(iframe);
    widget.bind(SC.Widget.Events.PLAY, function() {
        window.webkit.messageHandlers.soundCloundEvents.postMessage('PLAY');
    });
    widget.bind(SC.Widget.Events.FINISH, function() {
        window.webkit.messageHandlers.soundCloundEvents.postMessage('FINISH');
    });
"""

Finally, extends Controller or Cell (this case) to listen script

extension PodcastsListTableViewCell: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    print(message)
   }
}

Upvotes: 0

fraxool
fraxool

Reputation: 3219

Based on the answer from @Alex Pelletier, which really helped me, here is the solution the my question.

In my "loadView()" function, here is what I have :

let contentController = WKUserContentController();
contentController.addScriptMessageHandler(
    self,
    name: "callbackHandler"
)

let config = WKWebViewConfiguration()
config.userContentController = contentController

webView = WKWebView(frame: CGRectZero, configuration: config)
webView.navigationDelegate = self
view = webView

My function to handle the Javascript event which is sent to Swift :

func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)
    {
        if(message.name == "callbackHandler") {
            print("Launch my Native Camera")
        }
    }

... And finally, my Javascript (jQuery) code when a click happens on my camera button (in HTML) :

$(document).ready(function() {

    function callNativeApp () {
        try {
            webkit.messageHandlers.callbackHandler.postMessage("camera");
        } catch(err) {
            console.log('The native context does not exist yet');
        }
    }

    $(".menu-camera-icon").click(function() {
        callNativeApp();
    });
});

I hope it will help someone else :-) !

Upvotes: 42

Alex Pelletier
Alex Pelletier

Reputation: 5123

First lets create a js file. In the js, when an element has been you clicked you can send a message back like so:

varmessageToPost = {'ButtonId':'clickMeButton'};
window.webkit.messageHandlers.buttonClicked.postMessage(messageToPost);

After you have created the js file and the wkwebview you need to inject the script:

  // Setup WKUserContentController instance for injecting user script
  var userController:WKUserContentController= WKUserContentController()

  // Get script that's to be injected into the document
  let js:String= GetScriptFromResources()

  // Specify when and where and what user script needs to be injected into the web document
  var userScript:WKUserScript =  WKUserScript(source: js,
                                         injectionTime: WKUserScriptInjectionTime.AtDocumentEnd
                                         forMainFrameOnly: false)

  // Add the user script to the WKUserContentController instance
  userController.addUserScript(userScript)

  // Configure the WKWebViewConfiguration instance with the WKUserContentController
  webCfg.userContentController= userController;

  //set the message handler
  userController.addScriptMessageHandler(self, name: "buttonClicked")  

Finally you have to add listener function:

func userContentController(userContentController: WKUserContentController,
                           didReceiveScriptMessage message: WKScriptMessage) {

        if let messageBody:NSDictionary= message.body as? NSDictionary{
            // Do stuff with messageBody
        }

    }

Source Code

Upvotes: 15

Related Questions