Commits

Bill Garrison committed 5d7fd27

Updated for gesture recognizers and ARC

  • Participants
  • Parent commits 34671df

Comments (0)

Files changed (24)

Classes/AppDelegate.m

 #pragma mark -
 #pragma mark UIApplication Overrides
 
-- (void)applicationDidFinishLaunching:(UIApplication *)application 
+- (void) applicationDidFinishLaunching:(UIApplication *)application 
 {    
     // Override point for customization after app launch    
 	
 #pragma mark -
 #pragma mark NSObject Overrides
 
-- (void)dealloc 
+- (void) dealloc 
 {
-	[navigationController release];
-	[window release];
-	[super dealloc];
+	navigationController = nil;
+    window = nil;
 }
 
 #pragma mark -

Classes/ImageViewController.h

-//
-//  ImageViewController.h
-//  ImageViewController
-//
-//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
-//
-// LICENSE <http://creativecommons.org/licenses/BSD/>
-//
-// $Revision$
-// $Author$
-// $Date$
-
-/**
- Implements a view controller that manages zooming and tapping in a scrollable image view.  The controller keeps the image centered within the scroll view when zooming happens.
- 
- The controller enables touch-interaction with the image view:
- - When the controller is embedded within a navigation controller, single tap toggles the visibility of the navigation bar.
- - Double-tap toggles image zooming between the image's original size and a zoom-to-fit-display size.
- - Two-finger tap will zoom out the image by a stepping factor until the minimum zoom is reached.
- 
- When the original image size is larger than the device display, the minimum zoom scale will that value which zooms the image to fit the display.
- When the image is smaller than the device's display, the minimum zoom scale will be 1.0: the original image size.
- */
-
-#import <UIKit/UIKit.h>
-#import "TapZoomImageView.h"
-//#import "TapDetectingScrollView.h"
-
-@protocol TapDetectingScrollViewDelegate;
-
-@interface ImageViewController : UIViewController  <TapDetectingScrollViewDelegate>
-{
-@private
-	UIImage *_image;
-	TapZoomImageView *_tapZoomImageView;
-	UISlider *_zoomSlider;
-}
-
-/** Designated initializer
- \param image The image to be displayed. Should not be nil.
- */
-- (id) initWithImage:(UIImage *)image;
-
-#pragma mark -
-#pragma mark Zooming and Scaling
-
-/** Action method to change the zoom on the image viewer
- */
-- (IBAction) changeZoom:(id)sender;
-
-#pragma mark -
-#pragma mark Properties
-
-/** The Image we're displaying. */
-@property (nonatomic, retain) UIImage *image;
-
-/** The embedded image view. */
-@property (nonatomic, readonly) IBOutlet TapZoomImageView *tapZoomImageView;
-
-/**  */
-@property (nonatomic, readonly) IBOutlet UISlider *zoomSlider;
-
-@end
-
-#pragma mark -
-
-/* 
- LICENSE
- Copyright (c) 2009, Standard Orbit Software, LLC. All rights reserved.
- 
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- 
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the Standard Orbit Software, LLC. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-

Classes/ImageViewController.m

-//
-//  ImageViewController.m
-//  ImageViewController
-//
-//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
-//
-// LICENSE <http://creativecommons.org/licenses/BSD/>
-//
-// $Revision$
-// $Author$
-// $Date$
-
-#define DEBUG_ZOOMING 0 // set to 1 for additional zoom calculation debugging
-#define DEBUG_LAYOUT 0
-
-#define ZOOM_OUT_FACTOR 2
-#define ZOOM_MAX_FACTOR 10
-
-#import "ImageViewController.h"
-#import "TapZoomImageView.h"
-
-@interface ImageViewController()
-- (void) updateZoomSliderUI;
-@end
-
-#pragma mark -
-@implementation ImageViewController
-
-#pragma mark -
-#pragma mark Initializers
-
-// Designated initializer
-
-- (id) initWithImage:(UIImage *)image;
-{
-	LOG_ENTRY;
-	self = [super initWithNibName:@"ImageViewController" bundle:nil];
-	if ( self ) {
-		_image = [image retain];
-	}
-	return self;
-}
-
-- (id) init;
-{
-	return [self initWithImage:nil];
-}
-
-#pragma mark -
-#pragma mark NSObject Overrides
-
-- (void) dealloc 
-{
-	ReleaseAndNil( _image );
-	ReleaseAndNil( _tapZoomImageView );
-	
-	LOG_EXIT;
-    [super dealloc];
-}
-
-#pragma mark -
-#pragma mark UIViewController Overrides
-
-- (void) viewDidLoad
-{
-	LOG_ENTRY;
-		
-	self.view.userInteractionEnabled = YES;
-	self.view.multipleTouchEnabled = YES;
-	
-#if DEBUG_LAYOUT
-	self.imageView.backgroundColor = [UIColor blueColor];
-	self.view.backgroundColor = [UIColor greenColor];
-#endif
-}
-
-- (void) viewWillAppear:(BOOL)animated
-{
-	LOG_ENTRY;
-	
-	// Install the image and resize the image view to fit it.
-	self.tapZoomImageView.image= self.image;
-		
-	[self updateZoomSliderUI];
-}
-
-- (void)didReceiveMemoryWarning 
-{
-	// Releases the view if it doesn't have a superview.
-    [super didReceiveMemoryWarning];
-}
-
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
-{
-	return YES;
-}
-
-- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
-{
-	self.zoomSlider.hidden = YES;
-}
-
-- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
-{
-	LOG_ENTRY;
-	
-	[self updateZoomSliderUI];
-	
-	if ( self.navigationController.navigationBarHidden == NO ) {
-		// Restore the zoom slider if we're not hiding
-		self.zoomSlider.hidden = NO;
-	}
-}
-
-#pragma mark -
-#pragma mark Zoom Scaling
-
-
-- (IBAction) changeZoom:(id)sender;
-{
-	[self.tapZoomImageView zoomToCenterWithScale:self.zoomSlider.value animated:NO];
-}
-
-- (void) updateZoomSliderUI
-{
-	// Reposition the slider
-	
-	CGRect sliderFrame = self.zoomSlider.frame;
-	sliderFrame.origin.y = CGRectGetHeight(self.view.frame) - 25.0;
-	self.zoomSlider.frame = sliderFrame;
-	
-	// Update the scale on the slider
-	self.zoomSlider.maximumValue = self.tapZoomImageView.maximumZoomScale;
-	self.zoomSlider.minimumValue = self.tapZoomImageView.minimumZoomScale;
-	[self.zoomSlider setValue:self.tapZoomImageView.zoomScale animated:YES];
-	
-}
-
-#pragma mark -
-#pragma mark UIScrollViewDelegate methods
-
-/*
-UIScrollView implements pinch-to-zoom behavior using two delegate methods: -viewForZoomingInScrollView: and -scrollViewDidEndZooming:withView:atScale:
-*/
-
-- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView 
-{
-	ASSERT( self.tapZoomImageView == scrollView );
-    return [self.tapZoomImageView imageView];
-}
-
-- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale 
-{	
-	LOG_ENTRY;
-	
-	ASSERT( self.tapZoomImageView == scrollView );
-
-	// Always reset the scroll view content size after zooming, so that scroll bars are rescaled appropriately.
-	scrollView.contentSize = view.frame.size;
-	
-	// Re-center the image view
-	[self.tapZoomImageView updateContentInsetForCentering];
-	
-	// Update the zoom slider control after a user has performed a pinch-zoom
-	[self.zoomSlider setValue:scale animated:YES];
-
-	LOG_EXIT;
-}
-
-
-#pragma mark -
-#pragma mark TapDetectingScrollViewDelegate methods
-
-/*
-We implement specific tap-behaviors using the TapDetectingScrollViewDelegate methods.  
-For single taps, we take the image full screen, hiding the navigation controller and auxillary controls (the zoom slider).
-For double-taps, we toggle zooming the display between the image's native size and it's zoom-to-fit-the-display scale factor.
-For two-finger taps, we incrementally zoom out from the image until we reach the minimum scale factor.
-*/
-- (void)tapDetectingScrollView:(TapDetectingScrollView *)scrollView gotSingleTapAtPoint:(CGPoint)tapPoint;
-{		
-	// Toggle the nav controller hidden or visible.
-	
-	[self.navigationController setNavigationBarHidden:!self.navigationController.isNavigationBarHidden animated:YES];
-	
-	// Update the zoom slider
-	if ( self.navigationController.isNavigationBarHidden ) {
-		[self.zoomSlider setValue:scrollView.zoomScale animated:NO];
-		self.zoomSlider.hidden = YES;
-	} else {
-		self.zoomSlider.hidden = NO;
-		[self.zoomSlider setValue:scrollView.zoomScale animated:YES];
-	}
-	
-	// Rezoom the image to accomodate the presence/absence of the nav bar.
-	[self.tapZoomImageView zoomToCenterWithScale:scrollView.zoomScale animated:NO];
-	
-	[self updateZoomSliderUI];
-}
-
-- (void)tapDetectingScrollView:(TapDetectingScrollView *)scrollView gotDoubleTapAtPoint:(CGPoint)tapPoint;
-{	
-	BOOL isZoomedToOriginalSize = fabs(scrollView.zoomScale - 1.0) < 0.0001;
-	
-	if ( isZoomedToOriginalSize ) {
-		// Rezoom to fit the display bounds
-		[self.tapZoomImageView zoomToCenterWithScale: self.tapZoomImageView.zoomToFitScale animated:YES];
-	} else {
-		// Rezoom to the image's original size
-		[self.tapZoomImageView zoomToCenterWithScale:1.0 animated:YES];
-	}
-	
-	// Update the zoom slider
-	[self.zoomSlider setValue:scrollView.zoomScale animated:YES];
-}
-
-- (void)tapDetectingScrollView:(TapDetectingScrollView *)scrollView gotTwoFingerTapAtPoint:(CGPoint)tapPoint;
-{
-	LOG_ENTRY;
-	
-	// two-finger tap zooms the image back out.
-		
-	// If already at the minimum zoom factor, we're done.
-	BOOL isMinimallyZoomed = fabs(scrollView.zoomScale - scrollView.minimumZoomScale) < 0.0001;
-	if ( isMinimallyZoomed ) return;
-	
-	// Calculate the new zoom factor, ensuring it stays to the minimum
-	CGFloat newScale = scrollView.zoomScale / ZOOM_OUT_FACTOR;
-	
-	if ( newScale < scrollView.minimumZoomScale ) {
-		newScale = scrollView.minimumZoomScale;
-	}
-	
-	[UIView beginAnimations:nil context:NULL];
-	{
-		// Zoom the image to the tap point at the new scale value.
-		[self.tapZoomImageView zoomToCenterWithScale:newScale animated:NO];
-
-		// Update the zoom slider
-		[self.zoomSlider setValue:scrollView.zoomScale animated:NO];
-	}
-	
-	[UIView commitAnimations];
-	
-	LOG_EXIT;
-}
-
-#pragma mark -
-#pragma mark Properties
-
-@synthesize tapZoomImageView = _tapZoomImageView;
-@synthesize zoomSlider = _zoomSlider;
-@synthesize image = _image;
-
-@end
-
-

Classes/RootViewController.h

 // $Author$
 // $Date$
 
-#import "TapDetectingImageView.h"
-
-@interface RootViewController : UIViewController <TapDetectingImageViewDelegate>
+@interface RootViewController : UIViewController
 {
 	UIView *portraitView, *landscapeView;
 }
 
+
 - (IBAction) showSmallImageInViewer:(id)sender;
 - (IBAction) showLargeImageInViewer:(id)sender;
 
 #pragma mark -
 #pragma mark Properties
 
+
 @property (nonatomic, retain) IBOutlet UIView *portraitView;
 @property (nonatomic, retain) IBOutlet UIView *landscapeView;
 

Classes/RootViewController.m

 // $Date$
 
 #import "RootViewController.h"
-#import "ImageViewController.h"
+#import "TapZoomDemoViewController.h"
+
+@interface RootViewController()
+@end
 
 @implementation RootViewController
 
 #pragma mark -
 #pragma mark NSObject Overrides
 
-- (void)dealloc 
+- (void) dealloc 
 {	
 	// We're no longer interested in any notifications.
 	[[NSNotificationCenter defaultCenter] removeObserver:self];
-
-    [super dealloc];
 }
 
 #pragma mark -
 	 Using UIDeviceOrientationDidChangeNotification, when an image viewer controller is on top of the stack and the device is rotated, this root view controller is also notified of the orientation change.  We take advanatage of this to enable a smooth UI experience when switching between root controller and image viewer controller after any change in device orientation.
 	 */
 	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
+    
+    /* Add tap recognizers on the image views to give them actions. */
+    
+    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showSmallImageInViewer:)];
+    [[[self view] viewWithTag:100] addGestureRecognizer:singleTap];
+    [[[self view] viewWithTag:300] addGestureRecognizer:singleTap];
+    singleTap = nil;
+    
+    singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showLargeImageInViewer:)];
+    [[[self view] viewWithTag:200] addGestureRecognizer:singleTap];
+    [[[self view] viewWithTag:400] addGestureRecognizer:singleTap];
+    singleTap = nil;
 }
 
 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
 
 - (IBAction) showSmallImageInViewer:(id)sender;
 {
-	ImageViewController * imageViewer = [[ImageViewController new] autorelease];
+	TapZoomDemoViewController * imageViewer = [TapZoomDemoViewController new];
 	imageViewer.image = [UIImage imageNamed:@"small.png"];
 	[self.navigationController pushViewController:imageViewer animated:YES];
 }
 
 - (IBAction) showLargeImageInViewer:(id)sender;
 {
-	id imageViewer = [[[ImageViewController alloc] initWithImage:[UIImage imageNamed:@"large.png"]] autorelease];
+	id imageViewer = [[TapZoomDemoViewController alloc] initWithImage:[UIImage imageNamed:@"large.png"]];
 	[self.navigationController pushViewController:imageViewer animated:YES];
 }
 
 #pragma mark -
 #pragma mark TapDetectingImageViewDelegate Methods
 
