A multitouch UIPanGestureRecognizer trick

Even though I wanted our app to still support iPhone OS 3.1, I realized that since our extra gesture-based features are iOS4 only, I could use UIGestureRecognizer for those. I already had a UIView subclass I was using as a transparent overlay where I implemented my custom tap detection and drag tracking. What I did was bite the bullet and replaced it completely with a plain UIView with gesture recognizers attached. In a few minutes I was able to duplicate all the behaviour that took lots of trial and error to get right. Kudos to everyone at Apple who worked on UIGestureRecognizer.

One part of the new behaviour I wanted to try out was a drag that started with two fingers. It looked like UIPanGestureRecognizer was working but it was ending the gesture when one finger was lifted. I wanted to support starting with a two-finger tap but then still track if the user lifts one finger.

I thought I’d have to implement a custom recognizer subclass when I came upon a little hack that seems to work: In my action method, I change the minimumNumberOfTouches property from 2 to 1 on the fly when called in the Began state, then set it back to 2 when called with Ended or Cancelled.

- (void)drag2Fingers:(UIPanGestureRecognizer *)sender {
    if (sender.state == UIGestureRecognizerStateBegan) {
        sender.minimumNumberOfTouches = 1;
    } else if (sender.state == UIGestureRecognizerStateEnded ||
               sender.state == UIGestureRecognizerStateCancelled) {
        sender.minimumNumberOfTouches = 2;

This seems to work but maybe it’s presumptuous to think it’s always going to. I think I’ll be doing that custom recognizer subclass in a future update.


UIAlertView + UIImage

Today I wanted some debug code to easily display an UIImage to check its orientation. At first I was going to just add it to my main view then remove it after a few seconds, but then realized a modified UIAlertView would be better since it could be displayed fire-and-forget style like [alert show]; [alert release];, it wouldn’t go away before I had a chance to look, and there could be a title to remind me what I was looking at.

Some trial-and-error was involved to get the message newlines & image position right, but here’s what I came up with. It displays the image scaled to 100 units height.

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:@"\n\n\n\n\n" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CGFloat imageHeight = 100;
CGFloat imageWidth = imageHeight * image.size.width / image.size.height;
imageView.frame = CGRectMake(floor((284 - imageWidth)/2), 47, imageWidth, imageHeight);
[alert addSubview:imageView];
[alert show];
[alert release];

Intercepting UINavigationController back button

I was looking for a simple way to add a UINavigationBar to my a modal view controller that presents a normal (pointy) Back button as if there was a root view controller to go back to. It would be as if my outer view was the root view controller.

Now I know one legitimate way of doing this would be to wrap a UINavigationController around my existing outer view an hiding the navigation bar, but that would be an intrusive change to my view class that I didn’t want to change. Another way would be to create UINavigationBar and have my view controller manage it, but this seemed like extra work I was hoping to avoid. I was looking for a self-contained, sneaky way of using a UINavigationController within my modal view controller class’ code.

The two issues were: giving the navigation bar a pointy Back button, and getting called when the user presses the button.

Since it seemed impossible to create a left-pointing button on-demand, I figured the first issue could most easily be solved by making a fake root view controller then pushing my modal view controller onto the UINavigationController’s stack. To keep this detail private to my modal view controller, I made a public method containerViewController that assembles this UINavigationController & fake root view. My caller uses it like:

modalViewController = [[MyModalTableViewController alloc] init];
modalViewController.delegate = whatever;
// and other setup, notification observers added, and such
[myViewController presentModalViewController:
    [modalViewController containerViewController] animated:YES];
// rather than presenting modalViewController itself

To intercept the back button, I searched the net to find a solution. One of the top hits suggests subclassing UINavigationController and overriding - [UIViewController popViewControllerAnimated:]. This worked but I was saw that the UINavigationController’s pop animation back to the fake root controller was still happening, which was interfering with my delegate’s for dismissing the modal view. I needed to find another override that could prevent the pop animation.

I noticed that UINavigationController is documented to be the delegate of UINavigationBar, even though it isn’t declared to be in the header file. I figured this meant UINavigationController implemented the UINavigationBarDelegate methods, one of which is - [UINavigationBarDelegate navigationBar:shouldPopItem:] and I found that indeed my sneaky UINavigationController subclass did indeed get a call to this method.

Here the code I made to do what I wanted:

@interface UINavigationController (UndocumentedUINavigationBarDelegateMethod)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
@interface SneakyNavigationController : UINavigationController

@implementation MyModalTableViewController
. . .
- (UIViewController *)containerViewController {
  UIViewController *fakeRootView = [[[UIViewController alloc] init] autorelease];
  fakeRootView.navigationItem.title = NSLocalizedString(@"Back", @"Modal TableView Back button");
  MyModalTableViewController *navController = [[[MyModalTableViewController alloc] initWithRootViewController:fakeRootView] autorelease];
  navController.navigationBar.barStyle = UIBarStyleBlack;
  [navController pushViewController:self animated:NO];
  return navController;

@implementation SneakyNavigationController
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
  UIViewController *viewController = nil;
  for (viewController in self.viewControllers)
    if (viewController.navigationItem == item)
  if ([viewController isKindOfClass:[MyModalTableViewController class]]) {
    [(ManageSocialAccountsViewController *)viewController close];
    return NO;
  return [super navigationBar:navigationBar shouldPopItem:item];

Mysterious twitpic authentication problem fixed

Error 401 from twitpic “Could not authenticate you (header rejected by twitter).”

Thanks to http://shkspr.mobi/blog/index.php/2010/05/howto-twitpic-and-oauth/ and i found that the OAuth header must be created with “GET” even though the request to twitpic is sent as a “POST”. That was unexpected and it probably would have taken me quite a while to find this just going by the documentation.

I wrote an AppleScript!

After messing up a migration of my iTunes library, I was left with many tracks in my library duplicated on disk
…/iTunes Music/Radiohead/OK Computer/01 Airbag.mp3
…/iTunes Music/Radiohead/OK Computer/01 Airbag 1.mp3
…/iTunes Music/Radiohead/OK Computer/02 Paranoid Android.mp3
…/iTunes Music/Radiohead/OK Computer/02 Paranoid Android 1.mp3
with iTunes using the later duplicate, the one with the ” 1″ prefix.

I made a script to iterate through all the iTunes library, find these tracks that point at these duplicate files, remap them back to the original files, then deletes the duplicate.

Posted script to macscripter.net

This was done with iTunes 9, I have no idea if the script would work on iTunes 10.

Improvement to Trevor Harmon’s UIImage+Resize.m

from the “Resize a UIImage the right way” post on Trevor’s Bike Shed
Trevor says:
December 24, 2009 at 9:18 pm
True; I didn’t bother with handling the imageRotation setting in croppedImage. I changed the method to include a source code comment explaining this.
If anyone would like to contribute an improved croppedImage function that fixes this oversight, I’d be happy to include it in the distribution.
I found a good post on Robert Clark’s Niftybean blog, “Selecting regions from rotated EXIF images on iPhone” with code for adapting the a crop rect based on a UIImage’s imageRotation. Adding this to croppedImage I then only needed to finally rotate the resulting CGImageRef, for I only needed to slightly generalize the private resizeImage:transform:… helper method and then use it to rotate the resulting cropped image to match imageRotation. It would be much better if the original UIImage’s metadata, imageRotation included, could be retained so the final rotation step could be avoided.
// Returns a copy of this image that is cropped to the given bounds.
// The bounds will be adjusted using CGRectIntegral.
// JPMH-This method no long ignores the image's imageOrientation setting.
- (UIImage *)croppedImage:(CGRect)bounds {
    CGAffineTransform txTranslate;
    CGAffineTransform txCompound;
    CGRect adjustedBounds;
    BOOL drawTransposed;
    switch (self.imageOrientation) {
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
            txTranslate = CGAffineTransformMakeTranslation(self.size.width, self.size.height);
            txCompound = CGAffineTransformRotate(txTranslate, M_PI);
            adjustedBounds = CGRectApplyAffineTransform(bounds, txCompound);
            drawTransposed = NO;
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            txTranslate = CGAffineTransformMakeTranslation(self.size.height, 0.0);
            txCompound = CGAffineTransformRotate(txTranslate, M_PI_2);
            adjustedBounds = CGRectApplyAffineTransform(bounds, txCompound);
            drawTransposed = YES;
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            txTranslate = CGAffineTransformMakeTranslation(0.0, self.size.width);
            txCompound = CGAffineTransformRotate(txTranslate, M_PI + M_PI_2);
            adjustedBounds = CGRectApplyAffineTransform(bounds, txCompound);
            drawTransposed = YES;
            adjustedBounds = bounds;
            drawTransposed = NO;
    CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], adjustedBounds);
    UIImage *croppedImage;
    if (CGRectEqualToRect(adjustedBounds, bounds))
        croppedImage = [UIImage imageWithCGImage:imageRef];
        croppedImage = [self resizedImage:imageRef
                                transform:[self transformForOrientation:bounds.size]
    return croppedImage;
Where the resizeImage: utility method was change to take a CGImageRef as a parameter instead of always using self.CGImage:
- (UIImage *)resizedImage:(CGImageRef)imageRef
- (CGAffineTransform)transformForOrientation:(CGSize)newSize;