In this, my latest article on rich text editing, I will detail the many choices available to developers when planning to create a rich text editor. From UIWebView to UITextView and Core Text, all the latest techniques are here to give you a first look into the (up until recently) mysterious world of rich text editing.
This entails going through all the sub-nodes and identifying the node where the start and end of the range is and what the index of the start/end is within in the identified node. Once we have done this you know what your new selection needs to be but first you need to save the old selection. I used a library called Rangy to do this which allows you to save and restore the selection. Once saved you can execute the command and finally restore the selection. So all that work just to provide a basic function, any further features will require more code and more work!
If you are interested in a basic rich text editor which deals only with applying attributes to the selection then do please check out my previous tutorials on the matter. Now then lets move on to the elephant in the room Core Text.
If you are going to use core text you have a couple of decisions to make. The first being whether or not you are going to start from scratch or piggy back off an existing component. Your second decision is how you are going to actually draw the text. We’ll cover both decisions and their options in this section so that you have a grasp of the advantages and disadvantages before you start working on your editor.
Decision 1 – Where to start
Start from scratch
This option will require quite a commitment and a lot of your time and effort. It certainly won’t be easy as you will be having to start from scratch. You will begin by simply having a UIView which draws using core text and you will have to gradually add editing functionality from there.
The first step in adding editing functionality is for your basic text view to implement the UITextInput protocol. This will mean your text view can implement the basic text editing actions such as insertion of text however it requires you to implement a number of its methods such that you are able to keep track of what’s going on and update your text view accordingly. This may not seem to bad so far but the UITextInput protocol does not give you everything you still need to deal with selection and more specifically the loupe view. As well as this the protocol isn’t all about it giving you notifications you also need to pass it a lot of information such as the on screen frame/rectangles of specific ranges. If you’re looking to add images there will be a lot of added work there too, this is certainly not an option you will want to consider if you are a beginner or do not have a lot of time on your hands.
However it is not all bad, once you have got the basic functionality set up it you have a lot of flexibility and you can quite easily add the basic rich text editing functions such as colouring and generally formatting the text. By building it from scratch you’ve not only greatly furthered your programming skills but you have also built a solid foundation to add functionality to.
If you don’t have a plentiful supply of time to build your rich text view but still want to use core text this may well be a better choice. In taking this option you will be subclassing a UITextView and replacing its drawing functionality with that of core text’s.
You’ll be saving yourself a lot of time using this method and it means that you don’t spend a lot of your time trying to get selection to work and dealing with all the UITextInput methods so you don’t have to worry about giving it a huge load of information as the text view will deal with all that by itself. However there will be some obvious down sides as the information the text view provides may not be entirely accurate for the text drawn by core text.
Despite this being a much simpler and easier method there are some immediate downsides. To begin with you have to make sure that the origin of the UIView doing the drawing lines up with the UITextViews document view so that the text is lined up and so the text view selects the correct areas. Another issue is how core text draws letters which can fit together (apologies for not knowing the correct typographical term) such as ‘fi’, these will form one ligature instead of the two which the text view will use. This means that all text after it is slightly unaligned, this does cause an immediate problem but it can contribute further to our next issue, that being line breaks. UITextView and core text use different drawing systems so the line breaks may not always be aligned and this is made worse by any general mis-alignment of text or the frame itself. This then causes the actual selection to not highlight the correct area and has an impact on the rest of the document. Rest assured if you are going to take this approach in a production app you will have to spend time ironing out this issue to ensure that 99% of the time it functions correctly.
Decision 2 – Drawing techniques
Draw text all at once
This is the simplest of the two options as you can get core text to just draw everything in one block. The only downside of this is that it can be inefficient as redrawing all the text every time it is edited will slow your app down especially if there is a lot of text plus a lot of attributes.
Draw line by line
Indeed the slightly more difficult option as it involves going through every line in the text and drawing them to the right location which can be difficult if a line break is required. Another thing to be wary of if you are going to use this technique and are only going to redraw the line being edited is to keep an eye to see if a line break is added as in this case all lines below the edited one must be shifted down and some could well be hidden if they are no longer visible. Despite these few niggles this option is probably the more flexible of the two as it allows you to keep the lines as separate objects/views and thus initially only draw lines those which will visible to the user and while editing to only redraw lines with changes. This option is definitely more efficient provided you optimise it similarly to how I have specified and so would be the better choice if you were dealing with a lot of styled text.
UITextView plus some UILabels
Our last and probably most recently discovered technique is using a normal, unchanged UITextView with any styled text displayed in UILabels. This technique has been used in one of the most noteworthy apps for developers on iPad of late, Codea.
The idea is a simple yet powerful one allowing you to only display labels which would be visible to the user and then reuse them much like how UITableView does with cells. It also allows you to – in a similar fashion to drawing line by line with core text – only redraw or adjust labels which have been editing or need to be moved due to editing. Of all the techniques this is probably the best when it comes to the ratio of efficiency to building time required.
Despite it being a simple idea there are some little niggles that occur due to the way it works. The first issue is related to figuring out where exactly to place the labels in the text view, to do this you need a method to take in an NSRange and spit out a CGPoint with the origin of that text in the text view. Unfortunately, UITextView provides no way to do this so you have to do it yourself. In brief you end up splitting the text in the text view into visible lines (not text separated by \n), going through them up until the line containing the range’s location and adding up their height to get a y-value. As for the x-value you have to, once you have found the line containing the location, work out the width of the text up until the range. Once you have done that you can construct and return the point. The problem lies with the fact that this technique is not entirely accurate so the label may not be aligned perfectly.
Issue number two is the recurring problem with line breaks. As I mentioned in the previous paragraph the technique entails splitting then text up into visible lines based upon how UITextView does line breaks. Again the problem with accuracy strikes, if the text is not broken up into lines exactly how UITextView does it then labels will end up in the wrong place.
Other than these two inherent problems (both of which are present in Codea) this method proves to be extremely simple and efficient which is likely why we are beginning to see it being used in more and more apps.
So there we have it, the up to date comprehensive listing of all the current methods to achieve our ultimate goal of a rich text editor. Some of these methods have only come about fairly recently with the UITextView-UILabel method being the most newest. There has been some quite obvious innovation when it has come to rich text editing in the recent year or two as more and more developers are looking for this function in their app. The absence of an Apple provided iOS control for this function has always puzzled me as it would give way for a number of new and feature rich apps but let’s just say looking forward to iOS 6, the future looks bright.