New file |
| | |
| | | // |
| | | // TAPageControl.m |
| | | // TAPageControl |
| | | // |
| | | // Created by Tanguy Aladenise on 2015-01-21. |
| | | // Copyright (c) 2015 Tanguy Aladenise. All rights reserved. |
| | | // |
| | | |
| | | #import "TAPageControl.h" |
| | | #import "TAAbstractDotView.h" |
| | | #import "TAAnimatedDotView.h" |
| | | #import "TADotView.h" |
| | | |
| | | /** |
| | | * Default number of pages for initialization |
| | | */ |
| | | static NSInteger const kDefaultNumberOfPages = 0; |
| | | |
| | | /** |
| | | * Default current page for initialization |
| | | */ |
| | | static NSInteger const kDefaultCurrentPage = 0; |
| | | |
| | | /** |
| | | * Default setting for hide for single page feature. For initialization |
| | | */ |
| | | static BOOL const kDefaultHideForSinglePage = NO; |
| | | |
| | | /** |
| | | * Default setting for shouldResizeFromCenter. For initialiation |
| | | */ |
| | | static BOOL const kDefaultShouldResizeFromCenter = YES; |
| | | |
| | | /** |
| | | * Default spacing between dots |
| | | */ |
| | | static NSInteger const kDefaultSpacingBetweenDots = 8; |
| | | |
| | | /** |
| | | * Default dot size |
| | | */ |
| | | static CGSize const kDefaultDotSize = {8, 8}; |
| | | |
| | | |
| | | @interface TAPageControl() |
| | | |
| | | |
| | | /** |
| | | * Array of dot views for reusability and touch events. |
| | | */ |
| | | @property (strong, nonatomic) NSMutableArray *dots; |
| | | |
| | | |
| | | @end |
| | | |
| | | @implementation TAPageControl |
| | | |
| | | |
| | | #pragma mark - Lifecycle |
| | | |
| | | |
| | | - (id)init |
| | | { |
| | | self = [super init]; |
| | | if (self) { |
| | | [self initialization]; |
| | | } |
| | | |
| | | return self; |
| | | } |
| | | |
| | | |
| | | - (id)initWithFrame:(CGRect)frame |
| | | { |
| | | self = [super initWithFrame:frame]; |
| | | if (self) { |
| | | [self initialization]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | |
| | | - (id)initWithCoder:(NSCoder *)aDecoder |
| | | { |
| | | self = [super initWithCoder:aDecoder]; |
| | | if (self) { |
| | | [self initialization]; |
| | | } |
| | | |
| | | return self; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Default setup when initiating control |
| | | */ |
| | | - (void)initialization |
| | | { |
| | | self.dotViewClass = [TAAnimatedDotView class]; |
| | | self.spacingBetweenDots = kDefaultSpacingBetweenDots; |
| | | self.numberOfPages = kDefaultNumberOfPages; |
| | | self.currentPage = kDefaultCurrentPage; |
| | | self.hidesForSinglePage = kDefaultHideForSinglePage; |
| | | self.shouldResizeFromCenter = kDefaultShouldResizeFromCenter; |
| | | } |
| | | |
| | | |
| | | #pragma mark - Touch event |
| | | |
| | | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event |
| | | { |
| | | UITouch *touch = [touches anyObject]; |
| | | if (touch.view != self) { |
| | | NSInteger index = [self.dots indexOfObject:touch.view]; |
| | | if ([self.delegate respondsToSelector:@selector(TAPageControl:didSelectPageAtIndex:)]) { |
| | | [self.delegate TAPageControl:self didSelectPageAtIndex:index]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | #pragma mark - Layout |
| | | |
| | | |
| | | /** |
| | | * Resizes and moves the receiver view so it just encloses its subviews. |
| | | */ |
| | | - (void)sizeToFit |
| | | { |
| | | [self updateFrame:YES]; |
| | | } |
| | | |
| | | |
| | | - (CGSize)sizeForNumberOfPages:(NSInteger)pageCount |
| | | { |
| | | return CGSizeMake((self.dotSize.width + self.spacingBetweenDots) * pageCount - self.spacingBetweenDots , self.dotSize.height); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Will update dots display and frame. Reuse existing views or instantiate one if required. Update their position in case frame changed. |
| | | */ |
| | | - (void)updateDots |
| | | { |
| | | if (self.numberOfPages == 0) { |
| | | return; |
| | | } |
| | | |
| | | for (NSInteger i = 0; i < self.numberOfPages; i++) { |
| | | |
| | | UIView *dot; |
| | | if (i < self.dots.count) { |
| | | dot = [self.dots objectAtIndex:i]; |
| | | } else { |
| | | dot = [self generateDotView]; |
| | | } |
| | | |
| | | [self updateDotFrame:dot atIndex:i]; |
| | | } |
| | | |
| | | [self changeActivity:YES atIndex:self.currentPage]; |
| | | |
| | | [self hideForSinglePage]; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Update frame control to fit current number of pages. It will apply required size if authorize and required. |
| | | * |
| | | * @param overrideExistingFrame BOOL to allow frame to be overriden. Meaning the required size will be apply no mattter what. |
| | | */ |
| | | - (void)updateFrame:(BOOL)overrideExistingFrame |
| | | { |
| | | CGPoint center = self.center; |
| | | CGSize requiredSize = [self sizeForNumberOfPages:self.numberOfPages]; |
| | | |
| | | // We apply requiredSize only if authorize to and necessary |
| | | if (overrideExistingFrame || ((CGRectGetWidth(self.frame) < requiredSize.width || CGRectGetHeight(self.frame) < requiredSize.height) && !overrideExistingFrame)) { |
| | | self.frame = CGRectMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame), requiredSize.width, requiredSize.height); |
| | | if (self.shouldResizeFromCenter) { |
| | | self.center = center; |
| | | } |
| | | } |
| | | |
| | | [self resetDotViews]; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Update the frame of a specific dot at a specific index |
| | | * |
| | | * @param dot Dot view |
| | | * @param index Page index of dot |
| | | */ |
| | | - (void)updateDotFrame:(UIView *)dot atIndex:(NSInteger)index |
| | | { |
| | | // Dots are always centered within view |
| | | CGFloat x = (self.dotSize.width + self.spacingBetweenDots) * index + ( (CGRectGetWidth(self.frame) - [self sizeForNumberOfPages:self.numberOfPages].width) / 2); |
| | | CGFloat y = (CGRectGetHeight(self.frame) - self.dotSize.height) / 2; |
| | | |
| | | dot.frame = CGRectMake(x, y, self.dotSize.width, self.dotSize.height); |
| | | } |
| | | |
| | | |
| | | #pragma mark - Utils |
| | | |
| | | |
| | | /** |
| | | * Generate a dot view and add it to the collection |
| | | * |
| | | * @return The UIView object representing a dot |
| | | */ |
| | | - (UIView *)generateDotView |
| | | { |
| | | UIView *dotView; |
| | | |
| | | if (self.dotViewClass) { |
| | | dotView = [[self.dotViewClass alloc] initWithFrame:CGRectMake(0, 0, self.dotSize.width, self.dotSize.height)]; |
| | | if ([dotView isKindOfClass:[TAAnimatedDotView class]] && self.dotColor) { |
| | | ((TAAnimatedDotView *)dotView).dotColor = self.dotColor; |
| | | } |
| | | } else { |
| | | dotView = [[UIImageView alloc] initWithImage:self.dotImage]; |
| | | dotView.frame = CGRectMake(0, 0, self.dotSize.width, self.dotSize.height); |
| | | } |
| | | |
| | | if (dotView) { |
| | | [self addSubview:dotView]; |
| | | [self.dots addObject:dotView]; |
| | | } |
| | | |
| | | dotView.userInteractionEnabled = YES; |
| | | return dotView; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Change activity state of a dot view. Current/not currrent. |
| | | * |
| | | * @param active Active state to apply |
| | | * @param index Index of dot for state update |
| | | */ |
| | | - (void)changeActivity:(BOOL)active atIndex:(NSInteger)index |
| | | { |
| | | if (self.dotViewClass) { |
| | | TAAbstractDotView *abstractDotView = (TAAbstractDotView *)[self.dots objectAtIndex:index]; |
| | | if ([abstractDotView respondsToSelector:@selector(changeActivityState:)]) { |
| | | [abstractDotView changeActivityState:active]; |
| | | } else { |
| | | NSLog(@"Custom view : %@ must implement an 'changeActivityState' method or you can subclass %@ to help you.", self.dotViewClass, [TAAbstractDotView class]); |
| | | } |
| | | } else if (self.dotImage && self.currentDotImage) { |
| | | UIImageView *dotView = (UIImageView *)[self.dots objectAtIndex:index]; |
| | | dotView.image = (active) ? self.currentDotImage : self.dotImage; |
| | | } |
| | | } |
| | | |
| | | |
| | | - (void)resetDotViews |
| | | { |
| | | for (UIView *dotView in self.dots) { |
| | | [dotView removeFromSuperview]; |
| | | } |
| | | |
| | | [self.dots removeAllObjects]; |
| | | [self updateDots]; |
| | | } |
| | | |
| | | |
| | | - (void)hideForSinglePage |
| | | { |
| | | if (self.dots.count == 1 && self.hidesForSinglePage) { |
| | | self.hidden = YES; |
| | | } else { |
| | | self.hidden = NO; |
| | | } |
| | | } |
| | | |
| | | #pragma mark - Setters |
| | | |
| | | |
| | | - (void)setNumberOfPages:(NSInteger)numberOfPages |
| | | { |
| | | _numberOfPages = numberOfPages; |
| | | |
| | | // Update dot position to fit new number of pages |
| | | [self resetDotViews]; |
| | | } |
| | | |
| | | |
| | | - (void)setSpacingBetweenDots:(NSInteger)spacingBetweenDots |
| | | { |
| | | _spacingBetweenDots = spacingBetweenDots; |
| | | |
| | | [self resetDotViews]; |
| | | } |
| | | |
| | | |
| | | - (void)setCurrentPage:(NSInteger)currentPage |
| | | { |
| | | // If no pages, no current page to treat. |
| | | if (self.numberOfPages == 0 || currentPage == _currentPage) { |
| | | _currentPage = currentPage; |
| | | return; |
| | | } |
| | | |
| | | // Pre set |
| | | [self changeActivity:NO atIndex:_currentPage]; |
| | | _currentPage = currentPage; |
| | | // Post set |
| | | [self changeActivity:YES atIndex:_currentPage]; |
| | | } |
| | | |
| | | |
| | | - (void)setDotImage:(UIImage *)dotImage |
| | | { |
| | | _dotImage = dotImage; |
| | | [self resetDotViews]; |
| | | self.dotViewClass = nil; |
| | | } |
| | | |
| | | |
| | | - (void)setCurrentDotImage:(UIImage *)currentDotimage |
| | | { |
| | | _currentDotImage = currentDotimage; |
| | | [self resetDotViews]; |
| | | self.dotViewClass = nil; |
| | | } |
| | | |
| | | |
| | | - (void)setDotViewClass:(Class)dotViewClass |
| | | { |
| | | _dotViewClass = dotViewClass; |
| | | self.dotSize = CGSizeZero; |
| | | [self resetDotViews]; |
| | | } |
| | | |
| | | |
| | | #pragma mark - Getters |
| | | |
| | | |
| | | - (NSMutableArray *)dots |
| | | { |
| | | if (!_dots) { |
| | | _dots = [[NSMutableArray alloc] init]; |
| | | } |
| | | |
| | | return _dots; |
| | | } |
| | | |
| | | |
| | | - (CGSize)dotSize |
| | | { |
| | | // Dot size logic depending on the source of the dot view |
| | | if (self.dotImage && CGSizeEqualToSize(_dotSize, CGSizeZero)) { |
| | | _dotSize = self.dotImage.size; |
| | | } else if (self.dotViewClass && CGSizeEqualToSize(_dotSize, CGSizeZero)) { |
| | | _dotSize = kDefaultDotSize; |
| | | return _dotSize; |
| | | } |
| | | |
| | | return _dotSize; |
| | | } |
| | | |
| | | @end |