+#if 0
 - (void)tapDetectingImageView:(TapDetectingImageView *)view gotSingleTapAtPoint:(CGPoint)tapPoint;
 {
 	LOG_ENTRY;
 		[self showLargeImageInViewer:nil];
 	}
 }
+#endif
 
 #pragma mark -
 #pragma mark Properties

Classes/SOPanZoomImageView.h

+//
+//
+//  Created on 01/06/09.
+//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
+//	LICENSE <http://creativecommons.org/licenses/BSD/>
+
+
+/**
+Subclass of UIScrollView that handles zooming and centering an embedded image view.
+
+Zooming Methods:
+-zoomToCenterWithScale:animated: zooms the image view to the given scale factor, and keeps the image centered when it is smaller than the scroll view bounds.
+
+-configureZoomingScale recalculates minimum, maximum, and the necessary zoom-to-fit scale factors as determined by the size of the image compared to the scroll view and the device's  orientation.
+
+ -isZoomedToFit reports when the scroll view is zoomed such that the image just fits on the screen in its device orientation.
+
+*/
+
+@interface SOPanZoomImageView : UIScrollView
+{
+@private    
+	
+	UIImageView *_imageView;
+	UIImage *_image;
+	
+	// Zooming
+	CGFloat _zoomToFitScaleFactor;
+}
+
+#pragma mark -
+#pragma mark Zooming and Centering
+
+/**
+\brief Update the scroll view's content inset to center the zoomable view.
+
+When the image view is smaller than the receiver's content area, this method calculates and sets the content edge insets required to keep the view centered.
+*/
+- (void) updateContentInsetForCentering;
+
+
+/**
+ Zoom and center the image view.
+ \param animate YES to animate the zoom and centering.
+ */
+- (void) zoomToCenterWithScale:(CGFloat)scale animated:(BOOL)animate;
+
+/**
+ The zoom scale required to fit the image completely within the scroll view's bounds.
+ The value is recalculated whenever the device changes between horizontal and vertical orientation. 
+ */
+- (void) configureZoomingScale;
+
+
+/**
+ \return YES if scroll view zoom is at the zoom-to-fit value for the current orientation.
+ */
+- (BOOL) isZoomedToFit;
+
+#pragma mark -
+#pragma mark Properties
+
+/** The Image we're displaying. */
+@property (nonatomic, retain) UIImage *image;
+
+/** The embedded image view.
+*/
+@property (nonatomic, readonly) UIImageView *imageView;
+
+
+/** 
+\brief The zoom scale required to fit the image completely within the scroll view's bounds.
+\desc This zoom scale can be used to zoom the scroll view so that the image just fits within the display. The value is recalculated whenever the device changes between horizontal and vertical orientation. 
+ */
+@property (nonatomic, readonly) CGFloat zoomToFitScale;
+
+/**
+\return YES if the scroll view zooming scale is such that the image is zoomed to fit the current device orientation.
+*/
+@property (nonatomic, readonly) BOOL isZoomedToFit;
+
+@end
+
+#pragma mark -
+#pragma mark Constants
+
+extern NSString * const ScrollableImageViewZoomAndCenterAnimation;
+

