MOBILE

Programming the iPhone : Standard Control Types (part 2) - Modal Buttons

8/4/2012 3:16:02 PM

2. Modal Buttons

Modal controls are UI controls that change state across two or more steps as part of a single expression of user intent. A good example of a modal control is the “Buy Now/Install/Installed” button in the mobile App Store application. You install applications from the App Store on the mobile device by tapping the button several times at specific points in the sequence.

Modal controls are very effective for operations that should be confirmed by users. They can also be excellent triggers when one of a set of options is available, depending on context. For example, the individual record screen in the Contacts application allows you to click an Edit button that changes the view to an editable view of the record. The Edit button then becomes a Done button that can be used to cancel or commit any editing actions that have taken place. Figure 3 shows the modal control in the Contacts application.

Figure 3. The Contacts application uses a modal control for “Edit” and “Done”


Providing secondary confirmations is a common and useful pattern for preventing destructive operations such as deletions.

2.1. Creating a modal button subclass

This example illustrates just one way to create a modal button. There are several options, including building custom UIView objects from scratch. I chose to take a simpler route and simply subclassed UIButton in order to keep the interface consistent, reduce duplication of code, and avoid tiny deviations in the look and feel of my button and standard UIButton instances. The implementation is fairly standard. ModalButton is a subclass of UIButton and overloads the touchesEnded:withEvent: method to add custom handling behavior. Specifically, each touch deactivates the button and sets an NSTimer to fire after a delay, presenting the next state in a finite sequence of modes. The delay emulates what might happen if you were to design the App Store button that triggers an application download over HTTP and acts as an implicit opt-in agreement to purchase an application (versus a one-touch sequence, which might be triggered by accident).

The ModalButton class adheres to the UIControl target-action messaging mechanism. In this example, the main view controller, ModalButtonViewController, adds itself as a target for the UIControlEventTouchUpInside control event. Doing so sends a specific action message to the ModalButtonViewController instance when a UITouch sequence begins and ends inside the bounds of the ModalButton:

#import "ModalButtonViewController.h"

@implementation ModalButtonViewController

- (void)viewDidLoad
{
	[super viewDidLoad];
	button = [[ModalButton alloc] init];
	[button addTarget:self action:@selector(performModalActionForButton:)
		forControlEvents:UIControlEventTouchUpInside];
	[button retain];
	[self.view addSubview:button];
	button.center = CGPointMake(160.0, 210.0);
}

- (void)performModalActionForButton:(id)sender
{
	ModalButton *btn = (ModalButton *)sender;
	ModalButtonMode mode = btn.mode;
	NSLog(@"The button mode is: %d", mode);
}

- (void)dealloc
{
	[button release];
	[super dealloc];
}

@end


					  

The definition of the ModalButton class follows:

#import <UIKit/UIKit.h>

static NSString *download = @"Download";
static NSString *downloading = @"Downloading...";
static NSString *install = @"Install";
static NSString *installing = @"Installing...";
static NSString *complete = @"Complete!";

static UIImage *heart = nil;
static UIImage *clover = nil;

typedef enum {
	ModalButtonModeDefault = 0,
	ModalButtonModeDownload,
	ModalButtonModeInstall,
	ModalButtonModeComplete,
} ModalButtonMode;


@interface ModalButton : UIButton {
	ModalButtonMode mode;
	NSTimer *timer;
	UIActivityIndicatorView *indicator;
}

@property (readonly) ModalButtonMode mode;

- (void)update:(NSTimer *)theTimer;

@end


					  

#import "ModalButton.h"

@interface ModalButton (PrivateMethods)
- (void)handleTap;
@end

@implementation ModalButton

@synthesize mode;

- (id) init
{
	self = [self initWithFrame:CGRectMake(0.0, 0.0, 118.0, 118.0)];
	return self;
}

