Skip to content

Swift Guide for Codox + QuillJS

This guide shows you how to integrate Codox with QuillJS in a Swift application using the WKWebView class.

GitHub

Full integration example can be found here

Main Steps of the Integration

  1. Create and load an HTML file with the Quill editor and Codox initialization logic using a WKWebView

  2. In Swift, fetch the initial document state from the backend and initialize the Quill editor.

  3. In Swift, set up listeners (callbacks) to respond to Codox client library session and error events.

  4. Explicitly start Codox session via a javascript interface.

  5. When Codox triggers hooks in the JavaScript code, corresponding callbacks in Swift are invoked, passing the necessary data.

Step-by-Step Integration Tutorial

Step 1: Set Up Local Files

  1. The first step is to prepare the local files for your project. Create seprate directory QuillJSEditor/ in project root to store html and javascript files

    Terminal window
    mkdir QuillJSEditor
  2. Navigate to created directory QuillJSEditor/ and create local html file:

    Terminal window
    cd QuillJSEditor/
    touch quill.html
  3. Create a directory for Codox library files:

    Terminal window
    mkdir codoxLib/
  4. Navigate to codoxLib/ and create package.json file for javascript dependencies

    Terminal window
    cd codoxLib/
    npm init -y
  5. Install the library:

    Terminal window
    npm install @codoxhq/quill-provider@latest
  6. Copy the Codox lib file into QuillJSEditor/ directory:

    Terminal window
    cp codoxLib/node_modules/@codoxhq/quill-provider/dist/index.js QuillJSEditor/codox.js

    The QuillJSEditor/ directory should now look like this:

    Terminal window
    QuillJSEditor/
    ├── quill.html
    └── codox.js

Step 2: Initialize QuillJS Editor

Add the following code to quill.html to set up the Quill editor:

<!DOCTYPE html>
<html lang="en">
<head>
<title>Codox+QuillJS Integration</title>
<!-- Include quill css stylesheet -->
<link
href="https://cdn.quilljs.com/1.3.7/quill.snow.css"
rel="stylesheet"
/>
<!-- Include the Quill library -->
<script src="https://cdn.quilljs.com/1.3.7/quill.js"></script>
</head>
<body>
<!-- Editor container -->
<div id="editor"></div>
<script>
// create editor toolbar options
var toolbarOptions = [
['bold', 'italic', 'underline', 'strike'],
[{ header: 1 }, { header: 2 }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
];
var quill = new Quill('#editor', {
modules: { toolbar: toolbarOptions },
theme: 'snow',
placeholder: 'Start typing...',
});
</script>
</body>
</html>

Step 3: Initialize WKWebView in Swift

In swift app in ViewController.swift init WKWebView with local html with Quill editor load. To display the HTML/Javascript app in Swift, we will use the WKWebView class. The following code in ViewController.swift sets this up:

import UIKit
import WebKit
class ViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// create web view
setupWebView()
// load html with editor
loadQuillEditor()
}
// add meta attributes and configuration for webview
func setupWebView() {
// webview configs...
}
// load local html file with Quill
func loadQuillEditor() {
if let htmlFilePath = Bundle.main.path(forResource: "quill", ofType: "html", inDirectory: "QuillJSEditor") {
do {
let htmlContent = try String(contentsOfFile: htmlFilePath)
/**
* Setting baseUrl to a domain whitelisted in
* the Subscription dashboard
*/
let baseUrl = URL(string: "http://[domain from Codox subscription]")
webView.loadHTMLString(htmlContent, baseURL: baseUrl)
} catch {
print("Error loading HTML: \(error)")
}
}
}
}

Step 4: Inject Codox library script

Local javascript file codox.js should be injected into WKWebView after local html page is loaded.

Inject Codox library script codox.js into WKWebView after local html page is loaded. The following code in ViewController.swift sets this up:

class ViewController: UIViewController {
...
// this fn will be invoked as soon as webview is fully loaded and ready
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// inject codox lib
injectCodoxLibToHTML()
}
func injectCodoxLibToHTML() {
/*
Inject codox lib script directly into page, when html is loaded.
*/
if let codoxJSPath = Bundle.main.path(forResource: "QuillJSEditor/codox", ofType: "js"),
let jsContent = try? String(contentsOfFile: codoxJSPath, encoding: .utf8) {
webView.evaluateJavaScript(jsContent)
}
}
...
}

Step 5: Initialize the Editor State

Before initiating or joining a Codox session, the latest version of the document content must be available and must be setup in editor. The following code shows how to set up this from Swift app.
Add an initialization function in your HTML/Javascript quill.html to expose a method for Swift to set up the editor with initial content.

var quill = new Quill('#editor', {
...options,
});
// callback to set initial state for editor
function setQuillInitState(initQuillState) {
// Set initial content to Quill
quill.setContents(initQuillState, 'silent');
}

Then, invoke this function from Swift after the page has been fully loaded using the webView method from WKWebView .

class ViewController: UIViewController {
var initEditorState = /** Init editor state json, fetched from backend */
...
// this fn will be invoked as soon as webview is fully loaded and ready
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// inject codox lib first
injectCodoxLibToHTML()
// init editor state
setInitQuillEditorState()
}
func setInitQuillEditorState() {
/**
Invoke js function with init state json
*/
let jsCode = "setQuillInitState(\(initEditorState));"
webView.evaluateJavaScript(jsCode) { (result, error) in
if let error = error {
print("js invoking setQuillInitState error: \(error)")
}
}
}
...
}

Start Codox Session

Setup start session code in quill.html. To start or joining a Codox session, you need to explicitly call codox.start to connect to the session. Setup start session code in quill.html:

