Rich Text Editor for iOS using WKWebView
If you are an iOS engineer working for a tech company like HubSpot, where the product's main access point is a browser, the chances that you will have to deal with HTML/CSS are very high. In some sections of HubSpot there are text fields (see image below) where content is html and styling is handled via CSS.

While building the HubSpot iOS app we needed a text field able to display content created from the web version and vice versa, able to create content that the web version can consume. Summarizing the text field has to satisfy the following requirements:
- Multi-line support
- Display HTML
- Basic CSS support
- Bold, italic, and underline styles support (BIU)
The first thing that I tried was UITextView
and NSAttributedString
which can be used to display HTML passing NSHTMLTextDocumentType
as NSDocumentTypeDocumentAttribute
. The problem with UITextView
is that it doesn’t support the CSS styles needed to create the annotation in the image below.

Also, there’s no way to select BIU out of the box, and you will need to implement a custom UIToolBar
and assign it to the inputAccessoryView
property. We needed something else to solve this problem and so we started to look for rich text editors.
If you google for rich text editors you will find some implementations that use a UIWebView
. By using a webview you will get full HTML/CSS support so we gave it a shot. For apps running on iOS 8 and later Apple recommends to use WkWebView
. WKWebView
offers a richer api and a better Javascript engine so let’s implement a basic rich text editor with a WKWebView
and Swift.
We need three things:
- HTML page containing a
div
with thecontenteditable
attribute - Javascript file for setting/getting information to/from HTML
- custom
UIView
that wraps theWKWebView
HTML
For the HTML page we can use a basic template like this:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0" />
</head>
<body>
<div id="editor" contenteditable="true"></div>
</body>
</html>
When you tap on a webview showing a contenteditable
element the system will automatically present the keyboard and you will also get BIU styles from the long-press menu.
Javascript
One of the things that I like about WKWebView
is the way you can communicate from Javascript to native code. Using UIWebView
the only way is by changing window.location
passing the required parameters in the query string and implementing shouldStartLoadWith
delegate function. In this delegate function, you have to parse the request URL to understand the message that has been sent from Javascript.
Using WKWebView
we have a new set of tools for achieving this: WKUserContentController
and WKScriptMessageHandler
protocol.
WKUserContentController
can be used to register message handlers and to inject Javascript. Using func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String)
we can add a message handler that will result in exposing a function window.webkit.messageHandlers.name.postMessage(messageBody)
to the Javascript environment. Using func addUserScript(_ userScript: WKUserScript)
we can inject arbitrary Javascript code.
WKScriptMessageHandler
is a protocol that contains a single function and this function gets called, on the conforming object, each time you use window.webkit.messageHandlers.name.postMessage(messageBody)
from javascript and the received WKScriptMessage
object is an object that encapsulates the message body and the message name.
The Javascript file will expose an API to insert text and to get the height of the currently displayed text. This is the script that I am using:
var richeditor = {};
var editor = document.getElementById("editor");
richeditor.insertText = function(text) {
editor.innerHTML = text;
window.webkit.messageHandlers.heightDidChange.postMessage(document.body.offsetHeight);
}
editor.addEventListener("input", function() {
window.webkit.messageHandlers.textDidChange.postMessage(editor.innerHTML);
}, false)
document.addEventListener("selectionchange", function() {
window.webkit.messageHandlers.heightDidChange.postMessage(document.body.offsetHeight);
}, false);
WKWebView wrapper
Putting it all together we can create a re-useable rich text component that subclasses UIView
. The class exposes a text, height and placeholder properties and it also has a delegate property used to notify text and height change. You can find the implementation here.
We can use this view like any other UIView
and set the delegate if you need to handle dynamic height and retrieve the content text. One important thing that you need to watch out for is the userContentController
strong reference to the message handler. This is the reason behind the use of WeakScriptMessageHandler
.
The gif below shows the final result using random HTML. As you can see it handles many HTML/CSS combinations very well.

Any of this sound interesting? HubSpot is hiring iOS developers! You can find more details here.