27 May, 2011

UIGestureRecognizer Saves The Day

Written by

So you’ve just finished implementing the latest, greatest application and for once, you feel that your design was kept intact through the entire project schedule.  You’ve kept your objects oriented and your models viewed and controlled.  You’ve put together a design that Donald Knuth himself would approve of.  In short, your implementation is pure and perfect, and your developer heart is light.

But, wait.  It looks like a last-minute change request from the beta testers just showed up in your inbox.  You take a look and quickly realize that your beta team members know what they’re talking about and if you want commercial success, you really need to add this feature.  You pop open Xcode and start going through your source code only to realize that you now face a difficult choice.  You can either spend weeks and months refactoring your design or you can quickly hack something in but it will ruin your design, and has a high likelihood of breaking in some of the edge cases.  Internally, your pragmatic brain must now wrestle with your code-purity soul.

Most experienced developers have found themselves in this situation at some point in their career.  Fortunately if you’re an iOS developer the UIGestureRecognizer class just might be able to get you out of the pickle you’re stuck in.  UIGestureRecognizers allow you to easily extend touch handling to UIView objects regardless of whether they were created to do so or not.  Applications written for iOS 3.2 and later can take advantage of them by either adding a pre-defined recognizer or implementing their own.

Using UIGestureRecognizers is incredibly simple.  The UIView class implements an addGestureRecognizer/removeGestureRecognizer function pair for inserting recognizers into their touch handling hierarchy.  The quickest way to get up and running is to simply use one of the built-in gesture recognizers.  The six built-in types are:

  1. UITapGestureRecognizer
  2. UIPinchGestureRecognizer
  3. UIPanGestureRecognizer
  4. UISwipeGestureRecognizer
  5. UIRotationGestureRecognizer
  6. UILongPressGestureRecognizer

While you certainly can come up with other types of gestures to build, these six provide a large repertoire to start with.  It should be noted that multiple gesture recognizers could be added to an object.  What this means is that if you have a UIView object, you can easily add both rotate, swipe and press recognizers (or any other combination you can think of.)  This is the true power of gesture recognizers because they can be added cumulatively using the observer pattern without impacting existing object hierarchies.

Figure 1 shows a minimalist implementation of how you might add one to a view.  Please note that memory reference counting code has been removed to simplify the examples.  If you need to keep a pointer to the recognizer, you will need to make sure that you properly retain and release the pointer yourself or it will autorelease when removed from the view.

UIGestureRecognizer* recognizer = [[UITapGestureRecognizer alloc]
      initWithTarget:self action:@selector(tapReceived:)];
[self.view addGestureRecognizer:recognizer];

By choosing to implement gesture recognizers in the UIView base class, Apple chose to give developers greatest flexibility in what types of objects you can add these to.  You could just as easily add a rotate or swipe gesture to a UIButton as to a UIImageView, or a UITableView.  As you can see from the code in Figure 1, you simply pass a selector for whatever class function you choose to implement.  In this example, the calling class implements a tapReceived function that takes a UIGestureRecognizer as a parameter.  When tapReceived is called, the calling class receives the recognizer that it can then interrogate to determine what it should do in response.  Figure 2 shows an example of how you might implement the callback for handling a swipe gesture.

-(void)swipeReceived:(UISwipeGestureRecognizer *)gesture
{
CGPoint offset = self.view.center;
if (gesture.direction == UISwipeGestureRecognizerDirectionLeft)
{
offset.x -= 50.0;
[self.view setCenter:offset];
}
else if (gesture.direction == UISwipeGestureRecognizerDirectionRight)
{
offset.x += 50.0;
[self.view setCenter:offset];
}
}

As if this amount of flexibility wasn’t enough, Apple also provides one more mechanism for squeezing in “bad-code-bailout” functionality.  It does this in the form of the UIGestureRecognizerDelegate.  The three delegate functions shouldReceiveTouch, gestureRecognizerShouldBegin and shouldRecognizeSimultaneouslyWithGestureRecognizer will get called on your delegate object when a gesture is beginning.  They give you the opportunity to basically allow and disallow gestures based on whatever logic you might implement.  Obvious uses would be to disallow them based on application state or because other gestures are already in play.  To be clear, you can use gesture recognizers without setting the delegate property of the recognizer despite plenty of sample code to the contrary.  Furthermore, if you do chose to use the delegate functionality, all of the protocol functions are declared as optional.

If given all of the above flexibility still isn’t enough for you, you can always implement your own gesture recognizer.  If you go down that road, make sure to include UIGestureRecognizerSubclass.h, which will redeclare the UIGestureRecognizer state property as read/write as well as declare the eight functions that a derived class must implement.  The primary duty when implementing your own recognizer is to properly update the state property.  I will save implementing your own subclass as an activity for the reader, but suffice it to say the exercise is fairly straightforward.

As you can see, UIGestureRecognizers are incredibly handy for extending built-in UIKit classes as well as any proprietary classes you might have.  I personally have found them to be invaluable for the end of the project crunches where your pragmatic side needs to kick into gear.  Despite them being available since OS 3.2, not a lot of developers are aware of how powerful they can be.  For their part, Apple provides sample projects and documentation on their developer website, but to be honest, the class is so simple to use, you can find everything you need in the header files for the various classes.  I am repeatedly surprised at how many developers are unaware of the existence of these classes.  Hopefully if you fall into that category, this brief introduction to the UIGestureRecognizer class will give you another tool for your belt for those times when you need to get your project across the finish line.

 

Comments are closed.

%d bloggers like this: