Previous in the series:
Here it is, the final tutorial in the series where we’ll attempt the most advanced feature yet! In this tutorial we’ll be subclassing UIGestureRecognizer, dealing with and using blocks but most importantly expanding our knowledge of javascript.
Javascript : The next step
First things first we need to add some javascript to our ‘index.html’ file. Just after the opening html tag add the following code:
<script language="text/javascript">
</script>
This just defines the area where we are going to add our functions. Now let’s add that function, in between the script tags add the following code:
function moveImageAtTo(x, y, newX, newY) {
// Get our required variables
var element = document.elementFromPoint(x, y);
if (element.toString().indexOf('Image') == -1) // Attempt to move an image which doesn't exist at the point
return;
var caretRange = document.caretRangeFromPoint(newX, newY);
var selection = window.getSelection();
// Save the image source so we know this later when we need to re-insert it
var imageSrc = element.src;
// Set the selection to the range of the image, so we can delete it
var nodeRange = document.createRange();
nodeRange.selectNode(element);
selection.removeAllRanges();
selection.addRange(nodeRange);
// Delete the image
document.execCommand('delete');
// Set the selection to the caret range, so we can then add the image
var selection = window.getSelection();
var range = document.createRange();
selection.removeAllRanges();
selection.addRange(caretRange);
// Re-insert the image
document.execCommand('insertImage', false, imageSrc);
}
This is the function which is actually going to do the moving, I’ve added comments to the code so you can step through it and know exactly what is going on. Everything it does is rather simple really but takes a fair amount of code to do it.
Subclassing UIGestureRecognizer
Now that we have that sorted, we can come back into the world of objective-c where our next mission is to subclass UIGestureRecognizer. Let’s start by creating a new class named ‘RTEGestureRecognizer’ which is quite obviously a subclass of UIGestureRecognizer.
Once you have done that, replace all the code in the header file with the following:
typedef void (^TouchEventBlock)(NSSet * touches, UIEvent * event);
@interface RTEGestureRecognizer : UIGestureRecognizer {
TouchEventBlock touchesBeganCallback;
TouchEventBlock touchesEndedCallback;
}
@property(copy) TouchEventBlock touchesBeganCallback;
@property(copy) TouchEventBlock touchesEndedCallback;
@end
Now hop over to the implementation file and add the following code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (touchesBeganCallback)
touchesBeganCallback(touches, event);
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (touchesEndedCallback)
touchesEndedCallback(touches, event);
}
What this will enable us to do is set up two different blocks which will be called when the touches began and when they ended thus allowing us to adjust the location of our image.
There is in fact one more thing to add:
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer {
if ([[preventingGestureRecognizer description] rangeOfString:@"UIScrollViewPanGestureRecognizer"].location != NSNotFound)
return NO;
return [super canBePreventedByGestureRecognizer:preventingGestureRecognizer];
}
Without this some of our touches will be taken by the scroll views pan gesture recognizer thus stoping some our image drags. The code in the method simply stops our gesture recognizer from being prevented by the scroll views equivalent.
Dealing with touches
Now back to our view controller, before we implement the touch handling. Go to the header file and add a variable called ‘initialPointOfImage’ of type CGPoint as well as importing the ‘RTEGestureRecognizer’ header file.
We can now start dealing with touches, swap to the implementation file and lets add our first chunk of code to the ‘viewDidLoad’ method:
RTEGestureRecognizer *tapInterceptor = [[RTEGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet *touches, UIEvent *event) {
// Here we just get the location of the touch
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
// What we do here is to get the element that is located at the touch point to see whether or not it is an image
NSString *javascript = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).toString()", touchPoint.x, touchPoint.y];
NSString *elementAtPoint = [webView stringByEvaluatingJavaScriptFromString:javascript];
if ([elementAtPoint rangeOfString:@"Image"].location != NSNotFound) {
// We set the inital point of the image for use latter on when we actually move it
initialPointOfImage = touchPoint;
// In order to make moving the image easy we must disable scrolling otherwise the view will just scroll and prevent fully detecting movement on the image.
webView.scrollView.scrollEnabled = NO;
} else {
initialPointOfImage = CGPointZero;
}
};
That’s a fair amount of code, there are comments there to help you a long but let’s step through what everything does. We start by creating the gesture recognizer (here called the ‘tapInterceptor’) and that set the block it should call when the touches begin. In this block we get location of the touch in our view, then from this we get the element located at that point in the web view by using a little bit of javascript. This information is important as we then check whether or not that element is an image, if it is we set the initial point of the image and disable scrolling in the scroll view (if you want to know why just have a look at the comment accompanying it) but if not we reset the initial point of the image.
Now that we have set everything up we can deal with what will happen when the user stops dragging, straight after that code add the following:
tapInterceptor.touchesEndedCallback = ^(NSSet *touches, UIEvent *event) {
// Let's get the finished touch point
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
// And move that image!
NSString *javascript = [NSString stringWithFormat:@"moveImageAtTo(%f, %f, %f, %f)", initialPointOfImage.x, initialPointOfImage.y, touchPoint.x, touchPoint.y];
[webView stringByEvaluatingJavaScriptFromString:javascript];
// All done, lets re-enable scrolling
webView.scrollView.scrollEnabled = YES;
};
This time there’s less code as we do most of the hard work with the javascript we set up in the ‘index.html’ file earlier. Here we get the location of the touch, tell javascript to move it and relabel to scrolling. Now that the gesture recognizer knows what to do when it detects touches, we just need to add it to the web view. Following that code just add this single line:
[webView.scrollView addGestureRecognizer:tapInterceptor];
And that’s it, build and run the application (preferably on the device so you can add some images, if not just add some to the html code) and you should be able to drag images about seamlessly. You’ll notice that the image doesn’t follow you as you drag and this is because it takes too long to do so and doesn’t look particularly elegant but simply being able to move the image around should suffice!
This final paragraph signals the end of these tutorials so I hope you have enjoyed the series as much as I have enjoyed writing them and have learnt something from them. Until next time!





