Welcome to part one of the iOS 5 Rich Text Editing Series.

iOS 5 Rich Text Editing

In this series of iOS/html text edit tutorials we’ll be looking at creating a simple Rich Text Editor which you can easily implement in your own apps.

This tutorial is part of a series:

Rich Text Editing : A Simple Start (Part 1)

Rich Text Editing : Validation (Part 2)

Rich Text Editing : Highlighting and UIMenuController (Part 3)

Rich Text Editing : Fonts (Part 4)

Rich Text Editing : Undo and Redo (Part 5)

Rich Text Editing : Inserting Images (Part 6)

Rich Text Editing : Draggable Images (Part 7)

Bonus: Rich Text Editing: Sample Project

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:


1
self.viewController = [[RichTextEditorViewController alloc] initWithNibName:@"RichTextEditorViewController" bundle:nil];

with this:


1
2
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:


1
2
3
4
5
<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:


1
2
3
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:


1
2
3
4
5
6
7
8
9
10
11
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:


1
2
3
4
5
6
7
8
9
10
11
- (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.

iOS 5 Rich Text Editing

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

iOS 5 Rich text Editng

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:


1
[[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:


1
2
3
- (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:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (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.


1
2
3
4
5
6
7
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.


1
2
3
4
5
6
7
8
9
10
11
// 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:

Problems?

We have a new Questions and Answers section so you can get help from the awesome community.

Ask a Question

Enjoyed this post?

Subscribe to our RSS Feed or Follow us on twitter to keep up to date with the latest from iOS-Blog. Remember, Sharing is caring so please click one of the following options:

Tags: , , , , , ,