Invoking Swift from a UIWebView using Javascript

Hey there! I am currently exploring the process of making a call from a JavaScript function in a UIWebView to Swift in iOS 10. To do this, I have set up a basic project for testing purposes, and you can find the code below.

import UIKit

class ViewController: UIViewController, UIWebViewDelegate  {    
  @IBOutlet var webView: UIWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let url = Bundle.main.url(forResource: "products", withExtension: "html")
        let request = NSURLRequest(url: url! as URL)
        webView.loadRequest(request as URLRequest)
    }

    @IBAction func closeDocumentViewer() {
        displayView.isHidden = true;
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

If my goal is to receive a string from a JavaScript function, what additions should I make to the code above?

Answer №1

If you're considering updating your app's web view, I highly recommend switching to WKWebView over UIWebView. By using WKWebView, you can avoid the need to register a custom URL scheme. Moreover, WKWebView offers significant advantages, particularly in terms of performance and rendering since it operates in a separate process.

You can find more information and Apple's official recommendation for using WKWebView at this link: Apple Documentation on WKWebView

Note:

As of iOS 8.0 and OS X 10.10, it is advised to utilize WKWebView instead of UIWebView or WebView for incorporating web content into your application.

Creating a native to JavaScript bridge with WKWebView is straightforward:

import WebKit

final class ViewController: UIViewController, WKScriptMessageHandler {
    private var webView: WKWebView?
    override func loadView() {
        super.loadView()
        
        let webView = WKWebView(frame: self.view.frame)
        webView.configuration.userContentController.add(self, name: "scriptHandler")
        self.webView = webView
        
        self.view.addSubview(webView)
    }
    
    public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print("Received Message: \(message.name) with body: \(message.body)")
    }
    // remaining code
}

In your JavaScript code, you can then call it like so:

window.webkit.messageHandlers["scriptHandler"].postMessage("hello");

I have developed a library that enhances this functionality and introduces some advanced JavaScript syntax. GitHub Repository for BridgeCommander

To integrate it, simply reference the project (or include the Swift and JavaScript files in your Xcode project) and execute the following steps:

webView = WKWebView(frame: self.view.frame)
let commander = SwiftBridgeCommander(webView!)

commander.add("echo") {
    command in
    command.send(args: "You said: \(command.args)")
}

This will enable you to use callback syntax in JavaScript as demonstrated below:

var commander = new SwiftBridgeCommander();
commander.call("echo", "Hello", function(args) {
        // success callback
    }, function(error) { 
        // error callback
});

Answer №2

To implement a custom URL Scheme like myawesomeapp, you will need to intercept requests using the following method:

func handleWebRequest(webView: WKWebView, request: URLRequest, navigationType: WKNavigationType) -> Bool

Trigger a call to native code by using

window.location=myawesomeapp://hello=world
, and extract any query parameters passed from request.url.query in the native code.

For more insights, refer to my question on integrating WKWebView with JavaScript for synchronous communication here: Synchronous Communication between JavaScript and Native Code in WKWebView

Answer №3

To execute a swift function from JavaScript, we can utilize the WKScriptMessageHandler.

A class that adheres to the WKScriptMessageHandler protocol includes a method for accepting messages from JavaScript code running on a webpage.

