Building a native editor for iOS

Rajdeep Kwatra
Level Up Coding
Published in
9 min readJul 8, 2020

--

Prologue

I have always been fascinated with the great Open Source community that all of us software developers enjoy. There have been some great frameworks which have come out of people’s passion for solving a problem or making a great thing even better. I have used so many open source frameworks throughout my career as a software developer and have always wanted to contribute back. However, I was not able to come up with a good problem to solve, until now. You already know from the heading — it’s a full fledged completely native iOS editor framework.

We use rich text editors almost all the time without even realizing — like I am using an editor on Medium to write this story. I am using features like adding headings, links, images etc. but I am thinking about the content, not about how to add all these formatting. In my experience as a developer, I have found numerous web based Rich Text Editors, however not many as feature rich on iOS. So that’s why, I thought of building one.

Introduction

It always seemed like a very interesting technical challenge to solve to be able to come up with a capable and extensible editor on iOS. While it is possible to have a web based editor running on iOS and which just works there are a few things which are not as simple to handle in a non-native technology. My inclination towards native editor is driven by the fact that there are so many native features like Dynamic Font sizing and theming which are relatively easy to implement in native but can be very challenging when using web based technologies as a native solution (hybrid).

Requirements

Instead of creating a Rich Text Editor that can just serve as a drag-drop component, I wanted to come up with a framework that can help any one create a Rich Text Editor. The primary reason for this was that I wanted people to be able to add any feature that they need and not be driven or restricted by what is provided out of the box.

At a high level, the requirements that I wanted to cater were:

  • Be a standalone component — in its simplest form, it should be a straight replacement of UITextView and in its most complex form, it should be able to handle any functionality that can be expected out of a capable rich text editor.
  • Should be extensible to support adding any view as content in the Editor such that it flows with the text.
  • Resizing of content views should automatically resize the containing Editor and support this to nth nesting level.
  • Should support extending the appearance of text as the content is typed — for e.g. changing text as it is typed using mark-up syntax or highlighting as typeahead character is entered — and yet, not be aware of any of these requirements directly.
  • Should allow for working on multiple editors through the same toolbar based on where the focus is.
  • Respect the bounds of the container i.e. resize to change when the device orientation changes.
  • Support a default font and styling like alignment and head indentation.
  • And of course, support all this on macOS Catalyst as well with almost no additional effort.

A framework is born

With all these requirements in my mind, I started working on a framework and in about 3 months, I had a working version that catered to almost all the initial requirements. I called it Proton — something that brings about positivity for developers that use it and for their end users using the great apps by these developers!

Proton is composed of following key components:

Editor

To allow for all the points listed above, Editor introduces some key concepts. These concepts allow for extending the Editor at various levels — even in ways that I have not explicitly designed the Editor framework for as we will see later.

* All screen captures shown below are from iOS simulator.

Attachments

One of the most basic concepts of Editor is Attachments. Attachments are what allow Editor to have any view within Editor text. With support of Attachments, Editor can contain an emoji, a panel or even a table as long as it can be represented as a UIView. An Editor can contain another Editor as well.

There are five types of Attachments which are provided out of the box:

  • Matching content: An attachment that automatically resizes based on the size of the content i.e. as the content changes, the size of attachment changes to match that.
Attachment matching content size
  • Full width: An attachment that always takes the full width available in its container. If the width of container changes, the width of attachment would change to match that.
Attachment width matches container width
  • Fixed width: An attachment that always respects a configurable fixed width.
A fixed width attachment
  • Width range: An attachment that always retains a minimum width even when empty but only grows up to the given maximum width.
A attachment with min and max width
  • Percent width: An attachment that always takes up a certain percentage width of the container. As the container size changes, the size of attachment should change to respect the percentage that is used to create the attachment.

All these examples use a text view that is added as an attachment in the main editor. You can add any view inside attachment and apply the sizing rule that fits best for your scenario.

Creating any of these attachments is just a few lines of code:

Commands