Classes/SOPanZoomImageView.m

+
+//
+//  Created on 01/06/09.
+//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
+//	LICENSE <http://creativecommons.org/licenses/BSD/>
+
+#import "SOPanZoomImageView.h"
+
+#define ZOOM_MAX_FACTOR 10
+
+@implementation SOPanZoomImageView
+
+#pragma mark -
+#pragma mark Initialization
+
+- (void) commonInit;
+{
+	LOG_ENTRY;
+	
+	// Init an image view and install in the scroller
+	{
+		_imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
+		[_imageView setAutoresizingMask:UIViewAutoresizingNone];
+		[_imageView setUserInteractionEnabled:NO];
+		[_imageView setMultipleTouchEnabled:NO];
+	}
+	
+	// Init a scroll view and install in our view.
+	{		
+		// The scroll view resizes every which way but loose
+		[self setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+		[self setContentMode: UIViewContentModeRedraw];
+        
+		// Set contentSize so that scrollers accurately reflect the visible portion of the image.
+		self.contentSize = _imageView.frame.size;
+        
+		[self addSubview:_imageView];
+	}
+	
+	// We're very interactive
+	[self setUserInteractionEnabled:YES];
+	[self setMultipleTouchEnabled:YES];
+	
+	_zoomToFitScaleFactor = 1.0; // reasonable default
+	
+	// Listen for device orientation changes
+	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
+}
+
+- (void) awakeFromNib
+{	
+	LOG_ENTRY;
+	[super awakeFromNib];
+	[self commonInit];
+	
+    //	[self.imageView logResponderChain];
+}
+
+
+/** Our designated initializer */
+
+- (id) initWithImage:(UIImage *)image
+{
+	LOG_ENTRY;
+	CGRect frame = (CGRect){.size = image.size};
+	self = [super initWithFrame:frame];
+	if (!self) return nil;
+    
+    _image = image;
+    [self commonInit];
+	
+	return self;
+}
+
+// UIView designated initializer override
+- (id) initWithFrame:(CGRect)frame
+{
+	LOG_ENTRY;
+    return [self initWithImage:nil];
+}
+
+- (id) init;
+{
+	LOG_ENTRY;
+	return [self initWithFrame:CGRectZero];
+}
+
+- (void) dealloc;
+{
+	LOG_EXIT;
+	
+    _image = nil;
+    _imageView = nil;
+}
+
+#pragma mark -
+#pragma mark UIView Overrides
+
+- (void) willMoveToSuperview:(UIView *)newSuperview
+{
+	LOG_ENTRY;
+	
+	if ( newSuperview == nil ) 
+	{
+		// We're being pull out of the view hierarchy.  Cleanup this notification registration.
+		[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
+	}
+	
+	LOG_EXIT;
+}
+
+- (BOOL) isOpaque
+{
+	return YES;
+}
+
+// Must override -canBecomeFirstResponder so that we can participate in the responder chain
+- (BOOL) canBecomeFirstResponder 
+{
+    return YES;
+}
+
+#pragma mark -
+#pragma mark Observers and Updating
+
+// Here's where we change our root view to accomodate the new device orientation.
+
+- (void) orientationChanged:(NSNotification *)notification
+{
+	LOG_ENTRY;
+	
+	BOOL isLandscape = UIDeviceOrientationIsLandscape( [[UIDevice currentDevice] orientation] );
+	BOOL isPortrait = UIDeviceOrientationIsPortrait( [[UIDevice currentDevice] orientation] );
+#if DEBUG
+	NSLog(@"isLandscape: %@", StringFromBool(isLandscape));
+	NSLog(@"isPortrait: %@", StringFromBool(isPortrait));
+#endif
+    
+	if ( ! (isLandscape || isPortrait) ) {
+		// Only respond to this orientation change if the device has switched from portrait to landscape orientation, or vice-versa.  Ignore other orientations.
+		return;
+	}
+    
+	// Capture this condition before we recalculate ZTF.
+	BOOL isZTF = [self isZoomedToFit];
+	
+	// Resize our frame to match the orientation
+	self.frame = self.superview.bounds;
+	
+	// Reconfigure the min, max, and zoom-to-fit zooming scale values to accomodate the change in orientation.
+	[self configureZoomingScale];
+	
+	/* 
+     Special case when the zoom scale value is equal to zoom-to-fit: preserve the ZTF condition after changing orientation, as appropriate for the new orientation.  
+     That is, if the user has the image zoomed to fit in one orientation, it will continue to be zoomed to fit to the new orientation. 
+     The actual zoom scale value will change, but the image will be zoomed to fit as appropriate for the new landscape or portrait orientation.
+     */
+	
+	if ( isZTF ) 
+    {
+		// Always keep zoomed-to-fit
+		[self zoomToCenterWithScale: self.zoomToFitScale animated:YES];
+	} 
+    else
+    {
+		// Keep the user's current zoom value.
+		[self zoomToCenterWithScale:self.zoomScale animated:YES];
+	}
+	
+}
+
+- (void) updateLayoutForNewImage
+{
+	// Install the new image in the image view.
+	self.imageView.image = self.image;
+	[self.imageView sizeToFit];
+	
+	// Center the image
+	[self configureZoomingScale];
+	[self zoomToCenterWithScale:self.zoomToFitScale animated:YES];
+	
+	// Update the scroll view's content size so that scroll bars are properly sized.
+	self.contentSize = self.imageView.frame.size;	
+}
+
+#pragma mark -
+#pragma mark Zooming and Centering
+
+- (void) updateContentInsetForCentering
+{	
+	//	LOG_ENTRY;
+	
+	// Calculate edge insets to keep the image view centered
+	
+	UIEdgeInsets insetForCentering = { 0, 0, 0, 0};
+	{		
+		
+		// Does frame need to be centered horizontally?  If it is less wide than the scrollview width, yes.
+		CGFloat deltaWidth = CGRectGetWidth(self.bounds) - CGRectGetWidth(self.imageView.frame);
+		CGFloat deltaHeight = CGRectGetHeight(self.bounds) - CGRectGetHeight(self.imageView.frame);
+		
+		if (deltaWidth > 0)
+		{
+			// Split the delta width between the left and right sides
+			insetForCentering.left = deltaWidth / 2.0;
+			insetForCentering.right = -insetForCentering.left;
+		}
+		
+		// Does frame need to be centered vertically? If it is less tall than the scrollview height, yes.
+		if (deltaHeight > 0)
+		{
+			// Split the delta height between the top and bottom sides
+			insetForCentering.top = deltaHeight / 2.0;
+			insetForCentering.bottom = -insetForCentering.top;
+		}
+	}
+	
+	// Inset the image view within the scroll view to center it
+	if ( ! UIEdgeInsetsEqualToEdgeInsets (insetForCentering, self.contentInset)) 
+    {
+		self.contentInset = insetForCentering;
+	}
+}
+
+- (void) zoomToCenterWithScale:(CGFloat)scale animated:(BOOL)animate;
+{
+	// If animating, start an animation block.  
+	
+	// Future Developer: Note that the zoom and the centering are both animated when enclosed in the animation block.
+	// If you remove the use of the block and just ask for the zoom to animate, the resulting UI is jerky. 
+	
+	if (animate) [UIView beginAnimations:ScrollableImageViewZoomAndCenterAnimation context:NULL];
+	
+	// Re-zoom the image view and re-center in the scroll view
+	
+	[self setZoomScale:scale animated:NO];
+	[self updateContentInsetForCentering];
+	
+	// If animating, kick off the animations.
+	if (animate) [UIView commitAnimations];
+}
+
+- (void) configureZoomingScale;
+{
+	LOG_ENTRY;
+	
+	// Configure the min, max, and zoom-to-fit zooming scale values.
+	
+	//NSLog(@"Before calculation: _zoomToFitScale : %f", _zoomToFitScale);
+	
+	BOOL isLandscape = UIDeviceOrientationIsLandscape( [[UIDevice currentDevice] orientation] );
+	if (isLandscape) 
+    {
+		// Display height is the constraining dimension.
+		_zoomToFitScaleFactor = CGRectGetHeight(self.frame) / self.imageView.image.size.height;
+	} 
+    else 
+    {
+		// Display width is the constraining dimension.
+		_zoomToFitScaleFactor = CGRectGetWidth(self.frame) / self.imageView.image.size.width;
+	}
+	
+	// Set the zoom scale, determining the scale factor that makes the image zoom-to-fit the display (ZTF)
+    // For small images (i.e. images that will naturally fit within the bounds of the scroll view), use 1.0
+
+    self.minimumZoomScale = 1.0;
+
+	if (_zoomToFitScaleFactor < 1.0)
+    {
+		// For large images, the zoom-to-fit scale value will be the minimum
+		self.minimumZoomScale = _zoomToFitScaleFactor;
+	}
+
+	
+	self.maximumZoomScale = ZOOM_MAX_FACTOR;
+	
+	//NSLog(@"After calculation: _zoomToFitScale: %f", _zoomToFitScale);
+}
+
+#pragma mark -
+#pragma mark Properties
+
+@synthesize image = _image;
+@synthesize imageView = _imageView;
+@synthesize zoomToFitScale = _zoomToFitScaleFactor;
+
+- (BOOL) isZoomedToFit;
+{
+	BOOL isZTF = fabs(self.zoomScale - self.zoomToFitScale) < 0.00001;
+	return isZTF;
+}
+
+- (void) setImage:(UIImage *)newImage;
+{
+	if ( newImage != _image ) 
+    {
+		_image = newImage;
+		
+		[self updateLayoutForNewImage];
+	}
+}
+
+@end
+
+#pragma mark -
+#pragma mark Constants
+
+NSString * const ScrollableImageViewZoomAndCenterAnimation = @"ZoomAndCenterAnimation";

Classes/TapDetectingImageView.h

-
-/*
-     File: TapDetectingImageView.h
- Abstract: UIImageView subclass that responds to taps and notifies its delegate.
- 
-  Version: 1.1
- 
- Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
- Inc. ("Apple") in consideration of your agreement to the following
- terms, and your use, installation, modification or redistribution of
- this Apple software constitutes acceptance of these terms.  If you do
- not agree with these terms, please do not use, install, modify or
- redistribute this Apple software.
- 
- In consideration of your agreement to abide by the following terms, and
- subject to these terms, Apple grants you a personal, non-exclusive
- license, under Apple's copyrights in this original Apple software (the
- "Apple Software"), to use, reproduce, modify and redistribute the Apple
- Software, with or without modifications, in source and/or binary forms;
- provided that if you redistribute the Apple Software in its entirety and
- without modifications, you must retain this notice and the following
- text and disclaimers in all such redistributions of the Apple Software.
- Neither the name, trademarks, service marks or logos of Apple Inc. may
- be used to endorse or promote products derived from the Apple Software
- without specific prior written permission from Apple.  Except as
- expressly stated in this notice, no other rights or licenses, express or
- implied, are granted by Apple herein, including but not limited to any
- patent rights that may be infringed by your derivative works or by other
- works in which the Apple Software may be incorporated.
- 
- The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
- MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
- THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
- FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
- OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
- 
- IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
- MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
- AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
- STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
- POSSIBILITY OF SUCH DAMAGE.
- 
- Copyright (C) 2009 Apple Inc. All Rights Reserved.
- 
- */
-
-@protocol TapDetectingImageViewDelegate;
-
-
-@interface TapDetectingImageView : UIImageView {
-	
-    id <TapDetectingImageViewDelegate> delegate;
-    
-    // Touch detection
-    CGPoint tapLocation;         // Needed to record location of single tap, which will only be registered after delayed perform.
-    BOOL multipleTouches;        // YES if a touch event contains more than one touch; reset when all fingers are lifted.
-    BOOL twoFingerTapIsPossible; // Set to NO when 2-finger tap can be ruled out (e.g. 3rd finger down, fingers touch down too far apart, etc).
-}
-
-@property (nonatomic, assign) id <TapDetectingImageViewDelegate> delegate;
-
-@end
-
-
-/*
- Protocol for the tap-detecting image view's delegate.
- */
-@protocol TapDetectingImageViewDelegate <NSObject>
-
-@optional
-- (void)tapDetectingImageView:(TapDetectingImageView *)view gotSingleTapAtPoint:(CGPoint)tapPoint;
-- (void)tapDetectingImageView:(TapDetectingImageView *)view gotDoubleTapAtPoint:(CGPoint)tapPoint;
-- (void)tapDetectingImageView:(TapDetectingImageView *)view gotTwoFingerTapAtPoint:(CGPoint)tapPoint;
-
-@end
-

Classes/TapDetectingImageView.m

-
-/*
-     File: TapDetectingImageView.m
- Abstract: UIImageView subclass that responds to taps and notifies its delegate.
- 
-  Version: 1.1
- 
- Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
- Inc. ("Apple") in consideration of your agreement to the following
- terms, and your use, installation, modification or redistribution of
- this Apple software constitutes acceptance of these terms.  If you do
- not agree with these terms, please do not use, install, modify or
- redistribute this Apple software.
- 
- In consideration of your agreement to abide by the following terms, and
- subject to these terms, Apple grants you a personal, non-exclusive
- license, under Apple's copyrights in this original Apple software (the
- "Apple Software"), to use, reproduce, modify and redistribute the Apple
- Software, with or without modifications, in source and/or binary forms;
- provided that if you redistribute the Apple Software in its entirety and
- without modifications, you must retain this notice and the following
- text and disclaimers in all such redistributions of the Apple Software.
- Neither the name, trademarks, service marks or logos of Apple Inc. may
- be used to endorse or promote products derived from the Apple Software
- without specific prior written permission from Apple.  Except as
- expressly stated in this notice, no other rights or licenses, express or
- implied, are granted by Apple herein, including but not limited to any
- patent rights that may be infringed by your derivative works or by other
- works in which the Apple Software may be incorporated.
- 
- The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
- MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
- THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
- FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
- OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
- 
- IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
- MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
- AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
- STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
- POSSIBILITY OF SUCH DAMAGE.
- 
- Copyright (C) 2009 Apple Inc. All Rights Reserved.
- 
- */
-
-#import "TapDetectingImageView.h"
-
-#define DOUBLE_TAP_DELAY 0.35
-
-CGPoint midpointBetweenPoints(CGPoint a, CGPoint b);
-
-@interface TapDetectingImageView ()
-- (void)handleSingleTap;
-- (void)handleDoubleTap;
-- (void)handleTwoFingerTap;
-@end
-
-@implementation TapDetectingImageView
-@synthesize delegate;
-
-- (id)initWithImage:(UIImage *)image {
-    self = [super initWithImage:image];
-    if (self) {
-        [self setUserInteractionEnabled:YES];
-        [self setMultipleTouchEnabled:YES];
-        twoFingerTapIsPossible = YES;
-        multipleTouches = NO;
-    }
-    return self;
-}
-
-- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
-{
-    // cancel any pending handleSingleTap messages 
-    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleSingleTap) object:nil];
-    
-    // update our touch state
-    if ([[event touchesForView:self] count] > 1)
-        multipleTouches = YES;
-    if ([[event touchesForView:self] count] > 2)
-        twoFingerTapIsPossible = NO;
-    
-}
-
-- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
-{
-    BOOL allTouchesEnded = ([touches count] == [[event touchesForView:self] count]);
-    
-    // first check for plain single/double tap, which is only possible if we haven't seen multiple touches
-    if (!multipleTouches) {
-        UITouch *touch = [touches anyObject];
-        tapLocation = [touch locationInView:self];
-        
-        if ([touch tapCount] == 1) {
-            [self performSelector:@selector(handleSingleTap) withObject:nil afterDelay:DOUBLE_TAP_DELAY];
-        } else if([touch tapCount] == 2) {
-            [self handleDoubleTap];
-        }
-    }    
-    
-    // check for 2-finger tap if we've seen multiple touches and haven't yet ruled out that possibility
-    else if (multipleTouches && twoFingerTapIsPossible) { 
-        
-        // case 1: this is the end of both touches at once 
-        if ([touches count] == 2 && allTouchesEnded) {
-            int i = 0; 
-            int tapCounts[2]; CGPoint tapLocations[2];
-            for (UITouch *touch in touches) {
-                tapCounts[i]    = [touch tapCount];
-                tapLocations[i] = [touch locationInView:self];
-                i++;
-            }
-            if (tapCounts[0] == 1 && tapCounts[1] == 1) { // it's a two-finger tap if they're both single taps
-                tapLocation = midpointBetweenPoints(tapLocations[0], tapLocations[1]);
-                [self handleTwoFingerTap];
-            }
-        }
-        
-        // case 2: this is the end of one touch, and the other hasn't ended yet
-        else if ([touches count] == 1 && !allTouchesEnded) {
-            UITouch *touch = [touches anyObject];
-            if ([touch tapCount] == 1) {
-                // if touch is a single tap, store its location so we can average it with the second touch location
-                tapLocation = [touch locationInView:self];
-            } else {
-                twoFingerTapIsPossible = NO;
-            }
-        }
-
-        // case 3: this is the end of the second of the two touches
-        else if ([touches count] == 1 && allTouchesEnded) {
-            UITouch *touch = [touches anyObject];
-            if ([touch tapCount] == 1) {
-                // if the last touch up is a single tap, this was a 2-finger tap
-                tapLocation = midpointBetweenPoints(tapLocation, [touch locationInView:self]);
-                [self handleTwoFingerTap];
-            }
-        }
-    }
-        
-    // if all touches are up, reset touch monitoring state
-    if (allTouchesEnded) {
-        twoFingerTapIsPossible = YES;
-        multipleTouches = NO;
-    }
-}
-
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
-    twoFingerTapIsPossible = YES;
-    multipleTouches = NO;
-}
-
-#pragma mark Private
-
-- (void)handleSingleTap {
-    if ([delegate respondsToSelector:@selector(tapDetectingImageView:gotSingleTapAtPoint:)])
-        [delegate tapDetectingImageView:self gotSingleTapAtPoint:tapLocation];
-}
-
-- (void)handleDoubleTap {
-    if ([delegate respondsToSelector:@selector(tapDetectingImageView:gotDoubleTapAtPoint:)])
-        [delegate tapDetectingImageView:self gotDoubleTapAtPoint:tapLocation];
-}
-    
-- (void)handleTwoFingerTap {
-    if ([delegate respondsToSelector:@selector(tapDetectingImageView:gotTwoFingerTapAtPoint:)])
-        [delegate tapDetectingImageView:self gotTwoFingerTapAtPoint:tapLocation];
-}
-    
-@end
-
-CGPoint midpointBetweenPoints(CGPoint a, CGPoint b) {
-    CGFloat x = (a.x + b.x) / 2.0;
-    CGFloat y = (a.y + b.y) / 2.0;
-    return CGPointMake(x, y);
-}
-                    

