MOBILE

Programming the iPhone : Scrolling Controls

8/7/2012 3:50:22 PM
The iPhone handles scrolling in a very intuitive manner. The timing of animations and the touch and gesture recognition build muscle memory and acclimate users to the idea of UI elements moving on- and offscreen. Apple sets some precedents for the timing of scrolling, zooming, and panning, and developers who wish to provide a consistent user experience should build their visual transitions around those precedents.

You can use the UIScrollView class to create views that scroll horizontally, vertically, or both. Scrollviews respond to touches, drags, and flick gestures. Dragging a finger across the screen slowly scrolls the content in the scrolling view in parallel. Quickly flicking across the screen will throw the content in the direction of the swipe, and it will continue moving after the touch sequence has ended. Adding a scrolling view to an application is no different from adding any other view to the screen because UIScrollView is a subclass of UIView. Developers can enable a bounce effect using a property of the UIScrollView instance called bounces.

The Photos application uses a scrolling view to display full-screen photographs sequentially. Scrolling views can scroll incrementally or in larger units that correspond to the width of the screen, also known as paging. You can enable paging with the pagingEnabled attribute of UIScrollView.

The following example creates a basic UIScrollView subclass for displaying a sequence of full-screen images. Bouncing is enabled, as is paging. You can enable or disable a subtle scrollbar for scrolling views using the showsHorizontalScrollIndicator and showsVerticalScrollIndicator properties. Small details like scrollbars can make a big impact for users. In some cases, such as a photo gallery, even a subtle scrollbar can be distracting. In other cases, such as the current step in a process or remaining copy in an article, a scrollbar provides important context. UIKit classes offer a great deal of configurability and allow design and development teams to create the most appropriate interface for the task at hand:

// GalleryView.h

#import <UIKit/UIKit.h>

@interface GalleryView : UIScrollView {
}

- (void)addImage:(UIImage *)image;

@end

// GalleryView.m

#import "GalleryView.h"

@implementation GalleryView

- (id)initWithFrame:(CGRect)frame
{
	if(self = [super initWithFrame:frame]){
		self.backgroundColor = [UIColor blackColor];
		self.scrollEnabled = YES;
		self.pagingEnabled = YES;
		self.bounces = YES;
		self.directionalLockEnabled = NO;
	}
	return self;
}

- (void)addImage:(UIImage *)image
{
	int imageCount = [self.subviews count];
	float newContentWidth = ((float)imageCount + 1.0) * 320.0;
	CGSize newContentSize = CGSizeMake(newContentWidth, 460.0);
	UIImageView *imageView = [[UIImageView alloc]
		initWithFrame:CGRectMake((imageCount * 320.0), 0.0, 320.0, 460.0)];

	self.contentSize = newContentSize;

	imageView.image = image;
	[self addSubview:imageView];
	[imageView release];
}

@end


					  

// ImageGalleryViewController.m

#import "ImageGalleryViewController.h"

@implementation ImageGalleryViewController

- (void)viewDidLoad
{
	GalleryView *galleryView = [[GalleryView alloc]
					initWithFrame:[UIScreen mainScreen].applicationFrame];

	[galleryView addImage:[UIImage imageNamed:@"murray.png"]];
	[galleryView addImage:[UIImage imageNamed:@"murray.png"]];
	[galleryView addImage:[UIImage imageNamed:@"murray.png"]];
	[galleryView addImage:[UIImage imageNamed:@"murray.png"]];
	[galleryView addImage:[UIImage imageNamed:@"murray.png"]];
	
	self.view = galleryView;
	[galleryView release];

	[super viewDidLoad];
}

@end

Scrolling views can also be used for subtle effects. The following example shows the development of a custom UIControl subclass that uses a UIScrollView to create scrolling interaction. It is possible to develop a fully custom scrolling interface without the use of a UIScrollView, but the available UIKit classes help provide consistency in subtle ways, such as in the timing of animations:

// ScrollingControlViewController.h

#import <UIKit/UIKit.h>

@class Scroller;

@interface ScrollingControlViewController : UIViewController {
	Scroller *scroller;
}

