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
-
Create and load an HTML file with the Quill editor and Codox initialization logic using a WKWebView
-
In Swift, fetch the initial document state from the backend and initialize the Quill editor.
-
In Swift, set up listeners (callbacks) to respond to Codox client library session and error events.
-
Explicitly start Codox session via a javascript interface.
-
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
-
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 -
Navigate to created directory QuillJSEditor/ and create local html file:
Terminal window cd QuillJSEditor/touch quill.html -
Create a directory for Codox library files:
Terminal window mkdir codoxLib/ -
Navigate to codoxLib/ and create package.json file for javascript dependencies
Terminal window cd codoxLib/npm init -y -
Install the library:
Terminal window npm install @codoxhq/quill-provider@latest -
Copy the Codox lib file into QuillJSEditor/ directory:
Terminal window cp codoxLib/node_modules/@codoxhq/quill-provider/dist/index.js QuillJSEditor/codox.jsThe
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 UIKitimport 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 editorfunction 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 instancevar quill = new Quill('#editor', { // editor params});
// function to start codox sessionfunction 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 instancevar quill = new Quill('#editor', { // editor params});
// callback for fetchDocOnNetworkReconnect hookasync 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 hookfunction usersUpdateHook(data) { const json = JSON.stringify(data); // send data to swift window.webkit.messageHandlers.usersUpdate.postMessage(json);}
// callback for contentChanged hookfunction contentChangedHook(data) { const json = JSON.stringify(data); // send data to swift window.webkit.messageHandlers.contentChanged.postMessage(json);}
// callback for codox error eventsfunction onCodoxError(data) { let json = JSON.stringify(data); // send data to swift window.webkit.messageHandlers.onCodoxError.postMessage(json);}
// start codox sessionfunction 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 delegateextension 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