123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- #import "ReaderThumbsView.h"
- @interface ReaderThumbsView () <UIScrollViewDelegate, UIGestureRecognizerDelegate>
- @end
- @implementation ReaderThumbsView
- {
- CGPoint lastContentOffset;
- ReaderThumbView *touchedCell;
- NSMutableArray *thumbCellsQueue;
- NSMutableArray *thumbCellsVisible;
- NSInteger _thumbsX, _thumbsY, _thumbX;
- CGSize _thumbSize, _lastViewSize;
- NSUInteger _thumbCount;
- BOOL canUpdate;
- }
- #pragma mark - Properties
- @synthesize delegate;
- #pragma mark - ReaderThumbsView instance methods
- - (instancetype)initWithFrame:(CGRect)frame
- {
- if ((self = [super initWithFrame:frame]))
- {
- self.scrollsToTop = NO;
- self.autoresizesSubviews = NO;
- self.delaysContentTouches = NO;
- self.alwaysBounceVertical = YES;
- self.contentMode = UIViewContentModeRedraw;
- self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- self.backgroundColor = [UIColor clearColor];
- [super setDelegate:self];
- thumbCellsQueue = [NSMutableArray new]; thumbCellsVisible = [NSMutableArray new];
- UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
-
- [self addGestureRecognizer:tapGesture];
- UILongPressGestureRecognizer *pressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePressGesture:)];
- pressGesture.minimumPressDuration = 0.8;
- [self addGestureRecognizer:pressGesture];
- lastContentOffset = CGPointMake(CGFLOAT_MIN, CGFLOAT_MIN);
- }
- return self;
- }
- - (void)requeueThumbCell:(ReaderThumbView *)tvCell
- {
- [thumbCellsQueue addObject:tvCell];
- [thumbCellsVisible removeObject:tvCell];
- tvCell.tag = NSIntegerMin; tvCell.hidden = YES;
- [tvCell reuse];
- }
- - (void)requeueAllThumbCells
- {
- if (thumbCellsVisible.count > 0)
- {
- NSArray *visible = [thumbCellsVisible copy];
- for (ReaderThumbView *tvCell in visible)
- {
- [self requeueThumbCell:tvCell];
- }
- }
- }
- - (ReaderThumbView *)dequeueThumbCellWithFrame:(CGRect)frame
- {
- ReaderThumbView *theCell = nil;
- if (thumbCellsQueue.count > 0)
- {
- theCell = [thumbCellsQueue objectAtIndex:0];
- [thumbCellsQueue removeObjectAtIndex:0];
- theCell.frame = frame;
- }
- else
- {
- theCell = [delegate thumbsView:self thumbCellWithFrame:frame];
-
- theCell.tag = NSIntegerMin; theCell.hidden = YES;
- [self insertSubview:theCell atIndex:0];
- }
- [thumbCellsVisible addObject:theCell];
- return theCell;
- }
- - (NSMutableIndexSet *)visibleIndexSetForContentOffset
- {
- CGFloat minY = self.contentOffset.y;
- CGFloat maxY = (minY + self.bounds.size.height - 1.0f);
- NSInteger startRow = (minY / _thumbSize.height);
- NSInteger finalRow = (maxY / _thumbSize.height);
- NSInteger startIndex = (startRow * _thumbsX);
- NSInteger finalIndex = (finalRow * _thumbsX);
- finalIndex += (_thumbsX - 1);
- NSInteger maximumIndex = (_thumbCount - 1);
- if (finalIndex > maximumIndex) finalIndex = maximumIndex;
- NSRange indexRange = NSMakeRange(startIndex, (finalIndex - startIndex + 1));
- return [NSMutableIndexSet indexSetWithIndexesInRange:indexRange];
- }
- - (ReaderThumbView *)thumbCellContainingPoint:(CGPoint)point
- {
- ReaderThumbView *theCell = nil;
- for (ReaderThumbView *tvCell in thumbCellsVisible)
- {
- if (CGRectContainsPoint(tvCell.frame, point) == true)
- {
- theCell = tvCell; break;
- }
- }
- return theCell;
- }
- - (CGRect)thumbCellFrameForIndex:(NSInteger)index
- {
- CGRect thumbRect; thumbRect.size = _thumbSize;
- NSInteger thumbY = ((index / _thumbsX) * _thumbSize.height);
- NSInteger thumbX = (((index % _thumbsX) * _thumbSize.width) + _thumbX);
- thumbRect.origin.x = thumbX; thumbRect.origin.y = thumbY;
- return thumbRect;
- }
- - (void)updateContentSize:(NSUInteger)thumbCount
- {
- canUpdate = NO;
- if (thumbCount > 0)
- {
- CGFloat bw = self.bounds.size.width;
- _thumbsX = (bw / _thumbSize.width);
- if (_thumbsX < 1) _thumbsX = 1;
- _thumbsY = (thumbCount / _thumbsX);
- if ((_thumbsX * _thumbsY) < thumbCount) _thumbsY++;
- CGFloat tw = (_thumbsX * _thumbSize.width);
- CGFloat th = (_thumbsY * _thumbSize.height);
- if (tw < bw)
- _thumbX = ((bw - tw) * 0.5f);
- else
- _thumbX = 0;
- if (tw < bw) tw = bw;
- [self setContentSize:CGSizeMake(tw, th)];
- }
- else
- {
- [self setContentSize:CGSizeZero];
- }
- canUpdate = YES;
- }
- - (void)layoutSubviews
- {
- if (CGSizeEqualToSize(_lastViewSize, CGSizeZero) == true)
- {
- _lastViewSize = self.bounds.size;
- }
- else
- if (CGSizeEqualToSize(_lastViewSize, self.bounds.size) == false)
- {
- _lastViewSize = self.bounds.size;
- [self updateContentSize:_thumbCount];
- NSMutableArray *requeueCells = [NSMutableArray array];
- NSMutableIndexSet *visibleIndexSet = [self visibleIndexSetForContentOffset];
- for (ReaderThumbView *tvCell in thumbCellsVisible)
- {
- NSInteger index = tvCell.tag;
- if ([visibleIndexSet containsIndex:index] == YES)
- {
- tvCell.frame = [self thumbCellFrameForIndex:index];
- [visibleIndexSet removeIndex:index];
- }
- else
- {
- [requeueCells addObject:tvCell];
- }
- }
- for (ReaderThumbView *tvCell in requeueCells)
- {
- [self requeueThumbCell:tvCell];
- }
- [visibleIndexSet enumerateIndexesUsingBlock:
- ^(NSUInteger index, BOOL *stop)
- {
- CGRect thumbRect = [self thumbCellFrameForIndex:index];
- ReaderThumbView *tvCell = [self dequeueThumbCellWithFrame:thumbRect];
- [delegate thumbsView:self updateThumbCell:tvCell forIndex:index];
- tvCell.tag = index; tvCell.hidden = NO;
- }
- ];
- }
- }
- - (void)setThumbSize:(CGSize)thumbSize
- {
- if (CGSizeEqualToSize(_thumbSize, CGSizeZero) == true)
- {
- if (CGSizeEqualToSize(thumbSize, CGSizeZero) == false)
- {
- _thumbSize = thumbSize;
- }
- }
- }
- - (void)reloadThumbsCenterOnIndex:(NSInteger)index
- {
- assert(delegate != nil);
- assert(CGSizeEqualToSize(_thumbSize, CGSizeZero) == false);
- if (self.decelerating == YES)
- {
- [self setContentOffset:self.contentOffset animated:NO];
- }
- CGPoint newContentOffset = CGPointZero;
- lastContentOffset = CGPointMake(CGFLOAT_MIN, CGFLOAT_MIN);
- [self requeueAllThumbCells];
- _thumbCount = 0;
- NSUInteger thumbCount = [delegate numberOfThumbsInThumbsView:self];
- [self updateContentSize:thumbCount]; _thumbCount = thumbCount;
- if (thumbCount > 0)
- {
- NSInteger boundsHeight = self.bounds.size.height;
- NSInteger maxY = (self.contentSize.height - boundsHeight);
- NSInteger minY = 0; maxY--; if (maxY < minY) maxY = minY;
- if (index < 0) index = 0; else if (index > thumbCount) index = (thumbCount - 1);
- NSInteger thumbY = ((index / _thumbsX) * _thumbSize.height);
- NSInteger offsetY = (thumbY - (boundsHeight / 2) + (_thumbSize.height / 2));
- if (offsetY < minY) offsetY = minY; else if (offsetY > maxY) offsetY = maxY;
- newContentOffset.y = offsetY;
- }
- newContentOffset.y -= self.contentInset.top;
- if (CGPointEqualToPoint(self.contentOffset, newContentOffset) == false)
- [self setContentOffset:newContentOffset animated:NO];
- else
- [self scrollViewDidScroll:self];
- [self flashScrollIndicators];
- }
- - (void)reloadThumbsContentOffset:(CGPoint)newContentOffset
- {
- assert(delegate != nil);
- assert(CGSizeEqualToSize(_thumbSize, CGSizeZero) == false);
- if (self.decelerating == YES)
- {
- [self setContentOffset:self.contentOffset animated:NO];
- }
- lastContentOffset = CGPointMake(CGFLOAT_MIN, CGFLOAT_MIN);
- [self requeueAllThumbCells];
- _thumbCount = 0;
- NSUInteger thumbCount = [delegate numberOfThumbsInThumbsView:self];
- [self updateContentSize:thumbCount]; _thumbCount = thumbCount;
- if (thumbCount > 0)
- {
- NSInteger boundsHeight = self.bounds.size.height;
- NSInteger maxY = (self.contentSize.height - boundsHeight);
- NSInteger minY = 0; maxY--; if (maxY < minY) maxY = minY;
- NSInteger offsetY = newContentOffset.y;
- if (offsetY < minY) offsetY = minY; else if (offsetY > maxY) offsetY = maxY;
- newContentOffset.y = offsetY; newContentOffset.x = 0.0f;
- }
- else
- {
- newContentOffset = CGPointZero;
- }
- newContentOffset.y -= self.contentInset.top;
- if (CGPointEqualToPoint(self.contentOffset, newContentOffset) == false)
- [self setContentOffset:newContentOffset animated:NO];
- else
- [self scrollViewDidScroll:self];
- [self flashScrollIndicators];
- }
- - (void)refreshThumbWithIndex:(NSInteger)index
- {
- for (ReaderThumbView *tvCell in thumbCellsVisible)
- {
- if (tvCell.tag == index)
- {
- if ([delegate respondsToSelector:@selector(thumbsView:refreshThumbCell:forIndex:)])
- {
- [delegate thumbsView:self refreshThumbCell:tvCell forIndex:index];
- }
- break;
- }
- }
- }
- - (void)refreshVisibleThumbs
- {
- for (ReaderThumbView *tvCell in thumbCellsVisible)
- {
- if ([delegate respondsToSelector:@selector(thumbsView:refreshThumbCell:forIndex:)])
- {
- [delegate thumbsView:self refreshThumbCell:tvCell forIndex:tvCell.tag];
- }
- }
- }
- - (CGPoint)insetContentOffset
- {
- CGPoint insetContentOffset = self.contentOffset;
- insetContentOffset.y += self.contentInset.top;
- return insetContentOffset;
- }
- #pragma mark - UIGestureRecognizer action methods
- - (void)handleTapGesture:(UITapGestureRecognizer *)recognizer
- {
- if (recognizer.state == UIGestureRecognizerStateRecognized)
- {
- CGPoint point = [recognizer locationInView:recognizer.view];
- ReaderThumbView *tvCell = [self thumbCellContainingPoint:point];
- if (tvCell != nil) [delegate thumbsView:self didSelectThumbWithIndex:tvCell.tag];
- }
- }
- - (void)handlePressGesture:(UILongPressGestureRecognizer *)recognizer
- {
- if (recognizer.state == UIGestureRecognizerStateBegan)
- {
- if ([delegate respondsToSelector:@selector(thumbsView:didPressThumbWithIndex:)])
- {
- CGPoint point = [recognizer locationInView:recognizer.view];
- ReaderThumbView *tvCell = [self thumbCellContainingPoint:point];
- if (tvCell != nil) [delegate thumbsView:self didPressThumbWithIndex:tvCell.tag];
- }
- }
- }
- #pragma mark - UIScrollViewDelegate methods
- - (void)scrollViewDidScroll:(UIScrollView *)scrollView
- {
- if ((canUpdate == YES) && (_thumbCount > 0))
- {
- if (CGPointEqualToPoint(scrollView.contentOffset, lastContentOffset) == false)
- {
- lastContentOffset = scrollView.contentOffset;
- CGRect visibleBounds = self.bounds;
- NSMutableArray *requeueCells = [NSMutableArray array];
- NSMutableIndexSet *visibleCellSet = [NSMutableIndexSet indexSet];
- for (ReaderThumbView *tvCell in thumbCellsVisible)
- {
- if (CGRectIntersectsRect(tvCell.frame, visibleBounds) == true)
- [visibleCellSet addIndex:tvCell.tag];
- else
- [requeueCells addObject:tvCell];
- }
- for (ReaderThumbView *tvCell in requeueCells)
- {
- [self requeueThumbCell:tvCell];
- }
- NSMutableIndexSet *visibleIndexSet = [self visibleIndexSetForContentOffset];
- [visibleIndexSet enumerateIndexesUsingBlock:
- ^(NSUInteger index, BOOL *stop)
- {
- if ([visibleCellSet containsIndex:index] == NO)
- {
- CGRect thumbRect = [self thumbCellFrameForIndex:index];
- ReaderThumbView *tvCell = [self dequeueThumbCellWithFrame:thumbRect];
- [delegate thumbsView:self updateThumbCell:tvCell forIndex:index];
- tvCell.tag = index; tvCell.hidden = NO;
- }
- }
- ];
- }
- }
- }
- #pragma mark - UIResponder instance methods
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesBegan:touches withEvent:event];
- if (touchedCell != nil) { [touchedCell showTouched:NO]; touchedCell = nil; }
- if (touches.count == 1)
- {
- UITouch *touch = [touches anyObject];
- CGPoint point = [touch locationInView:touch.view];
- ReaderThumbView *tvCell = [self thumbCellContainingPoint:point];
- if (tvCell != nil) { touchedCell = tvCell; [touchedCell showTouched:YES]; }
- }
- }
- - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesCancelled:touches withEvent:event];
- if (touchedCell != nil) { [touchedCell showTouched:NO]; touchedCell = nil; }
- }
- - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesEnded:touches withEvent:event];
- if (touchedCell != nil) { [touchedCell showTouched:NO]; touchedCell = nil; }
- }
- - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesMoved:touches withEvent:event];
- }
- @end
|