- (void)scrollerDidScroll:(id)sender;

@end

// ScrollingControlViewController.m

#import "ScrollingControlViewController.h"
#import "Scroller.h"

@implementation ScrollingControlViewController

- (void)viewDidLoad
{
	CGRect f = CGRectMake(0.0, 0.0, 320.0, 460.0);
	UIImageView *backgroundView = [[[UIImageView alloc]
		initWithFrame:f]
		autorelease];
	backgroundView.image = [UIImage imageNamed:@"background.png"];
	[self.view addSubview:backgroundView];

	scroller = [[Scroller alloc]
		initWithFrame:CGRectMake(0.0, 100.0, 320.0, 60.0)];
	[scroller addTarget:self action:@selector(scrollerDidScroll:)
		forControlEvents:UIControlEventApplicationReserved];
	[self.view addSubview:scroller];

	f.size.height = 126.0;
	UIImageView *topView = [[[UIImageView alloc] initWithFrame:f]
		autorelease];
	topView.image = [UIImage imageNamed:@"top.png"];
	[self.view addSubview:topView];
	[super viewDidLoad];
	
}

- (void)scrollerDidScroll:(id)sender
{
	NSLog(@"Scroller did scroll.");
}

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

@end


					  

// Scroller.h

#import <UIKit/UIKit.h>

@interface Scroller : UIControl <UIScrollViewDelegate> {
	UIScrollView *numbersView;
	CGPoint touchPoint;
	CGPoint scrollerPoint;
}

@end

// Scroller.m

#import "Scroller.h"
#import "NumView.h"

@interface Scroller (PrivateMethods)

- (void)snapToClosestNumber;

@end

@implementation Scroller

#define WIDTH 500.0
#define NUM 10
#define NUM_WIDTH (WIDTH / NUM)
#define HALF (NUM_WIDTH / 2)
#define HEIGHT 40.0
#define INSET_WIDTH 160.0

