Monday, January 5, 2009

iPhone SDK : UIWebView

I've been tangling with UIWebView the past couple of days and am documenting my struggles in hopes that this post may save others some time in the future. I've found a solution to my problem, intercepting single-tap events that occur on a UIWebView, although the solution doesn't seem to be optimal. But first, a little background to the other approaches I've tried...

Subclassing UIWebView/Overlaying a Transparent UIView
These approaches are basically the same. In a UIWebView subclass or in a transparent UIView subclass (on top of a UIWebView), override the UIResponder methods (touchesBegan:withEvent, touchesMoved:withEvent, etc...) and handle the touches accordingly.

The catch here for me was that I really wanted the UIWebView to behave as it always did for any other events. This would suggest that I just needed to intercept any single-tap events, and afterwards, forward all events to the UIWebView. However, what you really need to do is forward the events to the UiWebView's immediate subview, which is an instance of UIScroller. This solution seemed like it would work until I realized that pinch zoom events no longer worked and they are essential to the application I'm building.

Even more frustrating is the fact that all of UIWebView's subviews are not part of the public SDK. This fact makes it pretty much impossible to programmatically change how UIWebView works under the covers.

For more information on this approach, see the following thread in the Apple developer forums.

Overriding UIView hitTest:withEventmethod
According to Kerem at CodingVentures, he was able to intercept double-tap events on a UIWebView by subclassing and overriding the hitTest:withEvent method. However, when I implemented this technique, the UIEvent passed into the method always had an empty set of touches making it impossible to get the tapCount property on any UITouch objects. I tried overriding hitTest:withEvent for other UIView controls (e.g UIWindow, UIScrollView, etc...) all with the same result.

My Solution
The solution I came up with was to subclass UIWindow and override the sendEvent: method. This method gets called after hitTest:sendEvent returns but before any of the UIResponder methods get called. At this point, the UIEvent contains all of the associated touches, allowing me to access the tapCount property of the associated UITouch objects.

This solution isn't perfect as I am now handling single-taps at the window level instead of just those that occur on the UIWebView. Luckily, this doesn't really interfere with what I'm trying to do in my application.

I'd still really like to know why it seems that someimes the UIEvent passed into hitTest:sendEvent has touches associated with it and sometimes doesn't. I'd also be interested if anyone can successfully forward on pinch events to UIWebView.


9 comments:

Florence said...

Did you get it to work.
I have been trying to get 3 buttons to launch different webpages.

Thanks
Florence

Anonymous said...

Can you explain your method in more details?

How to use the subclassed UIWindow? What is the relation between subclassed UIWindow and the UIWebView?

I try to use the subclassed UIWindow in the Application Delegate class, but it seems that it never gets called.

rmb177 said...

The class is just a subclass of UIWindow. The app delegate class has a member variable of the subclassed window. Did you remember to change the class of the window to your subclass in Interface Builder?

Nick Dalton said...

For an alternative way to receive touch events from a UIWebView see: http://iPhoneIncubator.com/blog/windows-views/360idev-iphone-developers-conference-presentation

There's also an example of how to handle pinch events in the presentation and in the sample code.

billibala said...

Hi Ryan,

Thanks for the sharing. I encounter similar problem when I want to program an UIView to intercept all multi-touch events (only forward single touch). It appears to be simple and straightforward. I read through the documentation on "Event Handling" in iPhone Application programming guide. It didn't say it's not that straightforward at all...

Anyway... here's some further reference I found in Apple's developer forum which are pretty useful:

Forward Touches to a WebView

Touch inside intersection of two UIView

Unknown said...

I am subclassing MainWindow with UIWindow and in that using
hit test method:
-(UIView *)hitTestCGPoint)point withEventUIEvent *)event {

UIView *hitView = [super hitTestoint withEvent:event];
if (hitView == self)

return [[self subviews] lastObject];

else if([hitView isDescendantOfView:myView])
{

NSSet *touches = [event allTouches];

if ([touches count] >= 2) {
// prevent this
NSLog(@"Count =2");
return [[self subviews] lastObject];
}

}
return hitView;

}

But I am getting touches as zero object.
I debugged and checked event is always having 0 object.
But responder is having details of the events .
why is like that and how should I get the tap count

Any help??

ipad wholesale said...

I try to do what yodark spoke, but I have difficulty doing so. when I run, it displays the application that created, but not in the view tab strip I linked.

Risma said...

Hi Ryan, i have try your suggestion, and then, amazing, the touches event can forward into the webview.

Thank you very much.

But in my project i have a problem, i didn't want to set a pinch gesture, and i didn't do it. But when i try pinch my app in device or touch it using 2 fingers, the app got crashed. Can you help me to solved it??

iPhone Wholesale said...

Thanks Ryan, I appreciate your guidance. The touches event forwards to the webview and everything else is working accordingly.