Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is UIPageControl Useless By Itself?

I've been looking into using the UIPageControl for a scrolling part of an application, and I was wondering how it works. The docs show methods for changing pages, setting the number of pages etc., but by itself the page control doesn't encompass any kind of scroll view.

Are the UIPageControl and UIScrollView classes supposed to be used in conjunction when one wants to show the page indicator (like on the home screen of the iPhone with the app pages)?

like image 402
Jasarien Avatar asked Jul 09 '09 13:07

Jasarien


4 Answers

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
like image 78
Werner Altewischer Avatar answered Sep 26 '22 18:09

Werner Altewischer


The paging bit is actually done by setting the paging property on the UIScrollView. The page control is simply a useful UI construct, but doesn't actually do anything to do with paging itself.

like image 41
Airsource Ltd Avatar answered Sep 24 '22 18:09

Airsource Ltd


UIPageControl does two things: (1) it displays dots, including a highlighted dot for the currently selected page, and (2) it generates a UIControlEventValueChanged event when the user taps on it. Tap on the right side of the control to page to the right; tap on the left side to page to the left. You should implement this behavior to be consistent with the HIG!

To trap the event, add:

[myUiPageControl addTarget:self action:@selector(pageChanged:) forControlEvents:UIControlEventValueChanged];

and that delegate function as well:

-(void)pageChanged:(UIPageControl*)thePageControl;

The name can be whatever you want; I used "pageChanged" for example. The callback signature can be pageChanged, pageChanged:, or pageChanged:forEvent:. This callback function should cause your scrollview (or what have you) to update.

Paging, itself, is usually done by using a UIScrollView with paging enabled and scrollbars hidden. Tell the scrollview to use paging and flicks magically move page by page; make sure you trap that change to update the currentPage property of the UIPageControl.

like image 45
AndrewS Avatar answered Sep 22 '22 18:09

AndrewS


You're correct--UIPageControl doesn't draw anything by itself other than the dots at the bottom of the page; you need another view to do that.

like image 32
David Maymudes Avatar answered Sep 26 '22 18:09

David Maymudes