In order to listen for the event, we must add a listener to the WKUserContentController.

 let contentController = WKUserContentController()
    contentController.add(self, name: "loginAction”)

We need to implement the userContentController in order to receive data transmitted from JavaScript, narrowing down our focus to handling only the "logout" action at this time.

  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {       
       if message.name == "logout" {
           print("JavaScript is sending a message \(message.body)")
       }
   }

On the JavaScript side, the implementation should be as follows:

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Executing a Ruby function via AJAX

I am fairly new to working with ajax, and I find myself in need of using it for my Rails application. Here is the function in my controller: def check_code input = params[:input] code = params[:code] if input == code return true else retur ...

Having trouble with your contact form and not getting it to work properly with Javascript, Ajax, or

I've been struggling to get a contact form working for an entire day. I included the following script at the top of my page: <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script> This is the structure ...

ensure that only one option can be selected with the checkbox

Can someone help me with applying this code on VueJS? I tried replacing onclick with @click but it's not working for me. I'm new to VueJS, so any guidance would be appreciated! function onlyOne(checkbox) { var checkboxes = document.getElement ...

I attempted to activate the hover effect on a DOM element using JavaScript, but was unsuccessful. It appears that achieving this is not possible. However, I am curious as to how Chrome is able to

I attempted to activate the hover state of a DOM element using JavaScript, but had no success. It appears that this task is not achievable in the way I initially approached it. I have heard that creating a class with a hover function and then adding or ...

Encountering CORS policy error preventing localhost from running

While working on my Net SuiteCommerce Advanced E-commerce webstore, I encountered an error message when running localhost. Despite conducting research on Stackoverflow and other websites, I have been unable to find a satisfactory solution. I attempted to ...

Dayjs is failing to retrieve the current system time

Hey everyone, I'm facing an issue with using Dayjs() and format to retrieve the current time in a specific format while running my Cypress tests. Despite using the correct code, I keep getting an old timestamp as the output: const presentDateTime = da ...

Dynamically implementing event listeners in JavaScript to remove specific elements based on their

My website has a list of phones displayed as ul li. When a user clicks the "Buy" button, it should create a new li within another div called "ordersDiv". Users can delete their purchase from the cart by clicking "Remove", which should remove the li with ma ...

Techniques for simulating functions in Jest

I have a pair of basic components that I'm currently creating tests for using jest. My goal is to verify that when I click on a currencyItem, the corresponding array gets added to the select state. To achieve this, I am passing the handleCurrencyToggl ...

Strange behavior of Lambda function in Typescript

Within a larger class, I'm working with the following code snippet: array.map(seq => this.mFunction(seq)); After compiling using the tsc command, it becomes: array.map(function (seq) { return _this.mFunction(seq); }); Everything seems fine so f ...

Utilize images inputted via an HTML DOM file uploader within p5.js

I'm facing a challenge with allowing users to upload their image file through a DOM file uploader (<input type="file"></input>). Once the image is uploaded, I'm unsure of how to transfer it to JavaScript and process it using p5.js. Ho ...

"Troubleshooting Nextjs in a Production Environment: Tips for Resolving Local Debugging Issues When

Everything is working flawlessly in my nextjs development setup. The build process goes smoothly without any issues. However, when attempting to serve the production locally, an error pops up saying: "Error: Element type is invalid: expected a string (for ...

Error: Attempted to remove an item from an index beyond the array's size in Swift causing a fatal error (lldb

I'm currently working on a method to compare two arrays, 'list' and 'id'. The objective is to determine if any of the integers stored in 'id' are also present in 'list', and then remove those integers from &apos ...

Tips for preserving newly add row with the help of jquery and php

Currently, I am attempting to implement a functionality on a Wordpress theme options page that dynamically adds rows using jQuery. Below is the HTML code snippet from the THEME-OPTIONS page <a href="#" title="" class="add-author">Add Author</ ...

Stopping a jQuery function from executing multiple times while it is already active - is it possible?

Currently, I am utilizing the http://jsfiddle.net/CqAU2/ plugin for image rotation on my website. The issue I am facing is that when the user clicks on the image box multiple times, it continues rotating each time. I want the click action to only be regi ...

change visibility:hidden to visible in a css class using JavaScript

I've put together a list of Game of Thrones characters who might meet their demise (no spoilers included). However, I'm struggling with removing a CSS class as part of my task. Simply deleting the CSS is not the solution I am looking for. I' ...

I am encountering an error when attempting to import the app from the server.js file in Express

In my server.js file, I have set up an express server and exported the app from it. //server.js require("dotenv").config(); const express = require("express"); const app = express(); const connectToDb = require("./connectToDb ...

Establishing a connection to a remote Mongo database using Node.js

I have a remote MongoDB server and I am looking to establish a connection with it using Node.js. I was able to successfully connect directly with the Mongo shell, but for some reason, I cannot establish a connection to the MongoDB server in Node.js. The co ...

Zooming in with Three.js OrthographicCamera by focusing on the cursor位置

In my Three.js project, I am using an OrthographicCamera and OrthographicTrackBallControls for zooming and panning. I am attempting to implement a functionality to zoom to the cursor position, but so far, I have been unsuccessful. To start, here is how I a ...

Organize divs based on their attributes

Using inspiration from this SO post, my goal is to group divs based on their "message-id" attribute. The idea is to wrap all divs with the same "message-id" in a div with the class name "group". <div class="message" message-id="1"> ...

Encoding and decoding a two-way stream in NodeJS

I am looking to encapsulate a socket within another object that has the following features: - transforms output, such as converting strings into Base64 format - transforms input, such as converting Base64 back into strings (Please note: while my specif ...