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!

About the Author: Josh Garnham

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