Classes/TapDetectingScrollView.h

-//
-//  TapZoomImageView.h
-//  TouchImageViewerDemo
-//
-//  Created on 01/06/09.
-//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
-//	LICENSE <http://creativecommons.org/licenses/BSD/>
-//
-// $Revision$
-// $Author$
-// $Date$
-
-/**
-TapDetectingScrollView is based on the TapDetectingImageView class from the Apple sample code project ScrollViewSuite.
-<http://developer.apple.com/iPhone/library/samplecode/ScrollViewSuite/>.
-
-It enables the detection and handling of single and double taps with one finger, and a double-finder tap in the scroll view.
-The delegate of the scroll view implements the methods defined in TapDetectingScrollViewDelegate protocol to handle the detected taps.
-*/
-
-@interface TapDetectingScrollView : UIScrollView 
-{
-@private	    
-    // Touch detection
-    CGPoint _tapLocation;         // Needed to record location of single tap, which will only be registered after delayed perform.
-	
-	NSSet *_twoFingerTapTouchesBegan; // Collect the touches of a two-finger tap at the start of the gesture.
-	NSMutableSet *_twoFingerTapTouchesEnded; // Collects each touch of a two-finger tap as they end, possible one finger at a time.
-}
-@end
-
-#pragma mark -
-
-/* Protocol for the tap-detecting image view's delegate. */
-@protocol TapDetectingScrollViewDelegate <UIScrollViewDelegate>
-
-@optional
-- (void) tapDetectingScrollView:(TapDetectingScrollView *)scrollView gotSingleTapAtPoint:(CGPoint)tapPoint;
-- (void) tapDetectingScrollView:(TapDetectingScrollView *)scrollView gotDoubleTapAtPoint:(CGPoint)tapPoint;
-- (void) tapDetectingScrollView:(TapDetectingScrollView *)scrollView gotTwoFingerTapAtPoint:(CGPoint)tapPoint;
-
-@end