- (id)initWithFrame:(CGRect)frame
{
	if (self = [super initWithFrame:frame]) {
		mode = ModalButtonModeDefault;
		heart = [UIImage imageNamed:@"heart.png"];
		clover = [UIImage imageNamed:@"clover.png"];

		indicator = [[UIActivityIndicatorView alloc]
			initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
		indicator.hidesWhenStopped = YES;
		[self addSubview:indicator];
		[self bringSubviewToFront:indicator];
		indicator.center = self.center;

		self.backgroundColor = [UIColor clearColor];
		[self setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
		[self setTitleColor:[UIColor whiteColor] forState:UIControlStateDisabled];
		self.titleEdgeInsets = UIEdgeInsetsMake(-90.0, 0, 0, 0);
		self.font = [UIFont fontWithName:@"Helvetica" size:12.0f];
		[self setBackgroundImage:heart forState:UIControlStateNormal];

		[self update:nil];
	}
	return self;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	UITouch *touch = [touches anyObject];
	if([self hitTest:[touch locationInView:self] withEvent:event]){
		[self handleTap];
	}

	[super touchesEnded:touches withEvent:event];
}

- (void)handleTap
{
	self.enabled = NO;
	NSString *title;
	switch(mode){
		case ModalButtonModeDownload:
			title = downloading;
			break;
		case ModalButtonModeInstall:
			title = installing;
			break;
		default:
			break;
	}
	[self setTitle:title forState:UIControlStateNormal];

	if([timer isValid]){
		[timer invalidate];
	}
	timer = [NSTimer timerWithTimeInterval:5.0 target:self
		selector:@selector(update:)
		userInfo:nil repeats:NO];
	[timer retain];
	[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

	[indicator startAnimating];
}

- (void)update:(NSTimer *)theTimer
{
	NSString *title;
	// Toggle mode
	switch(mode){
		case ModalButtonModeDefault:
			mode = ModalButtonModeDownload;
			title = download;
			self.enabled = YES;
			break;
		case ModalButtonModeDownload:
			mode = ModalButtonModeInstall;
			title = install;
			self.enabled = YES;
			break;
		case ModalButtonModeInstall:
			mode = ModalButtonModeComplete;
			title = complete;
			[self setBackgroundImage:clover forState:UIControlStateNormal];
			self.enabled = NO;
			break;
		default:
			self.enabled = NO;
			return;
	}
	[self setTitle:title forState:UIControlStateNormal];

	if([timer isValid]){
		[timer invalidate];
	}
	[indicator stopAnimating];
}

- (void)dealloc
{
	if([timer isValid]){
		[timer invalidate];
	}
	[timer release];
	[indicator release];
	[super dealloc];
}
@end
Other  
  •  Sony Introduced 3 New Phones In Xperia Series: Tipo, Miro And Ion
  •  Samsung Galaxy SIII And HTC One X: A Detailed And Thorough Comparison
  •  In Control
  •  BlackBerry Virtual Keyboard: The Development Through Each Appliance
  •  3D Phone – Why Is LG Optimus 3D Max P725 The Best Choice, Still?
  •  XNA Game Studio 4.0 : Xbox 360 Gamepad (part 2) - Moving Sprites Based on Gamepad Input
  •  XNA Game Studio 4.0 : Xbox 360 Gamepad (part 1) - Reading Gamepad State
  •  XNA Game Studio 4.0 : Adding Interactivity with User Input - Precision Control of a Mouse
  •  Asus Transformer Pad TF300
  •  Mobile Phone Game Programming : Using Sprite Animation - Building the UFO Example Program
  •  Mobile Phone Game Programming : Using Sprite Animation - Achieving Smooth Animation with the GameCanvas Class
  •  Mobile Phone Game Programming : Using Sprite Animation - Working with the Layer and Sprite Classes
  •  Windows Phone 8 Unveiled
  •  iOS 6 Beta Review (Part 2)
  •  iOS 6 Beta Review (Part 1)
  •  Ipad : Tabletop
  •  Ipad Lion (Part 3) - Other possible features
  •  Ipad Lion (Part 2) - What to expect from the upcoming mountain lion
  •  Ipad Lion (Part 1) - Lion paving the way for mountain lion's destiny
  •  The Effect Of IOS And Facebook On Shutterbugs (Part 2)
  •  
    Top 10
    Nikon 1 J2 With Stylish Design And Dependable Image And Video Quality
    Canon Powershot D20 - Super-Durable Waterproof Camera
    Fujifilm Finepix F800EXR – Another Excellent EXR
    Sony NEX-6 – The Best Compact Camera
    Teufel Cubycon 2 – An Excellent All-In-One For Films
    Dell S2740L - A Beautifully Crafted 27-inch IPS Monitor
    Philips 55PFL6007T With Fantastic Picture Quality
    Philips Gioco 278G4 – An Excellent 27-inch Screen
    Sony VPL-HW50ES – Sony’s Best Home Cinema Projector
    Windows Vista : Installing and Running Applications - Launching Applications
    Most View
    Making Movies On Your Camera (Part 1)
    The Ultimate Guide To Macro (Part 2) - Using off-camera flash for macro, 10 expert shooting tricks
    Silverstone Heligon HE01 - Asymetrical Cooling
    Google Chrome 21 - Fast, Free Web Browser
    Windows Vista : Scheduling with Windows Calendar (part 2) - Working with Multiple Calendars, Importing Calendar Files, Sharing Calendars
    Sharepoint 2010 : BCS Architecture - Presentation & Core Components
    A New Leaf (Part 1)
    Technology For Business (Part 1)
    SQL Server 2008 : Performance Tuning - Using Dynamic Management Views
    The Future Of Apple: Chip Off The Block (Part 1)
    Silverlight Tools: Vector Graphics Editors
    Rig Builder – May 2012 (Part 2)
    IIS 7.0 : Managing Configuration - Delegating Configuration (part 1)
    Ipad Lion (Part 2) - What to expect from the upcoming mountain lion
    Windows Phone 7 Development : Debugging Application Exceptions (part 1) - Debugging Page Load Exceptions
    Programming the iPhone : Progressive Enhancement - Audio Support
    5 Tips For Faster Editing
    SharePoint 2010 : Understanding Windows PowerShell Concepts (part 2)
    Programming .NET Components : Building a Distributed Application (part 6) - Remote Callbacks
    Building LOB Applications : Printing in a Silverlight LOB Application