- (void)snapToClosestNumber
{
	CGPoint coff = numbersView.contentOffset;
	float normalizedX = coff.x + INSET_WIDTH;
	double diff = fmod(normalizedX, NUM_WIDTH);
	
	// Move to the left or right, as needed
	if(diff < NUM_WIDTH){
		// If we're at the max...
		if(normalizedX == WIDTH){
			normalizedX -= NUM_WIDTH;
		}
		normalizedX -= diff;
	}else{
		normalizedX += diff;
	}
	
	float leftX = normalizedX - INSET_WIDTH + HALF;
	
	[numbersView scrollRectToVisible:CGRectMake(leftX, 0.0, 320.0, HEIGHT)
		animated:YES];	
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
	NSLog(@"sending actions for UIControlEventApplicationReserved.");
	[self sendActionsForControlEvents:UIControlEventApplicationReserved];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
		willDecelerate:(BOOL)decelerate
{
	[self snapToClosestNumber];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
	[self snapToClosestNumber];
}

- (id)initWithFrame:(CGRect)frame
{
	if (self = [super initWithFrame:frame]) {
		numbersView = [[UIScrollView alloc]
			initWithFrame:CGRectMake(0.0, 0.0, 320.0, 66.0)];
		numbersView.delegate = self;
		numbersView.showsHorizontalScrollIndicator = NO;
		numbersView.showsVerticalScrollIndicator = NO;
		numbersView.delaysContentTouches = NO;
		numbersView.bounces = YES;

		self.backgroundColor = [UIColor clearColor];
		//	Add in a bunch of subs
		NSUInteger i = 0;
		NumView *numView;
		CGRect frame;
		for(i; i < NUM; i++){
			frame = CGRectMake((i * NUM_WIDTH), 20.0, NUM_WIDTH, HEIGHT);
			numView = [[[NumView alloc] initWithFrame:frame number:i] autorelease];
			[numbersView addSubview:numView];
			numView.frame = frame;
		}
		[self addSubview:numbersView];
		numbersView.contentSize = CGSizeMake(WIDTH, HEIGHT);
		numbersView.contentInset = UIEdgeInsetsMake(0.0,
		                                                			INSET_WIDTH,
		                                                			0.0,
		                                                			INSET_WIDTH);
	}
	return self;
}

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


@end


					  

// NumView.h

#import <UIKit/UIKit.h>

@interface NumView : UILabel {
}

- (id)initWithFrame:(CGRect)frame number:(NSUInteger)num;

@end

// NumView.m

#import "NumView.h"

@implementation NumView

- (id)initWithFrame:(CGRect)frame number:(NSUInteger)num
{
	if (self = [super initWithFrame:frame]) {
		self.text = [NSString stringWithFormat:@"%d", num];
		self.backgroundColor = [UIColor clearColor];
		self.textColor = [UIColor whiteColor];
		self.textAlignment = UITextAlignmentCenter;
		self.shadowColor = [UIColor darkGrayColor];
		self.shadowOffset = CGSizeMake(0, 1.0);
		self.font = [UIFont boldSystemFontOfSize:36.0];
	}
	return self;
}

@end

Figure 1 shows the custom control.

Figure 1. Custom scrolling UIControl subclass

Other  
  •  BlackBerry Java Application Development : Networking - Testing for availability of transports
  •  XNA Game Studio 4.0 : Multitouch Input For Windows Phones (part 2) - Displaying GestureSample Data
  •  XNA Game Studio 4.0 : Multitouch Input For Windows Phones (part 1)
  •  Motorola Motoluxe
  •  Apple's Undiscovered Country : The future of the iPhone
  •  Apple's Undiscovered Country : The Future Of The Ipad
  •  Programming the iPhone : Standard Control Types (part 6) - Segmented Controls
  •  Programming the iPhone : Standard Control Types (part 5) - Search Bars
  •  Programming the iPhone : Standard Control Types (part 4) - Tables and Pickers
  •  Programming the iPhone : Standard Control Types (part 3) - Sliders
  •  Programming the iPhone : Standard Control Types (part 2) - Modal Buttons
  •  Programming the iPhone : Standard Control Types (part 1) - Buttons
  •  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
  •  
    Top 10
    SQL Server 2005 Security : Authentication and Authorization
    SQL Server 2005 Security : SQL Server 2005 Security Overview
    Windows System Programming : Exception Handling - Exceptions and Their Handlers
    ASP.NET 4 in VB 2010 : The XML Classes (part 4) - Reading an XML Document, Searching an XML Document
    ASP.NET 4 in VB 2010 : The XML Classes (part 3) - Working with XML Documents in Memory
    ASP.NET 4 in VB 2010 : The XML Classes (part 2) - The XML Text Reader
    ASP.NET 4 in VB 2010 : The XML Classes (part 1) - The XML TextWriter
    Canon EOS M – Is The Final Big Player?
    Build a Smut Proof PC
    Windows 8 - Microsoft’s Silver Bullet (Part 2)
    Most View
    Infinity Blade II
    Mobile Application Security : BlackBerry Security - Development and Security Testing
    Programming the Mobile Web : JavaScript Libraries
    DirectX 10 : The 2D Resurgence - Sprite Animation
    SQL Server 2008 : Failover clustering - Clustering topologies and failover rules
    Away With The Cloud? (Part 1)
    Silverlight Recipes : Managing XAML Resources
    Extra Network Hardware Round-Up (Part 3)
    Thunderbolt Arrives!
    Programming .NET Security : Cryptographic Keys Explained
    Programming the iPhone User : UX Anti-Patterns - Memory Lapse
    Cloud Computing : Controlling It All with Web-Based Desktops - Evaluating Web-Based Desktops
    Active Directory Domain Services 2008 : View Cached Credentials on a Read-Only Domain Controller
    Rise Of The Mobile Processors (Part 2)
    ASUS F2A85-M Pro - Reasonably Priced Motherboard
    Windows 8: End Game? (Part 1)
    Use Preview to Edit On Your Mac (Part 1)
    Managing the Cache
    All You Need To Know About iOS 6 (Part 2)
    Windows 7 : Detecting and Resolving Computer Problems (part 2) - Tracking Errors in the Event Logs