Welcome to the Buchman dot mobi blog, I'll occasionally post development related entries here.

Detecting Swipes in UIWebViews

November 5, 2009

While working on my latest Cocoa iPhone project I ran across an common frustrating issue, I was unable to find a way to add swipe detection to an UIWebView instance and have it retain it's ability to navigate and scroll. I tried subclassing, adding a transparent layer and anything else i could find on google about the issue. Finally i did come up with a useful solution and wanted to share it with anyone else who was having the same problems.

In Apple's documentation about Safari event handling for touch here, they describe two new event handlers for javascript that turn out to be quite useful. The touchstart event is triggered on a finger down on mobile safari and touchmove is triggered when the finger moves across the screen. As it turns out there's no direct way to call back to your cocoa program from the javascript event handlers, but you can navigate in response to them. Additionally your cocoa program's UIWebView delegate is notified when navigation is about to happen with webView:shouldStartLoadWithRequest:navigationType:

So the solution is two fold, firstly the pages that you're displaying in the webview have to be modified to include a javascript file that binds event handlers into the body element. This can be done by simply including the js file in the pages if you control them or you can actually inject the js in directly with stringByEvaluatingJavaScriptFromString: to your webview after the content's loaded. I've also included the jQuery javascript library for simplicity. My version of that javascript is as follows:

/* shared.js - Included javascript file in UIWebView html, requires jQuery to be included first */ var startX; var navigated=false; $(function () { $.each(["touches", "targetTouches"], function(i, propName) { if ( $.inArray(propName, $.event.props) < 0 ) $.event.props.push(propName); }); $("body").bind("touchmove", function (e) { if(navigated) return; event.preventDefault(); var curX=event.targetTouches[0].pageX var deltaX = Math.abs(startX - curX); if(deltaX > 40) { if(startX - curX > 0) { navigated=true; document.location="left"; } else { navigated=true; document.location="right"; } } }).bind("touchstart", function (event) { startX=event.targetTouches[0].pageX }) })

As you can see, if you speak js and jquery, when the swipe exceeds 40 pixels from the starting position of the touch the javascript navigates to either "left" or "right". These navigations can be captured with this webView:shouldStartLoadWithRequest:navigationType: method on your webview's delegate:

/* UIWebView's delegate */ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString * lastPart = [[[request URL] absoluteString] lastPathComponent]; if(navigationType==5 && ([lastPart isEqual:@"right"] || [lastPart isEqual:@"left"])) { if([lastPart isEqual:@"left"]) NSLog(@"Swipe Left"); else NSLog(@"Swipe Right"); return FALSE; } }

Since the only gesture i wanted to capture was a left or right swipe this will do nicely, you could easily capture just about any other gesture with a similar method. I hope that this will help some who were stuck with the same strange issue and just wanted to swipe some web content in apps.