Show Menu

Looking to hire an app developer?

Submit your 30 day Job Listing for FREE

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:

    
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:



 
   
This is out Rich Text Editing View

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.

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:


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

having issues?

We have a Questions and Answer section where you can ask your iOS Development questions to thousands of iOS Developers.

Ask Question

FREE Download!

Get your FREE Swift 2 Cheat Sheet and quick reference guide PDF download when you sign up to SwiftMonthly


Sharing is caring

If you enjoyed this tutorial, please help us and others by sharing using one of the social media buttons below.


Written by:

Josh is an indie Mac and iOS Developer with 2 years of experience behind him. You may know him as the developer of Spark for Mac as well as 4Score and 2Code for iOS. He is also a huge Apple lover, a Star Trek fan and active users on both Stack Overflow and Twitter

Comments

There are currently: 41 Responses to “Rich Text Editing : A Simple Start (Part 1)”. Leave your comment!

well, i certainly am happy about that! i find it quite usable, though far from perfect.anyhow, you’ve done a good job posting about the issues. i do hope that there will be a resolution now

childcare jobs


On IOS 7 when you remove the UIWebFormAccessory you can still view the placeholder of the toolbar.

To remove it completly change the code to:

// 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]) {

if ([[possibleFormView description] hasPrefix:@”<UIPeripheralHostView"]) {

for (UIView* peripheralView in [possibleFormView subviews]) {

// hides the backdrop (iOS 7)

if ([[peripheralView description] hasPrefix:@"<UIKBInputBackdropView"]) {

//skip the keyboard background….hide only the toolbar background

if ([peripheralView frame].origin.y == 0){

[[peripheralView layer] setOpacity:0.0];

}

}

// hides the accessory bar

if ([[peripheralView description] hasPrefix:@"= 5.0) {

webScroll = [[self webView] scrollView];

} else {

webScroll = [[[self webView] subviews] lastObject];

}

CGRect newFrame = webScroll.frame;

newFrame.size.height += peripheralView.frame.size.height;

webScroll.frame = newFrame;

// remove the form accessory bar

[peripheralView removeFromSuperview];

}

// hides the thin grey line used to adorn the bar (iOS 6)

if ([[peripheralView description] hasPrefix:@”<UIImageView"]) {

[[peripheralView layer] setOpacity:0.0];

}

}

}

}


it’s really great


– I am using “contentEditable” UIWebView to display html content from server and user can edit that content and send back to server.
– Now To change font size, color & font-type
I am using following code:
[webView stringByEvaluatingJavaScriptFromString:@”document.execCommand(‘fontSize’, false, ’14’)”];
– The problem is when i send this html content to server, content has content , font tag is deprecated in html4.0.1. Other browsers could not recognize font tag…
– I want UIWebview add the style for font size instead of font tag.
Is it possible with UIWebView??

Thanks in Advance.


Hi Josh,

Thanks for your great keyboard hack, I tested it in a small sample PhoneGap app and it worked well. But there has been two issues and I haven’t found why it happened:

1. When keyboard popup (I tapped to an text field), the screen is also moved up about 20px.

2. There is a strange grey line about 40px above keyboard when it appears, this line is also disappeared when I tap anywhere to hide the keyboard.

Please let me know if you experience these issues? I tested on an iPhone 4/iOS 6.1.3

Thanks,


@Josh : Thanks , I have added button using Inserthtml method , i have one more query , inserting any UI Component on webview work only if keyboard is present in your sample , if keyboard is not there how to handle ?


is it possible to add UIbutton web view?


    @sutz I’m afraid you’re going to have to clarify your question. It is possible to add a UIButton to any view including a web view by adding it as a subview, for example: [webView addSubview:button]. I’m not sure this is what you mean though.


Everyone loves it whenever people get together and share views.
Great website, continue the good work!


@josh thanks for that link.

Also just found this https://gist.github.com/3732403 which seems to do the trick.


@paul – did you ever figure out a way to remove the shadow?


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


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.


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.


@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):


- (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) {
NSMutableArray *items = [((UIToolbar *) subviewWhichIsPossibleFormView).items mutableCopy];
if (items.count > 0)
[items removeObjectAtIndex:0];
((UIToolbar *) subviewWhichIsPossibleFormView).items = items;
}
}
}
}
}


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


@Shivani Did you definitely save the file with the extension .html and not .txt? So your main file should be named ‘index.html’.


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.


@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:


// Get our content div
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.


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.


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:



    This is our Rich Text Editing View



    To get the number of characters in javascript this is the code you will need:

    var contentDiv = document.getElementById('content'); // Content div itself
    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.


@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 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.


@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.


When I insert the the code: RichTextEditorViewController *viewController = [[[RichTextEditorViewController alloc]…
I get “Use of undeclared identifier ‘RichTextEditor’
Using X code 4.3.1
Thanks


@Steve S Check the comments in Part 5 of the series where Jen describes how she figured out the problem.


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 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.


Ok, to answer my own question, this appears to work for me …

<This is out Rich Text Editing View


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…..


Really love this tutorial, thank you


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


@Kev Here’s what you can do to solve your problem:

In between the opening html and body tags add the following code:


Hope that fixes your problem!


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.


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.


Leave a Reply

Would you like to sign up to the mailing list by our sister site: SwiftMonthly?