Besides adding different kinds of views as content, an editor should allow to change content attributes as well as contents based on user interaction. For e.g. a user might want to select some text to make it bold or select some text and put that in a Panel. All such interactions which change the appearance attributes of the text in the editor or the content in the Editor based on user’s interaction are made possible by Commands.

Making text bold on clicking a button

A command can be executed directly on a given Editor or be passed on to Command Executor so that it is automatically run on the Editor that has the focus.

Executing command on the editor in focus

Proton allows you to add your own commands, that you can add logic to, to carry out tasks like the ones shown above. For text formatting, Proton already provide a base class that can be extended to add any Font Traits supported by iOS.

Creating a command for making text bold is as simple as:

Executing a command is just another line of code which can be invoked on click of a button:

BoldCommand().execute(on: editor)

Based on your logic, a command can work on the selected text or the entire Editor.

Command Executor

A Command Executor allows you to execute the given command on any Editor that has the focus. This comes in handy if you have an attachment that contains an editor, and the command can be executed on both the main editor containing the attachment as well as the editor inside the attachment. It abstracts away the complexity of knowing which Editor the user is trying to run the command on. This is made possible by another concept called Context which binds the Editor to the Command Executor. All the Editors sharing the same context as the Command Executor will automatically be linked with Command Executor.

Context

A Context acts like a key to link between Command Executor and an Editor. A Context allows you to link multiple Editors with the same Command Executor. This can be useful in the scenarios where you have multiple Toolbars acting on one or more Editors i.e. Editor contained inside an Attachments and then added to another Editor.

Text Processors

An editor does not only require making changes on explicit user interactions, but also as the user is changing the text. One such example is a Type-ahead where the text is highlighted in blue as the trigger character is entered. Another example is when a user tries to use markup syntax to format the text as they type opening and closing markers.

Example of Markdown TextProcessor

Text Processors also allow setting the priority. An Editor may register multiple Text Processors. Whenever the text changes inside the Editor, all the registered Text Processors get an opportunity to change the text or add attributes as required.

Mentions TextProcessor relaying typed text

Text Processors are given the opportunity to change text/attributes based on their respective priorities. The priority runs from High to Low and all the subsequent processors gets the text as changed by the processor that ran before it i.e. a processor will always get the updated text. This also allows to combine the effects from multiple Text Processors, if required. Besides high, medium and low, the Text Processor also has a priority called exclusive. If an Exclusive Text Processor is run, it prevents all the other Processors from executing. Instead, it notifies the other Processor that their processing has been interrupted so that the Processors can run any cleanup code, if required.

Just like Commands, you can add your own text processors to carry out the functionality like ones shown above.

Renderer

A Renderer always goes hand in hand with the Editor. Relation between Renderer and Editor is just same as that of UILabel and UITextView — it’s just a lot more powerful. In its simplest form, a Renderer is nothing but a read-only Editor. However, to provide an API that is easy to use and looks natural, Renderer encapsulated Editor and exposes its own set of features which are more suited to a Renderer. For e.g. having a TextProcessor would not make sense in the Renderer since the user cannot really type content inside the Renderer. However, Commands may be required to provide functionality like highlighting the text in the Renderer.

Highlight text based on selection

By virtue of the Editor, Renderer gains features like inspecting contents and scrolling to a given range. Both these capabilities can be used to create a feature like Find text.

Find text and scroll to content

Creating this command is just another few lines — thanks to the helper functions that Proton provides:

A recent addition to Proton is ListCommand and ListTextProcessor that takes care of all the formatting that you might want to have for creating lists in ` UITextView. To read more about lists, head over to Lists in UITextView.

And the story continues…

I plan to continue evolving Proton and add more features that I already have in my mind. I hope you have enjoyed reading about it and ready to take Proton out for a spin.

I would love to hear from you. Please feel free to share your comments/thoughts and any feature requests in comments section.

--

--

Rajdeep is an iOS developer at Atlassian. He believes that a piece of code can always be improved, but the cost may not always be justified against the benefits