
In this series of tutorials we’ll be looking at creating a simple Rich Text Editor which you can easily implement in your own apps.
Normally people look at creating a Rich Text Editor to be an extremely difficult task as it means having to build it from scratch using the Core Text framework in combination with the UITextInput protocol. Apple provide no standard control for Rich Text Editing, UITextView its self only supports one style, so that’s one font, one color, one justification for the whole document. So where does this leave us? It looks like we only have one option and that’s to use Core Text but there is in fact one other option that has been opened up to us in iOS 5.
This other mysterious option is UIWebView, as of iOS 5 contentEditable can be set to true in a web view, this enables the user to edit all content inside a web page. Today we are going to leverage the power of the DOM to create our own Rich Text Editor.
note: Please be aware this tutorial will only work on iOS 5
Let’s do this!
Let’s begin, make your way into Xcode and create a new ‘View-based application’ for iPad and name it ‘RichTextEditor’ and save it wherever you want.
In this tutorial we’ll need a navigation bar where we can add some bar button items to use later on. A nice and quick way to do this is to encapsulate our root view controller(‘RichTextEditingViewControler’) in a UINavigationController. To do this head to ‘RichTextEditingAppDelegate.h’ and in it’s ‘application:didFinishLaunchingWithOptions:’ replace this:
self.viewController = [[RichTextEditorViewController alloc] initWithNibName:@"RichTextEditorViewController" bundle:nil];
with this:
RichTextEditorViewController *viewController = [[RichTextEditorViewController alloc] initWithNibName:@"RichTextEditorViewController" bundle:nil];
self.viewController = [[UINavigationController alloc] initWithRootViewController:viewController];
All this does is encapsulate the root view controller in a navigation controller, a quick and easy way to add a navigation bar.
Now that’s sorted, navigate to the XIB file (‘RichTextEditingViewController.xib’) and add a UIWebView to the view. We need to quickly create a IBOutlet connecting this web view to our root view controller, fortunately Xcode 4 provides us with an easy way to do this. Switch to assistant mode (which should split the screen in two, interface builder to the left and the root view controller header file on the right), then right click on the web view (a window should appear) and drag the dot in the row ‘New Referencing Outlet’ to somewhere in the header file until the line appears which is when you can release the mouse.
Before we continue and set everything up in our root view controller we need to create a simple html file. To do this just open up TextEdit.app and paste in the following code:
<html>
<body>
<div id="content" contenteditable="true" style="font-family: Helvetica">This is out Rich Text Editing View </div>
</body>
</html>
This is very basic HTML, inside the body we just create a div, assign it an id (which may be needed in later tutorials), set the contentEditable attribute to true, set the font and then add a little bit of content. Now let’s save this file as ‘index.html’ and drag and drop the saved file into the Xcode project.
Now we’ve done that you can switch back to Xcode, come out of assistant mode and head to the root view controller’s implementation file (‘RichTextEditingViewController.m’) . Go to the ‘viewDidLoad’ method and add this code:
NSBundle *bundle = [NSBundle mainBundle];
NSURL *indexFileURL = [bundle URLForResource:@"index" withExtension:@"html"];
[webView loadRequest:[NSURLRequest requestWithURL:indexFileURL]];
This just locates the html file we added to our project and loads it into our web view. Now we’ve done that we’re going to add a few bar button items, add the following code in the same place:
NSMutableArray *items = [[NSMutableArray alloc] init];
UIBarButtonItem *bold = [[UIBarButtonItem alloc] initWithTitle:@"B" style:UIBarButtonItemStyleBordered target:self action:@selector(bold)];
UIBarButtonItem *italic = [[UIBarButtonItem alloc] initWithTitle:@"I" style:UIBarButtonItemStyleBordered target:self action:@selector(italic)];
UIBarButtonItem *underline = [[UIBarButtonItem alloc] initWithTitle:@"U" style:UIBarButtonItemStyleBordered target:self action:@selector(underline)];
[items addObject:underline];
[items addObject:italic];
[items addObject:bold];
self.navigationItem.rightBarButtonItems = items;
This is longer but it is certainly isn’t compacted, we simply create a mutable array where we can add some items, create three items (one for bold, italic and underline), add those to the array and set the array as the navigation items right bar button items.
Now, that’s all well and good but we need to implement those methods we set as actions for the bar button items. Somewhere in the same file add the following methods:
- (void)bold {
[webView stringByEvaluatingJavaScriptFromString:@"document.execCommand(\"Bold\")"];
}
- (void)italic {
[webView stringByEvaluatingJavaScriptFromString:@"document.execCommand(\"Italic\")"];
}
- (void)underline {
[webView stringByEvaluatingJavaScriptFromString:@"document.execCommand(\"Underline\")"];
}
Again, this is basically the same thing three times but what are we actually doing? We are calling the ‘stringByEvaluatingJavaScriptFromString:’ method on the web view which performs the javascript we pass to it. The javascript we have provided to it in this tutorial tells the document to execute a command on the current selection, we are using the basic ‘Bold’, ‘Italic’ and ‘Underline’ commands.
That should be it, just build and run your application select some text in the web view and press any of the buttons to either make the text Bold, Italic or to underline it. We have just made ourself an – albeit basic – rich text editor!
Bonus Code
Seeing as though this is supposed to be a Rich Text Editor you don’t particularly want your user to be seeing the next/previous/done bar (UIWebFormAccessory) above the keyboard while they’re editing. Not only is this a waste of space it also serves no purpose when there is no other text field to navigate to. So I’m going to show you a slightly complicated ‘hack’ to remove it from the keyboard. Firstly we need to know when the keyboard is going to appear so add the following code to the ‘viewDidLoad’ method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
This makes us an observer of the UIKeyboardWillShowNotification and tells the notification center to call ‘keyboardWillShow:’ when the notification updates. Next, let’s add the ‘keyboardWillShow:’ method:
- (void)keyboardWillShow:(NSNotification *)note {
[self performSelector:@selector(removeBar) withObject:nil afterDelay:0];
}
You might be thinking, hang on a minute, what are we doing here, what’s the point of calling another method when we could make it cleaner and do it in the same one. The reason we have to call another method after a delay of just ’0′ is because at the time the method is called the keyboard has not yet appeared, so we can’t do what we want to do which is to find the keyboard in the window. Now let’s add this removeBar method:
- (void)removeBar {
// Locate non-UIWindow.
UIWindow *keyboardWindow = nil;
for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) {
if (![[testWindow class] isEqual:[UIWindow class]]) {
keyboardWindow = testWindow;
break;
}
}
// Locate UIWebFormView.
for (UIView *possibleFormView in [keyboardWindow subviews]) {
// iOS 5 sticks the UIWebFormView inside a UIPeripheralHostView.
if ([[possibleFormView description] rangeOfString:@"UIPeripheralHostView"].location != NSNotFound) {
for (UIView *subviewWhichIsPossibleFormView in [possibleFormView subviews]) {
if ([[subviewWhichIsPossibleFormView description] rangeOfString:@"UIWebFormAccessory"].location != NSNotFound) {
[subviewWhichIsPossibleFormView removeFromSuperview];
}
}
}
}
}
Woah! That’s a lot of code and it does look quite complicated. Let’s step through it slowly.
UIWindow *keyboardWindow = nil;
for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) {
if (![[testWindow class] isEqual:[UIWindow class]]) {
keyboardWindow = testWindow;
break;
}
}
This section of code looks through all the application’s windows to the find one whose class isn’t actually UIWindow and is just posing as one. This is always the keyboard window. Now we have to find the UIWebFormAccessory.
// Locate UIWebFormView.
for (UIView *possibleFormView in [keyboardWindow subviews]) {
// iOS 5 sticks the UIWebFormView inside a UIPeripheralHostView.
if ([[possibleFormView description] rangeOfString:@"UIPeripheralHostView"].location != NSNotFound) {
for (UIView *subviewWhichIsPossibleFormView in [possibleFormView subviews]) {
if ([[subviewWhichIsPossibleFormView description] rangeOfString:@"UIWebFormAccessory"].location != NSNotFound) {
[subviewWhichIsPossibleFormView removeFromSuperview];
}
}
}
}
This is where it get’s a bit more complicated. We have to look through all the subviews of the keyboard window to find the UIPeripheralHostView where iOS sticks the keyboard as well as the UIWebFormAccessory. We then do a similar thing again, look through the subviews of the UIPeripheralHostView to find the UIWebFormAccessory and once we find it we remove it from it’s super view.
And there you have it, we’ve got rid of the UIWebFormAccessory to make our rich text editor look perfect.
I hope you’ve enjoyed this tutorial and make good use of our simple Rich Text Editor. Keep an eye out for the next tutorial in this series!
Next in the series:







