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.