Thursday, October 22, 2009

Kicks & Giggles with Windows 7


256 core system with 1TB of RAM. Windows 7 supports up to 2TB of RAM. You'll need Windows Server 2008 R2 (Windows 7 Server) if you need more however.


Netbeans and GWTTestCase

In the "yet another problem I couldn't find an answer on Google for" unofficial series.

The problem lies in the fact that the GWT compiler operates on Java source code and not Java bytecode. Unfortunately, a standard WAR project on Netbeans will exclude the Java sources from a WAR file.

The first step is to go edit your project and go to "Build" and in "Exclude From WAR File:" (their spelling here not mine) field remove the value there which by default is "**/*.java,**/*.form".

The second thing you need to do is go to the run section and specify heap options in the "VM options" field. This is needed because tests run outside the application server you're using.

Finally, just hit "Test Project" and see your GWTTestCase's go. However, I would be remiss if I didn't mention that the vast majority of your GWT test code should not use GWTTestCase. Simply put, if your views are properly segregated, the majority of your GWT client code will run as regular Java code, so just use regular JUnit test cases for those.

Saturday, October 3, 2009

Capturing UIWebView Touches

I’ve searched everywhere on how to do this and everywhere the same result.  Either use a transparent overlay that you put over the web view to capture touches but you then lose the ability to click on HTML anchors, pinch and move around or subclass UIWebView and disable user interactivity.

First, a word of caution, the technique I describe here is not for the faint of heart nor is it for the inexperienced.  If you mail me for help, most people do instead of leaving comments for some reason, I will silently discard your email with a smile on my face.

The first problem is that UIWebView internally uses many undocumented views.  The iPhone has many very useful, undocumented views.  Unfortunately, using them means an automatic rejection from Apple’s AppStore.  The UIWebView is a composite of HScroller, UIWebDocument, UIImageView and other more minor views.  Out of all of those, only UIImageView is documented. 

Furthermore, the UIWebView documentation states that you should not subclass this view.   Considering how completely useless doing that is, it’s actually sound advice since it will not hurt your AppStore submission approval process.  UIWebView delegates all its functionality to internal components, overriding any method found in there doesn’t buy you anything, just lost time.

The first thing you need to know is that UIWebView is a very narrow view of the underlying WebKit engine.  Apple probably did this to make its system secure but also keep people from doing too many things with it too.  If you’re on Android, just smile and be happy you don’t have to do business with these people.

The second thing you need to know is that it’s OK to reference undocumented views as long as you use documented API’s to get them and you store such references in a documented class with the obvious choice being UIView.

OK, so let’s describe what we’re going to do before we show some code.  The steps are the following:

  1. Define a new protocol that extends the UIWebViewDelegate protocol, we’ll call this UIWebViewDelegateEx for now.  Define a new method called “tappedView”.
  2. Define a view, not a view controller, that is completely transparent.  This view holds the reference to the UIWebView you want to capture information from.  This view implements the UIWebViewDelegate protocol in full and holds a reference to your custom UIWebViewDelegateEx object.
  3. We define a couple of flags in this view, basically didMove and didShouldStartLoadViewRequestGetCalled.
  4. We define a timer in this class.
  5. The overlay should be above web view.  Completely transparent.  It doesn’t have to be over all of it but at least the section you want to get events from.
  6. We find the view that we need that was touched by the user by using hit testing.  Again, only documented API’s are used to get this view.
  7. We define a timer that we start when we detect a tap.  If this timer fires before the UIWebView delegate method “shouldStartLoadWithRequest” is called, we consider this a tap, otherwise, we consider that you activated an HTML child object of some kind, most likely an anchor.

So, at the end we have something that looks like this:

#import <UIKit/UIKit.h>

@protocol UIWebViewDelegateEx<NSObject, UIWebViewDelegate>

/**
* Called when the view was touched by the user and wasn’t an anchor.
*/
- (void)tappedView;

@end

/**
* Intercept any touch events by displaying a transparent
* overlay on top of a web view.
*/
@interface WebOverlayView : UIView<UIWebViewDelegate> {
        /**
         * The view that we are monitoring, i.e., the view that we will
         * possibly steal events from
         */
        UIWebView *webViewComposite;
        NSObject<UIWebViewDelegateEx> *delegate;

@private
        BOOL didMove;
        BOOL didShouldStartLoadViewRequestGetCalled; 
        NSTimer *timer;
}

@property(nonatomic, retain) IBOutlet UIWebView *webViewComposite;
@property(nonatomic, retain) IBOutlet NSObject<UIWebViewDelegate> *delegate;
@property(nonatomic, retain) NSTimer *timer;

@end

From here, we have to filter what we want, the strategy is basically is that if the user touches the view and the UIWebViewDelegate method “shouldStartLoadWithRequest” does not get called, we can assume that the user touched the view without triggering an HTML object like an anchor, this is where the timer comes in.  If after the elapsed time, “shouldStartLoadWithRequest” has not been called, we call our custom “tappedView” message.

#import "WebOverlayView.h"

@implementation WebOverlayView

@synthesize webViewComposite;
@synthesize delegate;
@synthesize timer;

#pragma mark -
#pragma mark NSObject

- (void)dealloc {
    [webViewComposite release];
    [delegate release];
    [timer invalidate];
    [super dealloc];
}

#pragma mark -
#pragma mark WebOverlayView

- (UIView *)findViewToHitForTouches:(NSSet *)touches
        withEvent:(UIEvent *)event
        inView:(UIView *)v {
    UITouch *touch = [touches anyObject];
    CGPoint pt = [touch locationInView:v];

 
    return [v hitTest:pt withEvent:event];
}

- (UIView *)findViewToHitForTouches:(NSSet *)touches withEvent:(UIEvent *)event {
    return [self findViewToHitForTouches:touches withEvent:event inView:webViewComposite];
}

- (void)timerFired:(NSTimer *)t {
    timer = nil;
    if (didShouldStartLoadViewRequestGetCalled == NO)
        [delegate tappedContent];
}

#pragma mark -
#pragma mark UIView

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [[self findViewToHitForTouches:touches withEvent:event] touchesBegan:touches withEvent:event];
    didMove = NO;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [[self findViewToHitForTouches:touches withEvent:event] touchesMoved:touches withEvent:event];
    didMove = YES;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [[self findViewToHitForTouches:touches withEvent:event] touchesEnded:touches withEvent:event];
    if (didMove == NO) {
       [timer invalidate]; 
       didShouldStartLoadViewRequestGetCalled = NO;
       timer = [NSTimer scheduledTimerWithTimeInterval:0.75
                target:self
                selector:@selector(timerFired:)
                userInfo:nil repeats:NO];
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [[self findViewToHitForTouches:touches withEvent:event] touchesCancelled:touches withEvent:event];
    didMove = YES;
}

#pragma mark -
#pragma mark UIWebViewDelegate

- (void)webViewDidStartLoad:(UIWebView *)webView {
    [delegate webViewDidStartLoad:webView];
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
        navigationType:(UIWebViewNavigationType)navigationType {
    didShouldStartLoadViewRequestGetCalled = YES;
    return [delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [delegate webViewDidFinishLoad:webView];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    [delegate webView:webView didFailLoadWithError:error];
}

@end

If this code was useful to you, drop me a line in the comments (not e-mail).  Obviously, you need to apply the technique to your own application but at least it shows it’s doable.