Classes/TapDetectingScrollView.m

-//
-//  TapZoomImageView.h
-//  TouchImageViewerDemo
-//
-//  Created on 01/06/09.
-//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
-//	LICENSE <http://creativecommons.org/licenses/BSD/>
-//
-// $Revision$
-// $Author$
-// $Date$
-
-#import "TapDetectingScrollView.h"
-
-#define TAP_DELAY 0.40
-
-static inline CGPoint midpointBetweenPoints(CGPoint a, CGPoint b)
-{
-    CGFloat x = (a.x + b.x) / 2.0;
-    CGFloat y = (a.y + b.y) / 2.0;
-    return CGPointMake(x, y);
-}
-
-@interface TapDetectingScrollView ()
-- (void)handleSingleTap;
-- (void)handleDoubleTap;
-- (void)handleTwoFingerTap;
-@end
-
-@interface TapDetectingScrollView()
-@property (nonatomic, retain) NSSet *beginningTwoFingerTapTouches;
-@property (nonatomic, retain) NSMutableSet *endingTwoFinderTapTouches;
-@end
-
-
-@implementation TapDetectingScrollView
-
-#pragma mark -
-#pragma mark UIView Overrides
-
-- (id) initWithFrame:(CGRect)frame
-{
-    self = [super initWithFrame:frame];
-    if (self) {
-		
-		// The scroll view resizes every which way but loose
-		[self setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-		
-		// We're very interactive
-        [self setUserInteractionEnabled:YES];
-        [self setMultipleTouchEnabled:YES];
-    }
-    return self;
-}
-
-- (void) dealloc;
-{
-	LOG_EXIT;
-	[super dealloc];
-}
-
-- (void) willMoveToSuperview:(UIView *)newSuperview;
-{
-	if ( newSuperview == nil ) 
-	{
-		// Cleanup ivars when view is going away
-		ReleaseAndNil( _twoFingerTapTouchesBegan );
-		ReleaseAndNil( _twoFingerTapTouchesEnded );
-	}
-}
-
-
-#pragma mark -
-#pragma mark UIResponder Overrides
-
-
--(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
-{	
-	LOG_ENTRY;
-	NSSet *ourTouches = [event touchesForView:self];
-	
-#if DEBUG
-	NSLog(@"-----------");
-	NSLog(@"# of total touches: %d", [touches count]);
-	for ( UITouch *touch in ourTouches ) {
-		NSLog(@"touch <%p> tapCount: %d", touch, [touch tapCount]);
-	}
-#endif
-	
-	// Reset these two-finger tap-detecting properties at the beginning of any new touch.  When a new touch begins, any previous two-finger tap is cancelled.  
-	self.beginningTwoFingerTapTouches = nil;
-	self.endingTwoFinderTapTouches = nil;
-	/* NOTE,  FUTURE DEVELOPER!
-	 Two finger taps usually begin with two touchs down, but often end one touch at a time.  You need to distinguish when an ending touch is part of a two-finger-tap, a single-finger-tap, or something else.  In this code, a non-nil value for self.beginningTwoFingerTapTouches is used as an indicator that we've detected the start of a two-finger tap.  
-	 
-	 -touchesEnded: will be invoked twice when a two-finger tap ends one finger at a time.  To prevent the second ending touch from being treated as the end of a single tap, we branch there on the value of self.beginningTwoFingerTapTouches:  non-nil means the ending touch is part of a two-finger tap;  a nil value means that it isn't.
-	 */
-	
-	// Detect two-finger tap at the start of the sequence
-	
-	BOOL isTwoFingerTap = ([ourTouches count] == 2) && [touches isEqualToSet:ourTouches];
-	if ( isTwoFingerTap ) 
-	{
-		// Both touches should have ended with tap count of 1 in order to consider this a two-finger tap.
-		for ( UITouch *touch in ourTouches ) {
-			isTwoFingerTap = isTwoFingerTap && ([touch tapCount] == 1);
-		}
-		
-#if DEBUG
-		NSLog(@"isTwoFingerTap? %@", StringFromBool(isTwoFingerTap));
-#endif
-		
-		if ( isTwoFingerTap ) 
-		{
-			self.beginningTwoFingerTapTouches = [NSSet setWithSet:ourTouches];
-			self.endingTwoFinderTapTouches = [NSMutableSet set];
-		} 
-	}
-	
-	else {
-		[super touchesBegan:touches withEvent:event];
-	}
-	
-	LOG_EXIT;
-}
-
-- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
-{
-	LOG_ENTRY;
-	
-	NSSet *ourTouches = [event touchesForView:self];
-	
-	// Handle two-finger tap first.
-	
-	if ( self.beginningTwoFingerTapTouches ) 
-	{		
-		/* NOTE, FUTURE DEVELOPER!
-		 -touchesEnded: will be invoked twice when a two-finger tap ends one finger at a time.  To prevent the second ending touch from being treated as the end of a single tap, we branch there on the value of self.beginningTwoFingerTapTouches:  non-nil means the ending touch is part of a two-finger tap;  a nil value means that it isn't.  self.beginningTwoFingerTapTouches is nil'd out at the top of every new -touchesBegan: to make this work.
-		 */
-		
-		// We're only interested in the touches in our view...
-		
-		if ( [ourTouches count] == 2 ) 
-		{
-			// Both touches ended together
-			[self.endingTwoFinderTapTouches addObjectsFromArray:[ourTouches allObjects]];
-		}
-		else if ( [ourTouches count] == 1 ) 
-		{
-			// If this is one of our two-finger taps ending, collect it.
-			UITouch *touch = [ourTouches anyObject];
-			
-			if ( [self.beginningTwoFingerTapTouches containsObject:touch] ) 
-			{
-				[self.endingTwoFinderTapTouches addObject:touch];
-			}
-		}
-		
-		
-		// Do we have two tap touches collected for the two-finger tap?  If so, then fire it off.
-		if ( [self.endingTwoFinderTapTouches count] == 2 ) 
-		{
-			// Check if both touches are taps.  If so, then we have a two-finger tap.
-			BOOL isTwoFingerTap = YES;
-			for ( UITouch *touch in self.endingTwoFinderTapTouches ) 
-			{
-				isTwoFingerTap = isTwoFingerTap && [touch tapCount] == 1;
-			}
-			
-			if ( isTwoFingerTap )  
-			{
-				// Use the midpoint between the two touches as the location of the two-finger tap.
-				NSArray *taps = [self.endingTwoFinderTapTouches allObjects];
-				UITouch *firstFinger = [taps objectAtIndex:0];
-				UITouch *secondFinger = [taps objectAtIndex:1];
-				_tapLocation = midpointBetweenPoints( [firstFinger locationInView:self], [secondFinger locationInView:self]);
-				
-				[self handleTwoFingerTap];
-			}
-		}
-		
-		// Short-circuit return
-		LOG_EXIT;
-		return;
-	}
-	
-	// Handle single-finger taps
-	
-	BOOL singleFingerTapDetected = [ourTouches count] == 1;
-	if ( singleFingerTapDetected ) 
-	{
-		NSUInteger tapCount = [[ourTouches anyObject] tapCount];
-		switch ( tapCount ) {
-			case 1:
-				[self performSelector:@selector(handleSingleTap) withObject:nil afterDelay:TAP_DELAY];
-				break;
-				
-			case 2:
-				[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleSingleTap) object:nil];
-				[self handleDoubleTap];
-				break;
-				
-			default:
-				// Pass handling off to UIScrollView
-				[super touchesBegan:touches withEvent:event];
-				break;
-		} // switch
-		
-		LOG_EXIT;
-		return;
-	}
-	
-	// If no taps (single- or double-finger) were handled above, pass handling off to UIScrollView
-	[super touchesBegan:touches withEvent:event];
-	
-	LOG_EXIT;
-}
-
-- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
-{
-	LOG_ENTRY;
-	
-	// Reset our tap-detection flags
-	//	_twoFingerTapHandled = NO;
-	//	_twoFingerTapDetected = NO;
-	self.beginningTwoFingerTapTouches = nil;
-	self.endingTwoFinderTapTouches = nil;
-	
-	[super touchesCancelled:touches withEvent:event];
-	
-	LOG_EXIT;
-}
-
-#pragma mark -
-#pragma mark Tap Handling
-
-- (void)handleSingleTap 
-{
-	id <TapDetectingScrollViewDelegate> tappingDelegate = (id <TapDetectingScrollViewDelegate>)self.delegate;
-	
-	if ( [tappingDelegate respondsToSelector:@selector(tapDetectingScrollView:gotSingleTapAtPoint:)])
-		[tappingDelegate tapDetectingScrollView:self gotSingleTapAtPoint:_tapLocation];
-}
-
-- (void)handleDoubleTap 
-{
-	id <TapDetectingScrollViewDelegate> tappingDelegate = (id <TapDetectingScrollViewDelegate>)self.delegate;
-	
-	if ( [tappingDelegate respondsToSelector:@selector(tapDetectingScrollView:gotDoubleTapAtPoint:)])
-		[tappingDelegate tapDetectingScrollView:self gotDoubleTapAtPoint:_tapLocation];
-}
-
-- (void)handleTwoFingerTap 
-{
-	id <TapDetectingScrollViewDelegate> tappingDelegate = (id <TapDetectingScrollViewDelegate>)self.delegate;
-	
-	if ( [tappingDelegate respondsToSelector:@selector(tapDetectingScrollView:gotTwoFingerTapAtPoint:)])
-	{
-		[tappingDelegate tapDetectingScrollView:self gotTwoFingerTapAtPoint:_tapLocation];
-	}
-}
-
-#pragma mark -
-#pragma mark Properties
-
-@synthesize beginningTwoFingerTapTouches = _twoFingerTapTouchesBegan;
-@synthesize endingTwoFinderTapTouches = _twoFingerTapTouchesEnded;
-
-@end

Classes/TapZoomDemoViewController.h

+//
+//  ImageViewController.h
+//  ImageViewController
+//
+//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
+//
+// LICENSE <http://creativecommons.org/licenses/BSD/>
+//
+// $Revision$
+// $Author$
+// $Date$
+
+/**
+ Implements a view controller that manages zooming and tapping in a scrollable image view.  The controller keeps the image centered within the scroll view when zooming happens.
+ 
+ The controller enables touch-interaction with the image view:
+ - When the controller is embedded within a navigation controller, single tap toggles the visibility of the navigation bar.
+ - Double-tap toggles image zooming between the image's original size and a zoom-to-fit-display size.
+ - Two-finger tap will zoom out the image by a stepping factor until the minimum zoom is reached.
+ 
+ When the original image size is larger than the device display, the minimum zoom scale will that value which zooms the image to fit the display.
+ When the image is smaller than the device's display, the minimum zoom scale will be 1.0: the original image size.
+ */
+
+#import <UIKit/UIKit.h>
+#import "SOPanZoomImageView.h"
+
+@interface TapZoomDemoViewController : UIViewController
+{
+}
+
+/** Designated initializer
+ \param image The image to be displayed. Should not be nil.
+ */
+- (id) initWithImage:(UIImage *)image;
+
+#pragma mark -
+#pragma mark Zooming and Scaling
+
+/** Action method to change the zoom on the image viewer
+ */
+- (IBAction) changeZoom:(id)sender;
+
+#pragma mark -
+#pragma mark Properties
+
+/** The Image we're displaying. */
+@property (nonatomic, strong) UIImage *image;
+
+@end
+
+#pragma mark -
+
+/* 
+ LICENSE
+ Copyright (c) 2009, Standard Orbit Software, LLC. All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ 
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ Neither the name of the Standard Orbit Software, LLC. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+

Classes/TapZoomDemoViewController.m

+//
+//  ImageViewController.m
+//  ImageViewController
+//
+//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
+//
+// LICENSE <http://creativecommons.org/licenses/BSD/>
+//
+// $Revision$
+// $Author$
+// $Date$
+
+#define DEBUG_ZOOMING 0 // set to 1 for additional zoom calculation debugging
+#define DEBUG_LAYOUT 0
+
+#define ZOOM_OUT_FACTOR 2
+#define ZOOM_MAX_FACTOR 10
+
+#import "TapZoomDemoViewController.h"
+#import "SOPanZoomImageView.h"
+
+@interface TapZoomDemoViewController()
+@property (nonatomic, strong) IBOutlet SOPanZoomImageView *tapZoomImageView;
+@property (nonatomic, strong) IBOutlet UISlider *zoomSlider;
+- (void) updateZoomSliderUI;
+@end
+
+@implementation TapZoomDemoViewController
+
+#pragma mark -
+#pragma mark Properties
+
+@synthesize tapZoomImageView = _tapZoomImageView;
+@synthesize zoomSlider = _zoomSlider;
+@synthesize image = _image;
+
+#pragma mark -
+#pragma mark Initializers
+
+// Designated initializer
+
+- (id) initWithImage:(UIImage *)image;
+{
+	LOG_ENTRY;
+	self = [super initWithNibName:@"ImageViewController" bundle:nil];
+	if ( !self ) return nil;
+	
+	_image = image;
+	
+	return self;
+}
+
+- (id) init;
+{
+	return [self initWithImage:nil];
+}
+
+#pragma mark -
+#pragma mark Memory Management
+
+- (void) dealloc 
+{
+    [self viewDidUnload];
+    
+	 _image = nil;
+	
+	LOG_EXIT;
+}
+
+- (void) viewDidUnload
+{
+    [super viewDidUnload];
+    
+    _tapZoomImageView = nil;
+    _zoomSlider = nil;
+}
+
+
+- (void) didReceiveMemoryWarning 
+{
+	// Releases the view if it doesn't have a superview.
+    [super didReceiveMemoryWarning];
+}
+
+#pragma mark -
+#pragma mark UIViewController Overrides
+
+- (void) viewDidLoad
+{
+    [super viewDidLoad];
+    
+	LOG_ENTRY;
+		
+    [[self view] setUserInteractionEnabled:YES];
+    [[self view] setMultipleTouchEnabled:YES];
+    
+    UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
+    [doubleTap setNumberOfTapsRequired:2];
+    [_tapZoomImageView addGestureRecognizer:doubleTap];
+    
+    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
+    [_tapZoomImageView addGestureRecognizer:singleTap];
+    [singleTap requireGestureRecognizerToFail:doubleTap];
+    
+    UITapGestureRecognizer *twoFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerTap:)];
+    [twoFingerTap setNumberOfTapsRequired:2];
+    [twoFingerTap setNumberOfTouchesRequired:2];
+    [_tapZoomImageView addGestureRecognizer:twoFingerTap];
+    
+    /* Hint to ARC for cleanup */
+    doubleTap = nil;
+    singleTap = nil;
+    twoFingerTap = nil;
+    
+#if DEBUG_LAYOUT
+	self.imageView.backgroundColor = [UIColor blueColor];
+	self.view.backgroundColor = [UIColor greenColor];
+#endif
+}
+
+- (void) viewWillAppear:(BOOL)animated
+{
+	LOG_ENTRY;
+	
+	// Install the image and resize the image view to fit it.
+	self.tapZoomImageView.image = self.image;
+		
+	[self updateZoomSliderUI];
+}
+
+
+- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
+{
+	return YES;
+}
+
+- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
+{
+	self.zoomSlider.hidden = YES;
+}
+
+- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
+{
+	LOG_ENTRY;
+	
+	[self updateZoomSliderUI];
+	
+	if ( !self.navigationController.navigationBarHidden) 
+    {
+		// Restore the zoom slider if we're not hiding
+		self.zoomSlider.hidden = NO;
+	}
+}
+
+#pragma mark -
+#pragma mark Zoom Scaling
+
+- (IBAction) changeZoom:(id)sender;
+{
+	[self.tapZoomImageView zoomToCenterWithScale:self.zoomSlider.value animated:NO];
+}
+
+- (void) updateZoomSliderUI
+{
+	// Reposition the slider
+	
+	CGRect sliderFrame = self.zoomSlider.frame;
+	sliderFrame.origin.y = CGRectGetHeight(self.view.frame) - 25.0;
+	self.zoomSlider.frame = sliderFrame;
+	
+	// Update the scale on the slider
+	self.zoomSlider.maximumValue = self.tapZoomImageView.maximumZoomScale;
+	self.zoomSlider.minimumValue = self.tapZoomImageView.minimumZoomScale;
+	[self.zoomSlider setValue:self.tapZoomImageView.zoomScale animated:YES];
+	
+}
+
+#pragma mark -
+#pragma mark UIScrollViewDelegate methods
+
+/*
+UIScrollView implements pinch-to-zoom behavior using two delegate methods: -viewForZoomingInScrollView: and -scrollViewDidEndZooming:withView:atScale:
+*/
+
+- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView 
+{
+	ASSERT( self.tapZoomImageView == scrollView );
+    return [self.tapZoomImageView imageView];
+}
+
+- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale 
+{	
+	LOG_ENTRY;
+	
+	ASSERT( self.tapZoomImageView == scrollView );
+
+	// Always reset the scroll view content size after zooming, so that scroll bars are rescaled appropriately.
+	scrollView.contentSize = view.frame.size;
+	
+	// Re-center the image view
+	[self.tapZoomImageView updateContentInsetForCentering];
+	
+	// Update the zoom slider control after a user has performed a pinch-zoom
+	[self.zoomSlider setValue:scale animated:YES];
+
+	LOG_EXIT;
+}
+
+
+#pragma mark -
+#pragma mark Tap Gesture Handlers methods
+
+/*
+For single taps, we take the image full screen, hiding the navigation controller and auxillary controls (the zoom slider).
+For double-taps, we toggle zooming the display between the image's native size and it's zoom-to-fit-the-display scale factor.
+For two-finger taps, we incrementally zoom out from the image until we reach the minimum scale factor.
+*/
+- (void) handleSingleTap:(id)sender
+{		
+	// Toggle the nav controller hidden or visible.
+	
+	[self.navigationController setNavigationBarHidden:!self.navigationController.isNavigationBarHidden animated:YES];
+	
+	// Update the zoom slider
+	if ( self.navigationController.isNavigationBarHidden ) 
+    {
+		[self.zoomSlider setValue:_tapZoomImageView.zoomScale animated:NO];
+		self.zoomSlider.hidden = YES;
+	} 
+    else
+    {
+		self.zoomSlider.hidden = NO;
+		[self.zoomSlider setValue:_tapZoomImageView.zoomScale animated:YES];
+	}
+	
+	// Rezoom the image to accomodate the presence/absence of the nav bar.
+	[self.tapZoomImageView zoomToCenterWithScale:_tapZoomImageView.zoomScale animated:NO];
+	
+	[self updateZoomSliderUI];
+}
+
+- (void) handleDoubleTap:(id)sender
+{	
+	BOOL isZoomedToOriginalSize = fabs(_tapZoomImageView.zoomScale - 1.0) < 0.0001;
+	
+	if ( isZoomedToOriginalSize ) {
+		// Rezoom to fit the display bounds
+		[self.tapZoomImageView zoomToCenterWithScale: self.tapZoomImageView.zoomToFitScale animated:YES];
+	} else {
+		// Rezoom to the image's original size
+		[self.tapZoomImageView zoomToCenterWithScale:1.0 animated:YES];
+	}
+	
+	// Update the zoom slider
+	[self.zoomSlider setValue:_tapZoomImageView.zoomScale animated:YES];
+}
+
+- (void) handleTwoFingerTap:(id)sender
+{
+	LOG_ENTRY;
+	
+	// two-finger tap zooms the image back out.
+		
+	// If already at the minimum zoom factor, we're done.
+	BOOL isMinimallyZoomed = fabs(_tapZoomImageView.zoomScale - _tapZoomImageView.minimumZoomScale) < 0.0001;
+	if ( isMinimallyZoomed ) return;
+	
+	// Calculate the new zoom factor, ensuring it stays to the minimum
+	CGFloat newScale = _tapZoomImageView.zoomScale / ZOOM_OUT_FACTOR;
+	
+	if ( newScale < _tapZoomImageView.minimumZoomScale ) {
+		newScale = _tapZoomImageView.minimumZoomScale;
+	}
+	
+	[UIView beginAnimations:nil context:NULL];
+	{
+		// Zoom the image to the tap point at the new scale value.
+		[self.tapZoomImageView zoomToCenterWithScale:newScale animated:NO];
+
+		// Update the zoom slider
+		[self.zoomSlider setValue:_tapZoomImageView.zoomScale animated:NO];
+	}
+	
+	[UIView commitAnimations];
+	
+	LOG_EXIT;
+}
+
+
+@end
+
+

