I've written a class (PagedView) which works just like UITableView and merges the concepts of the UIPageControl and UIScrollView in a manner used for example on the iPhone home screen.
The concept is basically as follows: you need to implement PagedViewDelegate to return the number of pages and a view for each page of you PagedView. Reusing of views works the same as in UITableView. Use interface builder to connect the scrollview and pageControl outlets.
Please let me know if you find this class useful.
.h-file:
//
// PagedView.h
//
// Created by Werner Altewischer on 22/10/10.
// Copyright 2010 werner-it.com. All rights reserved.
//
@protocol ReusableObject
- (NSString *)reuseIdentifier;
- (void)prepareForReuse;
@end
@class PagedView;
@protocol PagedViewDelegate
- (NSUInteger)numberOfPagesInPagedView:(PagedView *)view;
- (UIView *)pagedView:(PagedView *)view viewForPageAtIndex:(NSUInteger)page;
@end
@interface PagedView : UIView<UIScrollViewDelegate> {
IBOutlet UIScrollView *scrollView;
IBOutlet UIPageControl *pageControl;
NSMutableDictionary *pageViewDictionary;
NSMutableDictionary *reuseViewDictionary;
IBOutlet id <PagedViewDelegate> delegate;
}
@property (nonatomic, assign) IBOutlet id <PagedViewDelegate> delegate;
- (UIView<ReusableObject> *)dequeueReusableViewWithIdentifier:(NSString *)identifier;
- (void)scrollToPageAtIndex:(NSUInteger)pageIndex animated:(BOOL)animated;
- (NSInteger)indexForSelectedPage;
- (CGSize)pageSize;
- (void)reloadData;
@end
.m-file:
//
// PagedView.m
//
// Created by Werner Altewischer on 22/10/10.
// Copyright 2010 werner-it.com. All rights reserved.
//
#define TT_RELEASE_SAFELY(__POINTER) { [__POINTER release]; __POINTER = nil; }
@interface PagedView (Private)
- (NSUInteger)pageCount;
- (UIView *)loadViewForIndex:(NSUInteger)pageIndex;
- (void)unloadViewForIndex:(NSUInteger)pageIndex;
- (void)loadViewsForVisiblePages:(BOOL)reloadData;
- (UIView *)viewForIndex:(NSUInteger)pageIndex;
@end
@implementation PagedView
@synthesize delegate;
- (void)dealloc {
TT_RELEASE_SAFELY(pageViewDictionary);
TT_RELEASE_SAFELY(reuseViewDictionary);
TT_RELEASE_SAFELY(scrollView);
TT_RELEASE_SAFELY(pageControl);
[super dealloc];
}
- (CGSize)pageSize {
return scrollView.frame.size;
}
- (void)reloadData {
if (!pageViewDictionary) {
//First time initialization
pageViewDictionary = [NSMutableDictionary new];
reuseViewDictionary = [NSMutableDictionary new];
[pageControl addTarget:self action:@selector(pageChanged:) forControlEvents:UIControlEventValueChanged];
scrollView.delegate = self;
scrollView.pagingEnabled = YES;
}
CGSize size = self.pageSize;
NSUInteger numberOfPages = self.pageCount;
pageControl.numberOfPages = MAX(1, numberOfPages);
[scrollView setContentSize:CGSizeMake(size.width * numberOfPages, size.height)];
pageControl.currentPage = self.indexForSelectedPage;
pageControl.hidden = (numberOfPages == 0);
[self loadViewsForVisiblePages:YES];
}
- (void)layoutSubviews {
if (!pageViewDictionary) {
[self reloadData];
}
}
- (void)scrollToPageAtIndex:(NSUInteger)pageIndex animated:(BOOL)animated {
if (pageIndex < self.pageCount) {
CGSize size = scrollView.frame.size;
CGRect rect = CGRectMake(size.width * pageIndex, 0, size.width, size.height);
[scrollView scrollRectToVisible:rect animated:animated];
}
}
- (NSInteger)indexForSelectedPage {
CGFloat cx = scrollView.contentOffset.x;
NSUInteger index = (NSUInteger)(cx / scrollView.frame.size.width);
if (index >= self.pageCount) {
index = NSNotFound;
}
return index;
}
#pragma mark -
#pragma mark UIScrollViewDelegate implementation
- (void)scrollViewWillBeginDragging:(UIScrollView *)theScrollView {
theScrollView.userInteractionEnabled = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)theScrollView {
if (theScrollView == scrollView) {
pageControl.currentPage = self.indexForSelectedPage;
[self loadViewsForVisiblePages:NO];
theScrollView.userInteractionEnabled = YES;
}
}
- (void)pageChanged:(UIPageControl *)thePageControl {
if (pageControl == thePageControl) {
[self scrollToPageAtIndex:pageControl.currentPage animated:YES];
[self loadViewsForVisiblePages:NO];
}
}
- (UIView<ReusableObject> *)dequeueReusableViewWithIdentifier:(NSString *)identifier {
UIView<ReusableObject> *v = [[[reuseViewDictionary objectForKey:identifier] retain] autorelease];
if (v) {
[v prepareForReuse];
[reuseViewDictionary removeObjectForKey:identifier];
}
return v;
}
@end
@implementation PagedView (Private)
- (NSUInteger)pageCount {
return [self.delegate numberOfPagesInPagedView:self];
}
- (UIView *)viewForIndex:(NSUInteger)pageIndex {
id key = [NSNumber numberWithUnsignedInteger:pageIndex];
return [pageViewDictionary objectForKey:key];
}
- (UIView *)loadViewForIndex:(NSUInteger)pageIndex {
id key = [NSNumber numberWithUnsignedInteger:pageIndex];
UIView *v = [pageViewDictionary objectForKey:key];
if (!v) {
CGSize size = self.pageSize;
UIView *v = [self.delegate pagedView:self viewForPageAtIndex:pageIndex];
if (v) {
v.frame = CGRectMake(pageIndex * size.width, 0, size.width, size.height);
[scrollView addSubview:v];
[pageViewDictionary setObject:v forKey:key];
}
}
return v;
}
- (void)unloadViewForIndex:(NSUInteger)pageIndex {
id key = [NSNumber numberWithUnsignedInteger:pageIndex];
UIView *v = [pageViewDictionary objectForKey:key];
if (v) {
if ([v conformsToProtocol:@protocol(ReusableObject)]) {
NSString *reuseIdentifier = [(id <ReusableObject>)v reuseIdentifier];
[reuseViewDictionary setObject:v forKey:reuseIdentifier];
}
[v removeFromSuperview];
[pageViewDictionary removeObjectForKey:key];
}
}
- (void)loadViewsForVisiblePages:(BOOL)reloadData {
//load the selected view and the one in front and behind
NSUInteger selectedPage = self.indexForSelectedPage;
NSUInteger numberOfPages = self.pageCount;
int intSelectedPage = (selectedPage == NSNotFound) ? -2 : (int)selectedPage;
//Find the max number present in the pageViewDictionary
NSUInteger existingPageCount = 0;
for (NSNumber *key in pageViewDictionary) {
if ([key unsignedIntegerValue] >= existingPageCount) {
existingPageCount = [key unsignedIntegerValue] + 1;
}
}
for (int i = 0; i < MAX(numberOfPages, existingPageCount); ++i) {
if (i >= numberOfPages ||
i < (intSelectedPage - 1) ||
i > (intSelectedPage + 1)) {
[self unloadViewForIndex:i];
} else {
if (reloadData) {
//Unload the view if we're reloading all the data
[self unloadViewForIndex:i];
}
[self loadViewForIndex:i];
}
}
[reuseViewDictionary removeAllObjects];
}
@end