Wow Josh, nice work. Glad to see you were able to do this finally. Sorry to see the end though.
Will there be a project file?
Thanks again for the great work.
@z ahmed, I’ll try to get the full project with all the features from all the tutorials put together for you as soon as possible.
Sure would love to get the project for this, it looks awesome.
Thank you.
Thanks for these tutorials. They are really helpful.
Can you tell me how to create an msword file (.doc file) of this text after user is done with editing and writing content in webview?
Or is there any other way around to create msword files in iphone programatically??
@z ahmed @Podcaster123 The sample project can be found in a new post here: http://ios-blog.co.uk/iphone-development-tutorials/rich-text-editing-sample-project/
@Namarta That is more complicated than it sounds and goes way beyond the aims of this tutorial. To get you started though I will offer you some advice.
I think the first thing you would want to do is get the HTML of the text that has been entered so that you can later convert the HTML to a word file. You can do this with the following code:
From this you would need to find some way or framework to convert HTML into a word doc. I would start simply by trying to find a way to create a word doc with just plain text. This is where it becomes difficult because help on this subject is pretty difficult to find. The most I could find was a few stack overflow questions:
- http://stackoverflow.com/questions/5999831/iphonehave-a-way-or-library-to-create-doc-files-in-iphone-app
- http://stackoverflow.com/questions/3255529/how-to-write-a-msword-doc-file-in-objective-c
- http://stackoverflow.com/questions/8235532/how-to-create-and-save-a-rtf-doc-docx-in-objective-c-for-ios
Good luck with it as it seems to be quite a challenge!
@Josh Garnham
Thanks !!
@Josh
Thanks for the sample project Josh – I just discovered it now. Very nice!
Been looking at the smapel project and I’m confused. The RTEGestureRecognizer file seems a lot different in the sample than in this article. Not sure I understand what is going on. The Javascript in the index.html also has some extra stuff that you don’t mention here.
Still having fun playing with this. Thanks
@zahmed RTEGestureRecognizer is not very different at all from the sample. All that I can see which is different is that in the implementation file I have synthesized the properties. Could you be a bit more specific with what’s different?
As for the Javascript there is a little extra (the event listener) because it was a request from the comments in Part 1.
I thought the extra Javascript was for the RTEGetsureRecognizer – that’s what was confusing me.
I don’t see any difference with the extra Javascript. If I leave it out, the keyboard still comes up OK if I touch the screen anywhere. Maybe I don’t’ understand?
Thanks again
@zahmed That is what the code is for, to display the keyboard for any touch anywhere as for me the keyboard will only come up when I touch the text itself if I don’t include the event listener.
Anyway, it doesn’t matter too much as long as it works!
Would it be possible to post sample code on how to show the image being dragged around with possible showing selectors around image?
Is it also possible to resize image?
@Developer Sorry that isn’t part of the series so I don’t have any code for it.
The way I think you could do is like so: When the user begins to drag, remove the image from the UIWebView and put it in an UIImageView. As the user drags around make the UIImageView follow their touch. Finally when they stop dragging/lift their finger remove the UIImageView and insert the image into the UIWebView at the correct position.
If you want to show selectors around the image you could get the frame of the image in the UIWebView then create 4 UIView’s to act as the selectors and place them at the corners.
It is definitely possible to resize the image. Once the user initially selects the image you should probably create a global variable in the javascript to hold the selected image. Then when the user starts to resize the image you can just use some javascript code a bit like this but replacing the size with what you want:
selectedImage.style.height = '300px'
Great Post !.
Could you suggest any thing to achieve this in lower versions of sdk? I mean ios 4.0.
I want to create a editor like apple’s mail app hai in it’s mail composer. Any help regarding this will be very helpful.
@Rahul Unfortunately the contentEditable attribute on div’s has only become possible to use in iOS 5, so is not available on any older versions. For versions less than iOS 5 the only real option is Core Text.