Classes/TapZoomImageView.h

-//
-//  TapZoomImageView.h
-//  TouchImageViewerDemo
-//
-//  Created on 01/06/09.
-//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
-//	LICENSE <http://creativecommons.org/licenses/BSD/>
-//
-// $Revision$
-// $Author$
-// $Date$
-
-/**
-Subclass of TapDetectingScrollView that handles zooming and centering an embedded image view.
-
-Zooming Methods:
--zoomToCenterWithScale:animated: zooms the image view to the given scale factor, and keeps the image centered when it is smaller than the scroll view bounds.
-
--configureZoomingScale recalculates minimum, maximum, and the necessary zoom-to-fit scale factors as determined by the size of the image compared to the scroll view and the device's  orientation.
-
- -isZoomedToFit reports when the scroll view is zoomed such that the image just fits on the screen in its device orientation.
-
-*/
-#import "TapDetectingScrollView.h"
-
-@interface TapZoomImageView : TapDetectingScrollView
-{
-@private    
-	
-	UIImageView *_imageView;
-	UIImage *_image;
-	
-	// Zooming
-	CGFloat _zoomToFitScaleFactor;
-}
-
-#pragma mark -
-#pragma mark Zooming and Centering
-
-/**
-\brief Update the scroll view's content inset to center the zoomable view.
-
-When the image view is smaller than the receiver's content area, this method calculates and sets the content edge insets required to keep the view centered.
-*/
-- (void) updateContentInsetForCentering;
-
-
-/**
- Zoom and center the image view.
- \param animate YES to animate the zoom and centering.
- */
-- (void) zoomToCenterWithScale:(CGFloat)scale animated:(BOOL)animate;
-
-/**
- The zoom scale required to fit the image completely within the scroll view's bounds.
- The value is recalculated whenever the device changes between horizontal and vertical orientation. 
- */
-- (void) configureZoomingScale;
-
-
-/**
- \return YES if scroll view zoom is at the zoom-to-fit value for the current orientation.
- */
-- (BOOL) isZoomedToFit;
-
-#pragma mark -
-#pragma mark Properties
-
-/** The Image we're displaying. */
-@property (nonatomic, retain) UIImage *image;
-
-/** The embedded image view.
-*/
-@property (nonatomic, readonly) UIImageView *imageView;
-
-
-/** 
-\brief The zoom scale required to fit the image completely within the scroll view's bounds.
-\desc This zoom scale can be used to zoom the scroll view so that the image just fits within the display. The value is recalculated whenever the device changes between horizontal and vertical orientation. 
- */
-@property (nonatomic, readonly) CGFloat zoomToFitScale;
-
-/**
-\return YES if the scroll view zooming scale is such that the image is zoomed to fit the current device orientation.
-*/
-@property (nonatomic, readonly) BOOL isZoomedToFit;
-
-@end
-
-#pragma mark -
-#pragma mark Constants
-
-extern NSString * const ScrollableImageViewZoomAndCenterAnimation;
-

