// // 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