var codox = null; // codox instance will be assigned
// create Quill Editor instance
var quill = new Quill('#editor', {
// editor params
});
// function to start codox session
function startCodox(/* can pass params here, e.g. docId, apiKey, username */) {
// create new instance
codox = new Codox();
// create codox config
const codoxConfig = {
app: 'quilljs',
editor: quill,
docId: 'demo_docId', //this is the unique id used to distinguish different documents
username: 'demo_username', //unique user name
apiKey: '[your apiKey here]', // codox apiKey
};
// async start codox
codox.start(codoxConfig);
}

Then, invoke this function from Swift after the page has been fully loaded using the webView callback from WKWebView, in ViewController.swift . Note that starting the session proceeds after the initial document state has been passed into the editor.

class ViewController: UIViewController {
...
// this fn will be invoked as soon as webview is fully loaded and ready
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
/*
When page is fully loaded to the following is strict sequence:
1. inject codox lib into page
2. set init editor state
3. start codox sync
*/
// inject codox lib first
injectCodoxLibToHTML()
// set initial editor state
setInitQuillEditorState()
// start codox
startCodox()
}
func startCodox() {
let jsCode = "startCodox();"
webView.evaluateJavaScript(jsCode) { (result, error) in
if let error = error {
print("js startCodox invoke error: \(error)")
}
}
}
...
}

Optional: Codox Hooks and Error events

To subscribe to codox hooks and error events, need to add related code both on javascript and swift sides.
In quill.html need to add hooks and error events callbacks to codox config. The callbacks contain code which sends data to swift

You can listen to events from Codox to allow the Swift app to respond to content change notifications, member change notifications, and errors triggered within the Codox session.

In quill.html, define callback functions and include them in the config object passed to Codox. These callbacks are triggered internally when specific events occur and serve as bridges to invoke corresponding handlers in the Swift app.

var codox = null; // codox instance will be assigned
// create Quill Editor instance
var quill = new Quill('#editor', {
// editor params
});
// callback for fetchDocOnNetworkReconnect hook
async function fetchDocOnNetworkReconnectHook() {
return await new Promise((resolve, reject) => {
// temp callback for swift response
window.fetchDocOnReconnectHookResponse = (data) => {
const { content, timestamp } = JSON.parse(data);
resolve({ content, timestamp });
delete window.fetchDocOnReconnectHookResponse;
};
// trigger swift attached handler to fetch state
window.webkit.messageHandlers.fetchDocOnNetworkReconnect.postMessage();
});
}
// callback for usersUpdate hook
function usersUpdateHook(data) {
const json = JSON.stringify(data);
// send data to swift
window.webkit.messageHandlers.usersUpdate.postMessage(json);
}
// callback for contentChanged hook
function contentChangedHook(data) {
const json = JSON.stringify(data);
// send data to swift
window.webkit.messageHandlers.contentChanged.postMessage(json);
}
// callback for codox error events
function onCodoxError(data) {
let json = JSON.stringify(data);
// send data to swift
window.webkit.messageHandlers.onCodoxError.postMessage(json);
}
// start codox session
function startCodox() {
// create new instance
codox = new Codox();
// subscribe to codox error events and pass callback
codox.on('error', onCodoxError);
// demo username - random number generation
let username = `user_${Math.round(Math.random() * 1000)}`;
// create codox config
const codoxConfig = {
// ...
// provide hooks callbacks in config
hooks: {
fetchDocOnNetworkReconnect: fetchDocOnNetworkReconnectHook,
usersUpdate: usersUpdateHook,
contentChanged: contentChangedHook,
},
};
// async start codox
codox.start(codoxConfig);
}

On the Swiftt side you need to setup handlers to manage JavaScript messages sent from Codox, like the following example in ViewController.swift:

class ViewController: UIViewController {
...
func setupWebView() {
// other webview configurations here
// define handler for codox hooks and error events
webView.configuration.userContentController.add(self, name: "contentChanged")
webView.configuration.userContentController.add(self, name: "usersUpdate")
webView.configuration.userContentController.add(self, name: "fetchDocOnNetworkReconnect")
webView.configuration.userContentController.add(self, name: "onCodoxError")
}
...
}
//webview delegate
extension ViewController : WKNavigationDelegate, WKScriptMessageHandler {
/**
Handle content messages from JavaScript
**/
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "contentChanged", let content = message.body as? String {
// Handle the contentUpdated hook from codox - content is json here
// grab full latest state adn do any custom ops here, e.g. write to db
let jsCode = "getQuillEditorState();"
webView.evaluateJavaScript(jsCode){ (state, error) in
if let error = error {
print("js stopCodox invoke error: \(error)")
} else if let state = state {
print("[Codox contentChanged hook] full state json: \(state)")
}
}
}
if message.name == "usersUpdate", let users = message.body as? String {
// handle usersUpdate hook from codox - users is json here
print("[Codox usersUpdate hook] users update: \(users)")
}
if message.name == "fetchDocOnNetworkReconnect" {
print("[Codox fetchDocOnNetworkReconnect hook] invoked")
do {
let jsonData = "" // json is expected
if let json = String(data: jsonData, encoding: .utf8) {
let jsCode = "window.fetchDocOnReconnectHookResponse(\(json));"
webView.evaluateJavaScript(jsCode)
}
} catch {
print("FetchedDocOnReconnect error: \(error)")
}
}
if message.name == "onCodoxError", let errorData = message.body as? String {
// handle codox errors - any custom logic here - errorData is json here
print("[Codox Error Event]: \(errorData)")
}
}
}

Github

Working integration example can be found here