The keyboard hack is really great. Congratulations for getting it and thanks for sharing this precious information to all of us developers. I think that taking benefit of Rich Text Editing features coded in the system facilitates compatibility and will automatically benefit of new features coming with next iOS releases.
Excellent article. Thank you very much.
Any idea how to bring up the keyboard and set the cursor to the editable content by clicking anywhere on the UIWebView? I have to click on the content itself at the moment.
@Kev Here’s what you can do to solve your problem:
In between the opening html and body tags add the following code:
document.addEventListener('touchend', function(e) { // Listen for touch end on the document
// Get the touch and coordinates
var touch = e.changedTouches.item(0);
var touchX = touch.clientX;
var touchY = touch.clientY;
// Get the rect for the content
var contentDIVRect = document.getElementById('content').getClientRects()[0];
// Make sure we don't block touches to the content div
if (touchX > contentDIVRect.left && touchY < contentDIVRect.bottom) {
return;
}
// If the touch is out of the content div then simply give the div focus
document.getElementById('content').focus();
}, false);
</script>
Hope that fixes your problem!
Anyway to bring the keyboard and set the cursor WITHOUT touching the device?
Like, I press a EDIT button and the “content” get focused, the cursor goes to the beginning of the document and the keyboard appears, so the user starts typing.
Thanks
@Cassio Unfortunately this is not possible. See this post here on stack overflow for an answer as to why: http://stackoverflow.com/q/7332160/92714
Really love this tutorial, thank you
Hi Josh – awsome tutorial.
I hope you can find time to help with this question – I have implemented your code and it works like a dream apart from this issue. The text area I have reaches to the bottom of the screen, so when the keyboard opens it covers the uiwebview, so I simply reduce the size of the web view, the issue is when I type and press return the cursor contains downwards, it doesn’t scroll the text up …. so the text remains static instead of scrolling upwards when I reach the bottom of the uiwebview…..
Ok, to answer my own question, this appears to work for me …
<This is out Rich Text Editing View
@Steve Morton Sorry I never got a chance to reply to you, I must have overlooked it. In response to your original question take a look at the comments on Part 5 where Jen asked a similar question and offered some tips on how she figured it out.
Excellent tutorials. How do we manage the keyboard and cursor position? As user types and cursor goes below keyboard or if user selects region below where the keyboard will be, how do we scroll up to make cursor visible?
@Steve S Check the comments in Part 5 of the series where Jen describes how she figured out the problem.
When I insert the the code: RichTextEditorViewController *viewController = [[[RichTextEditorViewController alloc]…
I get “Use of undeclared identifier ‘RichTextEditor’
Using X code 4.3.1
Thanks
@Kevin It depends on what you named your project and consequently what the name on the view controller is. When you look at all your classes which one is the view controller? Also the code you are replacing that was originally there should have the name of the view controller.
Hi,
I must say its a great article and well written.
Is it possible to focus and show the keyboard when richtexteditor view loads?
I. E. I want keyboard to be focused on rte and be seen so that user could just start typing in, instead of clicking on the area to open the keyboard.
Please do let me know ASAP.
Thanks.
@Jainam Unfortunately this is not possible. See this post here on stack overflow for an answer as to why: http://stackoverflow.com/q/7332160/92714
Hi,
I have few more questions:
- Is it possible to limit characters inserted by the user?
- Is it possible to have counter of characters?
Thanks.
@Jainam To limit the number of characters you’ll need to do a bit of javascript in the index.html file so that every time any key is pressed (and consequently text about to be inserted) in your content div, a function should be called which will return either true of false depending on whether or not the insertion should be allowed. This is my index.html with that implemented:
<script type="text/javascript">
function shouldAllowInsertion() {
var contentDiv = document.getElementById('content'); // Content div itself
var content = contentDiv.textContent; // Text content of div
var maxLength = 15; // Maximum number of characters allowed
if (content.length > maxLength)
return false; // If the length of the content is greater than the max length return false and disable insertion
return true; // Otherwise allow insertion
}
</script>
<body>
<div id="content" contenteditable="true" style="font-family: Helvetica" onkeypress="return shouldAllowInsertion();">This is our Rich Text Editing View </div>
</body>
</html>
To get the number of characters in javascript this is the code you will need:
var content = contentDiv.textContent; // Text content of div
var contentLength = content.length; // Length of string
If need be you can then set the content length as the content of some other div on every insertion thus making it act as a counter.
Hi Josh,
Your reply helped a lot! Thanks for all the effort!!
I’m digging more in this and was wondering if is it possible to move cursor by moving finger around in some fix area and based on that cursor can move in our RTE?
And that it can recognize one finger touch or two finger swipe, something like that?
Let me know.
Thanks.
@Jainam You can detect the movement/panning of a touch using a UIPanGestureRecognizer and then adjust the selection in the content editable div using some javascript similar to that below:
var mainDiv = document.getElementById('content');
// Get the text node which contains the text
var textNode = mainDiv.firstChild;
// Create a new range
var range = document.createRange();
range.setStart(textNode, 1); // Set the start position (e.g 1)
range.setEnd(textNode, 1); // Set the end position (e.g 1)
// Get and adjust the selection
var selection = window.getSelection();
selection.removeAllRanges(); // Remvoe existing
selection.addRange(range); // Add new
UIPanGestureRecognizer also allows you to specify the number of touches required (e.g one or two fingers) in order to initiate the pan.
Hello,
i am using this tutorial to create an RTF Editor.
But i am stuck.
The view does load but it shows the complete HTML file content. As in
……
And i am not able to edit the content.
Can you help?
Its urgent.
@Shivani Did you definitely save the file with the extension .html and not .txt? So your main file should be named ‘index.html’.
Hi Josh,
Stunning tutorial! Thanks. It saved me from having to see the next/previous buttons in phonegap.
One question. Do you have any idea, how I can remove the next and previous buttons, but keep the done button. This will make a lot of sense for dropdown menus that get’s populated with ajax.
Best,
Jacobus
@Jacobus That’s fairly easy to do, we should be able to simply adapt the code we have got as UIWebFormAccessory is in fact a subclass of UIToolbar. Because of this we should simply be able to remove the specific items we do not want. Here’s the code I have come up with to remove just the next and previous buttons (actually a segmented control):
// Locate non-UIWindow.
UIWindow *keyboardWindow = nil;
for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) {
if (![[testWindow class] isEqual:[UIWindow class]]) {
keyboardWindow = testWindow;
break;
}
}
// Locate UIWebFormView.
for (UIView *possibleFormView in [keyboardWindow subviews]) {
// iOS 5 sticks the UIWebFormView inside a UIPeripheralHostView.
if ([[possibleFormView description] rangeOfString:@"UIPeripheralHostView"].location != NSNotFound) {
for (UIView *subviewWhichIsPossibleFormView in [possibleFormView subviews]) {
if ([[subviewWhichIsPossibleFormView description] rangeOfString:@"UIWebFormAccessory"].location != NSNotFound) {
NSMutableArray *items = [((UIToolbar *) subviewWhichIsPossibleFormView).items mutableCopy];
if (items.count > 0)
[items removeObjectAtIndex:0];
((UIToolbar *) subviewWhichIsPossibleFormView).items = items;
}
}
}
}
}
Hi Josh,
Is it possible to have any document (doc file) be converted to HTML via iPhone SDK?
I hope you help me here again!!
Thanks.
@Jainam Unfortunately I cannot find a way to do this after searching for sometime on Google and programming sites such as Stack Overflow. I’m sorry I couldn’t help you this time, if anyone else has any idea please go ahead and help Jainam.
Great Article!
In iOS6 the remove UIAccessory function still works, though there is now a dark grey line left at the top of the keyboard. There must be a shadow added to the keyboard in iOS6. Any ideas how to remove this? Or resize the keyboard frame?
Thanks.
My apologies but what are some common reasons my html file doesn’t get loaded? I’ve created the HTML, it’s in my project, but whenever I load the web view I don’t see the “This is our Rich Text Editing View” text anywhere.
@Andy Is their any incorrect HTML? Is the UIWebView definitely visible? Is the HTML file added to your bundle resources? When you create your load request are you using the right name and path
@Paul This opensource project shows a way to remove all shadows from a UIWebView: https://github.com/aaronbrethorst/FlatWebView
@paul – did you ever figure out a way to remove the shadow?
@Dan This opensource project shows a way to remove all shadows from a UIWebView: https://github.com/aaronbrethorst/FlatWebView
@josh thanks for that link.
Also just found this https://gist.github.com/3732403 which seems to do the trick.