| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- //
- // ZKCycleScrollView.m
- // ZKCycleScrollViewDemo-OC
- //
- // Created by bestdew on 2019/3/9.
- // Copyright © 2019 bestdew. All rights reserved.
- //
- // d*##$.
- // zP"""""$e. $" $o
- //4$ '$ $" $
- //'$ '$ J$ $F
- // 'b $k $> $
- // $k $r J$ d$
- // '$ $ $" $~
- // '$ "$ '$E $
- // $ $L $" $F ...
- // $. 4B $ $$$*"""*b
- // '$ $. $$ $$ $F
- // "$ R$ $F $" $
- // $k ?$ u* dF .$
- // ^$. $$" z$ u$$$$e
- // #$b $E.dW@e$" ?$
- // #$ .o$$# d$$$$c ?F
- // $ .d$$#" . zo$> #$r .uF
- // $L .u$*" $&$$$k .$$d$$F
- // $$" ""^"$$$P"$P9$
- // JP .o$$$$u:$P $$
- // $ ..ue$" "" $"
- // d$ $F $
- // $$ ....udE 4B
- // #$ """"` $r @$
- // ^$L '$ $F
- // RN 4N $
- // *$b d$
- // $$k $F
- // $$b $F
- // $"" $F
- // '$ $
- // $L $
- // '$ $
- // $ $
- #import "ZKCycleScrollView.h"
- #import "ZKCycleScrollViewFlowLayout.h"
- @interface ZKCycleScrollView () <UICollectionViewDelegate, UICollectionViewDataSource>
- @property (nonatomic, strong) ZKCycleScrollViewFlowLayout *flowLayout;
- @property (nonatomic, strong) UICollectionView *collectionView;
- @property (nonatomic, strong) UIPageControl *pageControl;
- @property (nonatomic, strong) NSTimer *timer;
- @property (nonatomic, assign) NSInteger fromIndex;
- @property (nonatomic, assign) NSInteger numberOfItems;
- @property (nonatomic, assign) BOOL itemSizeFlag;
- @property (nonatomic, assign) NSInteger indexOffset;
- @property (nonatomic, assign) BOOL configuredFlag;
- @property (nonatomic, assign) NSInteger tempIndex;
- @end
- @implementation ZKCycleScrollView
- #pragma mark -- Init
- - (instancetype)initWithFrame:(CGRect)frame {
- return [self initWithFrame:frame shouldInfiniteLoop:YES];
- }
- - (instancetype)initWithFrame:(CGRect)frame shouldInfiniteLoop:(BOOL)infiniteLoop {
- if (self = [super initWithFrame:frame]) {
-
- _infiniteLoop = infiniteLoop;
- [self initialization];
- }
- return self;
- }
- - (instancetype)initWithCoder:(NSCoder *)aDecoder {
- if (self = [super initWithCoder:aDecoder]) {
-
- _infiniteLoop = YES;
- [self initialization];
- }
- return self;
- }
- - (void)initialization {
- _autoScroll = YES;
- _itemZoomScale = 1.f;
- _allowsDragging = YES;
- _autoScrollInterval = 3.f;
- _pageIndicatorTintColor = [UIColor grayColor];
- _currentPageIndicatorTintColor = [UIColor whiteColor];
-
- _flowLayout = [[ZKCycleScrollViewFlowLayout alloc] init];
- _flowLayout.minimumLineSpacing = 0.f;
- _flowLayout.minimumInteritemSpacing = 0.f;
- _flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
- _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_flowLayout];
- _collectionView.backgroundColor = nil;
- _collectionView.delegate = self;
- _collectionView.dataSource = self;
- _collectionView.bounces = NO;
- _collectionView.scrollsToTop = NO;
- _collectionView.showsHorizontalScrollIndicator = NO;
- _collectionView.showsVerticalScrollIndicator = NO;
- [self addSubview:_collectionView];
-
- _pageControl = [[UIPageControl alloc] init];
- _pageControl.enabled = NO;
- _pageControl.hidesForSinglePage = YES;
- _pageControl.pageIndicatorTintColor = _pageIndicatorTintColor;
- _pageControl.currentPageIndicatorTintColor = _currentPageIndicatorTintColor;
- [self addSubview:_pageControl];
-
- dispatch_async(dispatch_get_main_queue(), ^{
- [self configuration];
- if (_loadCompletion) _loadCompletion();
- });
- }
- - (void)layoutSubviews {
- [super layoutSubviews];
-
- if (_itemSizeFlag) {
- _flowLayout.itemSize = _itemSize;
- _flowLayout.headerReferenceSize = CGSizeMake((self.bounds.size.width - _itemSize.width) / 2, (self.bounds.size.height - _itemSize.height) / 2);
- _flowLayout.footerReferenceSize = CGSizeMake((self.bounds.size.width - _itemSize.width) / 2, (self.bounds.size.height - _itemSize.height) / 2);
- } else {
- _flowLayout.itemSize = self.bounds.size;
- _flowLayout.headerReferenceSize = CGSizeZero;
- _flowLayout.footerReferenceSize = CGSizeZero;
- }
- _collectionView.frame = self.bounds;
- _pageControl.frame = CGRectMake(0.f, self.bounds.size.height - 15.f, self.bounds.size.width, 15.f);
- }
- - (void)willMoveToSuperview:(UIView *)newSuperview {
- if (newSuperview == nil) [self removeTimer];
- }
- - (void)dealloc {
- _collectionView.delegate = nil;
- _collectionView.dataSource = nil;
- }
- #pragma mark -- Public Methods
- - (void)registerCellClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier {
- [_collectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
- }
- - (void)registerCellNib:(nullable UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier {
- [_collectionView registerNib:nib forCellWithReuseIdentifier:identifier];
- }
- - (__kindof ZKCycleScrollViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index {
- index = [self changeIndex:index];
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
- return [_collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
- }
- - (void)reloadData {
- [self removeTimer];
- [UIView performWithoutAnimation:^{
- [_collectionView reloadData];
- }];
- [_collectionView performBatchUpdates:nil completion:^(BOOL finished) {
- [self configuration];
- if (_loadCompletion) _loadCompletion();
- }];
- }
- - (void)scrollToItemAtIndex:(NSInteger)index animated:(BOOL)animated {
- NSInteger numberOfAddedCells = [self numberOfAddedCells];
- if (index < 0 || index > _numberOfItems - numberOfAddedCells - 1) {
- NSLog(@"⚠️attempt to scroll to invalid index:%zd", index);
- return;
- }
- [self removeTimer];
- index += numberOfAddedCells / 2;
- UICollectionViewScrollPosition position = [self scrollPosition];
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
- [_collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:position animated:animated];
- [self addTimer];
- }
- - (__kindof ZKCycleScrollViewCell *)cellForItemAtIndex:(NSInteger)index {
- NSInteger numberOfAddedCells = [self numberOfAddedCells];
- if (index < 0 || index > _numberOfItems - numberOfAddedCells - 1) {
- return nil;
- }
- index += numberOfAddedCells / 2;
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
- ZKCycleScrollViewCell *cell = [_collectionView cellForItemAtIndexPath:indexPath];
-
- return cell;
- }
- - (void)beginUpdates {
- _tempIndex = [self pageIndex];
- [self removeTimer];
- }
- - (void)endUpdates {
- [_flowLayout invalidateLayout];
- [self scrollToItemAtIndex:_tempIndex animated:NO];
- [self addTimer];
- }
- #pragma mark -- Private Methods
- - (UICollectionViewScrollPosition)scrollPosition {
- switch (_scrollDirection) {
- case ZKScrollDirectionVertical:
- return UICollectionViewScrollPositionCenteredVertically;
- default:
- return UICollectionViewScrollPositionCenteredHorizontally;
- }
- }
- - (NSInteger)changeIndex:(NSInteger)index {
- if (_infiniteLoop && _numberOfItems > 1) {
- if (index == 0) {
- index = _numberOfItems - 6;
- } else if (index == 1) {
- index = _numberOfItems - 5;
- } else if (index == _numberOfItems - 2) {
- index = 0;
- } else if (index == _numberOfItems - 1) {
- index = 1;
- } else {
- index -= 2;
- }
- }
- return index;
- }
- - (void)configuration {
- _fromIndex = 0;
- _indexOffset = 0;
- _configuredFlag = NO;
-
- if (_numberOfItems < 2) return;
-
- if (_infiniteLoop) {
- UICollectionViewScrollPosition position = [self scrollPosition];
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:2 inSection:0];
- [_collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:position animated:NO];
- }
-
- [self addTimer];
- [self updatePageControl];
-
- _configuredFlag = YES;
- }
- - (void)addTimer {
- [self removeTimer];
-
- if (_numberOfItems < 2 || !_autoScroll || _autoScrollInterval <= 0.f) return;
-
- _timer = [NSTimer scheduledTimerWithTimeInterval:_autoScrollInterval target:self selector:@selector(automaticScroll) userInfo:nil repeats:YES];
- [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
- }
- - (void)automaticScroll {
- NSInteger index = [self currentIndex] + 1;
- if (!_infiniteLoop && index >= _numberOfItems) index = 0;
- UICollectionViewScrollPosition position = [self scrollPosition];
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
- [_collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:position animated:YES];
- }
- - (void)removeTimer {
- if (!_timer) return;
- [_timer invalidate];
- _timer = nil;
- }
- - (void)updatePageControl {
- _pageControl.currentPage = 0;
- _pageControl.numberOfPages = MAX(0, _numberOfItems - [self numberOfAddedCells]);
- _pageControl.hidden = (_hidesPageControl || _pageControl.numberOfPages < 2);
- }
- #pragma mark -- UICollectionView DataSource
- - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
- if ([_dataSource respondsToSelector:@selector(numberOfItemsInCycleScrollView:)]) {
- _numberOfItems = [_dataSource numberOfItemsInCycleScrollView:self];
- if (_infiniteLoop && _numberOfItems > 1) {
- _numberOfItems += [self numberOfAddedCells];
- }
- }
- return _numberOfItems;
- }
- - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
- NSInteger index = [self changeIndex:indexPath.item];
- return [_dataSource cycleScrollView:self cellForItemAtIndex:index];
- }
- #pragma mark -- UICollectionView Delegate
- - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
- return YES;
- }
- - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
- if ([_delegate respondsToSelector:@selector(cycleScrollView:didSelectItemAtIndex:)]) {
- [self removeTimer];
- }
- }
- - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath {
- if ([_delegate respondsToSelector:@selector(cycleScrollView:didSelectItemAtIndex:)]) {
- [self addTimer];
- }
- }
- - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
- if ([_delegate respondsToSelector:@selector(cycleScrollView:didSelectItemAtIndex:)]) {
- NSInteger index = [self changeIndex:indexPath.item];
- [_delegate cycleScrollView:self didSelectItemAtIndex:index];
- }
- }
- - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
- _pageControl.currentPage = [self pageIndex];
- if (_configuredFlag && [_delegate respondsToSelector:@selector(cycleScrollViewDidScroll:progress:)]) {
- CGFloat total = 0.f, offset = 0.f;
- NSInteger numberOfAddedCells = [self numberOfAddedCells];
- switch (_scrollDirection) {
- case ZKScrollDirectionVertical:
- total = (_numberOfItems - 1 - numberOfAddedCells) * (_flowLayout.itemSize.height + _flowLayout.minimumLineSpacing);
- offset = fmod([self contentOffset].y, (_flowLayout.itemSize.height + _flowLayout.minimumLineSpacing) * (_numberOfItems - numberOfAddedCells));
- break;
- default:
- total = (_numberOfItems - 1 - numberOfAddedCells) * (_flowLayout.itemSize.width + _flowLayout.minimumLineSpacing);
- offset = fmod([self contentOffset].x, (_flowLayout.itemSize.width + _flowLayout.minimumLineSpacing) * (_numberOfItems - numberOfAddedCells));
- break;
- }
- CGFloat percent = offset / total;
- CGFloat progress = percent * (_numberOfItems - 1 - numberOfAddedCells);
- [_delegate cycleScrollViewDidScroll:self progress:progress];
- }
- }
- - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
- [self removeTimer];
- }
- - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
- [self addTimer];
- }
- - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
- NSInteger index = [self currentIndex];
- if (_infiniteLoop) {
- UICollectionViewScrollPosition position = [self scrollPosition];
- if (index == 1) {
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:_numberOfItems - 3 inSection:0];
- [_collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:position animated:NO];
- } else if (index == _numberOfItems - 2) {
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:2 inSection:0];
- [_collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:position animated:NO];
- }
- }
- NSInteger toIndex = [self changeIndex:index];
- if ([_delegate respondsToSelector:@selector(cycleScrollView:didScrollFromIndex:toIndex:)]) {
- [_delegate cycleScrollView:self didScrollFromIndex:_fromIndex toIndex:toIndex];
- }
- _fromIndex = toIndex;
- }
- - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
- if ([self pageIndex] != _fromIndex) return;
-
- CGFloat sum = velocity.x + velocity.y;
- if (sum > 0) {
- _indexOffset = 1;
- } else if (sum < 0) {
- _indexOffset = -1;
- } else {
- _indexOffset = 0;
- }
- }
- - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
- NSInteger index = [self currentIndex] + _indexOffset;
- index = MAX(0, index);
- index = MIN(_numberOfItems - 1, index);
- UICollectionViewScrollPosition position = [self scrollPosition];
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
- [_collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:position animated:YES];
- _indexOffset = 0;
- }
- #pragma mark -- Getter & Setter
- - (NSInteger)currentIndex {
- if (_numberOfItems <= 0) {
- return -1;
- }
-
- NSInteger index = 0;
- NSInteger minimumIndex = 0;
- NSInteger maximumIndex = _numberOfItems - 1;
-
- if (_numberOfItems == 1) {
- return index;
- }
-
- if (_infiniteLoop) {
- minimumIndex = 1;
- maximumIndex = _numberOfItems - 2;
- }
-
- switch (_scrollDirection) {
- case ZKScrollDirectionVertical:
- index = (_collectionView.contentOffset.y + (_flowLayout.itemSize.height + _flowLayout.minimumLineSpacing) / 2) / (_flowLayout.itemSize.height + _flowLayout.minimumLineSpacing);
- break;
- default:
- index = (_collectionView.contentOffset.x + (_flowLayout.itemSize.width + _flowLayout.minimumLineSpacing) / 2) / (_flowLayout.itemSize.width + _flowLayout.minimumLineSpacing);
- break;
- }
- return MIN(maximumIndex, MAX(minimumIndex, index));
- }
- - (NSInteger)pageIndex {
- return [self changeIndex:[self currentIndex]];
- }
- - (CGPoint)contentOffset {
- NSInteger numberOfAddedCells = [self numberOfAddedCells];
- switch (_scrollDirection) {
- case ZKScrollDirectionVertical:
- return CGPointMake(0.f, MAX(0.f, _collectionView.contentOffset.y - (_flowLayout.itemSize.height + _flowLayout.minimumLineSpacing) * numberOfAddedCells / 2));
- default:
- return CGPointMake(MAX(0.f, (_collectionView.contentOffset.x - (_flowLayout.itemSize.width + _flowLayout.minimumLineSpacing) * numberOfAddedCells / 2)), 0.f);
- }
- }
- - (NSInteger)numberOfAddedCells {
- return _infiniteLoop ? 4 : 0;
- }
- - (void)setScrollDirection:(ZKScrollDirection)scrollDirection {
- _scrollDirection = scrollDirection;
-
- switch (scrollDirection) {
- case ZKScrollDirectionVertical:
- _flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
- break;
- default:
- _flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
- break;
- }
- }
- - (void)setAutoScroll:(BOOL)autoScroll {
- _autoScroll = autoScroll;
- [self addTimer];
- }
- - (void)setAllowsDragging:(BOOL)allowsDragging {
- _allowsDragging = allowsDragging;
- _collectionView.scrollEnabled = allowsDragging;
- }
- - (void)setAutoScrollInterval:(NSTimeInterval)autoScrollInterval {
- _autoScrollInterval = autoScrollInterval;
- [self addTimer];
- }
- - (void)setItemSize:(CGSize)itemSize {
- _itemSizeFlag = YES;
- _itemSize = itemSize;
- _flowLayout.itemSize = itemSize;
- _flowLayout.headerReferenceSize = CGSizeMake((self.bounds.size.width - itemSize.width) / 2, (self.bounds.size.height - itemSize.height) / 2);
- _flowLayout.footerReferenceSize = CGSizeMake((self.bounds.size.width - itemSize.width) / 2, (self.bounds.size.height - itemSize.height) / 2);
- }
- - (void)setItemSpacing:(CGFloat)itemSpacing {
- _itemSpacing = itemSpacing;
- _flowLayout.minimumLineSpacing = itemSpacing;
- }
- - (void)setItemZoomScale:(CGFloat)itemZoomScale {
- _itemZoomScale = itemZoomScale;
- _flowLayout.zoomScale = itemZoomScale;
- }
- - (void)setHidesPageControl:(BOOL)hidesPageControl {
- _hidesPageControl = hidesPageControl;
- _pageControl.hidden = hidesPageControl;
- }
- - (void)setPageIndicatorTintColor:(UIColor *)pageIndicatorTintColor {
- _pageIndicatorTintColor = pageIndicatorTintColor;
- _pageControl.pageIndicatorTintColor = pageIndicatorTintColor;
- }
- - (void)setCurrentPageIndicatorTintColor:(UIColor *)currentPageIndicatorTintColor {
- _currentPageIndicatorTintColor = currentPageIndicatorTintColor;
- _pageControl.currentPageIndicatorTintColor = currentPageIndicatorTintColor;
- }
- @end
|