Classes/TapZoomImageView.m

-//
-//  TapZoomImageView.h
-//  TouchImageViewerDemo
-//
-//  Created on 01/06/09.
-//  Copyright Standard Orbit Software, LLC 2009. All rights reserved.
-//	LICENSE <http://creativecommons.org/licenses/BSD/>
-//
-// $Revision$
-// $Author$
-// $Date$
-
-#import "TapZoomImageView.h"
-
-#define ZOOM_MAX_FACTOR 10
-
-@implementation TapZoomImageView
-
-#pragma mark -
-#pragma mark Initialization
-
-- (void) commonInit;
-{
-	LOG_ENTRY;
-	
-	// Init an image view and install in the scroller
-	{
-		_imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
-		[_imageView setAutoresizingMask:UIViewAutoresizingNone];
-		[_imageView setUserInteractionEnabled:NO];
-		[_imageView setMultipleTouchEnabled:NO];
-	}
-	
-	// Init a scroll view and install in our view.
-	{		
-		// The scroll view resizes every which way but loose
-		[self setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-		[self setContentMode: UIViewContentModeRedraw];
-
-		// Set contentSize so that scrollers accurately reflect the visible portion of the image.
-		self.contentSize = _imageView.frame.size;
-
-		[self addSubview:_imageView];
-	}
-	
-	// We're very interactive
-	[self setUserInteractionEnabled:YES];
-	[self setMultipleTouchEnabled:YES];
-	
-	_zoomToFitScaleFactor = 1.0; // reasonable default
-	
-	// Listen for device orientation changes
-	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
-}
-
-- (void) awakeFromNib
-{	
-	LOG_ENTRY;
-	[super awakeFromNib];
-	[self commonInit];
-	
-//	[self.imageView logResponderChain];
-}
-
-
-/** Our designated initializer */
-
-- (id) initWithImage:(UIImage *)image
-{
-	LOG_ENTRY;
-	CGRect frame = CGRectMake(0., 0., image.size.width, image.size.height);
-	self = [super initWithFrame:frame];
-	if ( self ) {
-		_image = [image retain];
-		[self commonInit];
-	}
-	return self;
-}
-
-// UIView designated initializer override
-- (id) initWithFrame:(CGRect)frame
-{
-	LOG_ENTRY;
-    return [self initWithImage:nil];
-}
-
-- (id) init;
-{
-	LOG_ENTRY;
-	return [self initWithFrame:CGRectZero];
-}
-
-- (void) dealloc;
-{
-	LOG_EXIT;
-	
-	ReleaseAndNil( _image );
-	ReleaseAndNil( _imageView );
-	
-	[super dealloc];
-}
-
-#pragma mark -
-#pragma mark UIView Overrides
-
-- (void) willMoveToSuperview:(UIView *)newSuperview
-{
-	LOG_ENTRY;
-	
-	if ( newSuperview == nil ) 
-	{
-		// We're being pull out of the view hierarchy.  Cleanup this notification registration.
-		[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
-	}
-	
-	LOG_EXIT;
-}
-
-- (BOOL) isOpaque
-{
-	return YES;
-}
-
-// Must override -canBecomeFirstResponder so that we can participate in the responder chain
-- (BOOL) canBecomeFirstResponder 
-{
-    return YES;
-}
-
-#pragma mark -
-#pragma mark Observers and Updating
-
-// Here's where we change our root view to accomodate the new device orientation.
-
-- (void) orientationChanged:(NSNotification *)notification
-{
-	LOG_ENTRY;
-	
-	BOOL isLandscape = UIDeviceOrientationIsLandscape( [[UIDevice currentDevice] orientation] );
-	BOOL isPortrait = UIDeviceOrientationIsPortrait( [[UIDevice currentDevice] orientation] );
-#if DEBUG
-	NSLog(@"isLandscape: %@", StringFromBool(isLandscape));