| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174 |
- //
- // BGSwipeView.m
- // BuguLive
- //
- // Created by 朱庆彬 on 2017/8/16.
- // Copyright © 2017年 xfg. All rights reserved.
- //
- #import "BGSwipeView.h"
- #import <objc/message.h>
- #pragma GCC diagnostic ignored "-Wdirect-ivar-access"
- #pragma GCC diagnostic ignored "-Warc-repeated-use-of-weak"
- #pragma GCC diagnostic ignored "-Wreceiver-is-weak"
- #pragma GCC diagnostic ignored "-Wconversion"
- #pragma GCC diagnostic ignored "-Wselector"
- #pragma GCC diagnostic ignored "-Wgnu"
- #import <Availability.h>
- #if !__has_feature(objc_arc)
- #error This class requires automatic reference counting
- #endif
- @implementation NSObject (BGSwipeView)
- - (CGSize)swipeViewItemSize:(__unused BGSwipeView *)swipeView { return CGSizeZero; }
- - (void)swipeViewDidScroll:(__unused BGSwipeView *)swipeView {}
- - (void)swipeViewCurrentItemIndexDidChange:(__unused BGSwipeView *)swipeView {}
- - (void)swipeViewWillBeginDragging:(__unused BGSwipeView *)swipeView {}
- - (void)swipeViewDidEndDragging:(__unused BGSwipeView *)swipeView willDecelerate:(__unused BOOL)decelerate {}
- - (void)swipeViewWillBeginDecelerating:(__unused BGSwipeView *)swipeView {}
- - (void)swipeViewDidEndDecelerating:(__unused BGSwipeView *)swipeView {}
- - (void)swipeViewDidEndScrollingAnimation:(__unused BGSwipeView *)swipeView {}
- - (BOOL)swipeView:(__unused BGSwipeView *)swipeView shouldSelectItemAtIndex:(__unused NSInteger)index { return YES; }
- - (void)swipeView:(__unused BGSwipeView *)swipeView didSelectItemAtIndex:(__unused NSInteger)index {}
- @end
- @interface BGSwipeView () <UIScrollViewDelegate, UIGestureRecognizerDelegate>
- @property (nonatomic, strong) UIScrollView *scrollView;
- @property (nonatomic, strong) NSMutableDictionary *itemViews;
- @property (nonatomic, strong) NSMutableSet *itemViewPool;
- @property (nonatomic, assign) NSInteger previousItemIndex;
- @property (nonatomic, assign) CGPoint previousContentOffset;
- @property (nonatomic, assign) CGSize itemSize;
- @property (nonatomic, assign) BOOL suppressScrollEvent;
- @property (nonatomic, assign) NSTimeInterval scrollDuration;
- @property (nonatomic, assign, getter=isScrolling) BOOL scrolling;
- @property (nonatomic, assign) NSTimeInterval startTime;
- @property (nonatomic, assign) NSTimeInterval lastTime;
- @property (nonatomic, assign) CGFloat startOffset;
- @property (nonatomic, assign) CGFloat endOffset;
- @property (nonatomic, assign) CGFloat lastUpdateOffset;
- @property (nonatomic, strong) NSTimer *timer;
- @end
- @implementation BGSwipeView
- #pragma mark -
- #pragma mark Initialisation
- - (void)setUp
- {
- _scrollEnabled = YES;
- _pagingEnabled = YES;
- _delaysContentTouches = YES;
- _bounces = YES;
- _wrapEnabled = NO;
- _itemsPerPage = 1;
- _truncateFinalPage = NO;
- _defersItemViewLoading = NO;
- _vertical = NO;
- _scrollView = [[UIScrollView alloc] init];
- _scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
- _scrollView.autoresizesSubviews = YES;
- _scrollView.delegate = self;
- _scrollView.delaysContentTouches = _delaysContentTouches;
- _scrollView.bounces = _bounces && !_wrapEnabled;
- _scrollView.alwaysBounceHorizontal = !_vertical && _bounces;
- _scrollView.alwaysBounceVertical = _vertical && _bounces;
- _scrollView.pagingEnabled = _pagingEnabled;
- _scrollView.scrollEnabled = _scrollEnabled;
- _scrollView.decelerationRate = _decelerationRate;
- _scrollView.showsHorizontalScrollIndicator = NO;
- _scrollView.showsVerticalScrollIndicator = NO;
- _scrollView.scrollsToTop = NO;
- _scrollView.clipsToBounds = NO;
- _decelerationRate = _scrollView.decelerationRate;
- _itemViews = [[NSMutableDictionary alloc] init];
- _previousItemIndex = 0;
- _previousContentOffset = _scrollView.contentOffset;
- _scrollOffset = 0.0f;
- _currentItemIndex = 0;
- _numberOfItems = 0;
- UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTap:)];
- tapGesture.delegate = self;
- [_scrollView addGestureRecognizer:tapGesture];
- self.clipsToBounds = YES;
- //place scrollview at bottom of hierarchy
- [self insertSubview:_scrollView atIndex:0];
- if (_dataSource)
- {
- [self reloadData];
- }
- }
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- if ((self = [super initWithCoder:aDecoder]))
- {
- [self setUp];
- }
- return self;
- }
- - (id)initWithFrame:(CGRect)frame
- {
- if ((self = [super initWithFrame:frame]))
- {
- [self setUp];
- }
- return self;
- }
- - (void)dealloc
- {
- [_timer invalidate];
- }
- - (void)setDataSource:(id<FWSwipeViewDataSource>)dataSource
- {
- if (_dataSource != dataSource)
- {
- _dataSource = dataSource;
- if (_dataSource)
- {
- [self reloadData];
- }
- }
- }
- - (void)setDelegate:(id<FWSwipeViewDelegate>)delegate
- {
- if (_delegate != delegate)
- {
- _delegate = delegate;
- [self setNeedsLayout];
- }
- }
- - (void)setAlignment:(FWSwipeViewAlignment)alignment
- {
- if (_alignment != alignment)
- {
- _alignment = alignment;
- [self setNeedsLayout];
- }
- }
- - (void)setItemsPerPage:(NSInteger)itemsPerPage
- {
- if (_itemsPerPage != itemsPerPage)
- {
- _itemsPerPage = itemsPerPage;
- [self setNeedsLayout];
- }
- }
- - (void)setTruncateFinalPage:(BOOL)truncateFinalPage
- {
- if (_truncateFinalPage != truncateFinalPage)
- {
- _truncateFinalPage = truncateFinalPage;
- [self setNeedsLayout];
- }
- }
- - (void)setScrollEnabled:(BOOL)scrollEnabled
- {
- if (_scrollEnabled != scrollEnabled)
- {
- _scrollEnabled = scrollEnabled;
- _scrollView.scrollEnabled = _scrollEnabled;
- }
- }
- - (void)setPagingEnabled:(BOOL)pagingEnabled
- {
- if (_pagingEnabled != pagingEnabled)
- {
- _pagingEnabled = pagingEnabled;
- _scrollView.pagingEnabled = pagingEnabled;
- [self setNeedsLayout];
- }
- }
- - (void)setWrapEnabled:(BOOL)wrapEnabled
- {
- if (_wrapEnabled != wrapEnabled)
- {
- CGFloat previousOffset = [self clampedOffset:_scrollOffset];
- _wrapEnabled = wrapEnabled;
- _scrollView.bounces = _bounces && !_wrapEnabled;
- [self setNeedsLayout];
- [self layoutIfNeeded];
- self.scrollOffset = previousOffset;
- }
- }
- - (void)setDelaysContentTouches:(BOOL)delaysContentTouches
- {
- _delaysContentTouches = delaysContentTouches;
- _scrollView.delaysContentTouches = delaysContentTouches;
- }
- - (void)setBounces:(BOOL)bounces
- {
- if (_bounces != bounces)
- {
- _bounces = bounces;
- _scrollView.alwaysBounceHorizontal = !_vertical && _bounces;
- _scrollView.alwaysBounceVertical = _vertical && _bounces;
- _scrollView.bounces = _bounces && !_wrapEnabled;
- }
- }
- - (void)setDecelerationRate:(float)decelerationRate
- {
- if (fabsf(_decelerationRate - decelerationRate) > 0.0001f)
- {
- _decelerationRate = decelerationRate;
- _scrollView.decelerationRate = _decelerationRate;
- }
- }
- - (void)setAutoscroll:(CGFloat)autoscroll
- {
- if (fabs(_autoscroll - autoscroll) > 0.0001f)
- {
- _autoscroll = autoscroll;
- if (autoscroll)
- [self startAnimation];
- }
- }
- - (void)setVertical:(BOOL)vertical
- {
- if (_vertical != vertical)
- {
- _vertical = vertical;
- _scrollView.alwaysBounceHorizontal = !_vertical && _bounces;
- _scrollView.alwaysBounceVertical = _vertical && _bounces;
- [self setNeedsLayout];
- }
- }
- - (BOOL)isDragging
- {
- return _scrollView.dragging;
- }
- - (BOOL)isDecelerating
- {
- return _scrollView.decelerating;
- }
- #pragma mark -
- #pragma mark View management
- - (NSArray *)indexesForVisibleItems
- {
- return [[_itemViews allKeys] sortedArrayUsingSelector:@selector(compare:)];
- }
- - (NSArray *)visibleItemViews
- {
- NSArray *indexes = [self indexesForVisibleItems];
- return [_itemViews objectsForKeys:indexes notFoundMarker:[NSNull null]];
- }
- - (UIView *)itemViewAtIndex:(NSInteger)index
- {
- return _itemViews[@(index)];
- }
- - (UIView *)currentItemView
- {
- return [self itemViewAtIndex:_currentItemIndex];
- }
- - (NSInteger)indexOfItemView:(UIView *)view
- {
- NSUInteger index = [[_itemViews allValues] indexOfObject:view];
- if (index != NSNotFound)
- {
- return [[_itemViews allKeys][index] integerValue];
- }
- return NSNotFound;
- }
- - (NSInteger)indexOfItemViewOrSubview:(UIView *)view
- {
- NSInteger index = [self indexOfItemView:view];
- if (index == NSNotFound && view != nil && view != _scrollView)
- {
- return [self indexOfItemViewOrSubview:view.superview];
- }
- return index;
- }
- - (void)setItemView:(UIView *)view forIndex:(NSInteger)index
- {
- ((NSMutableDictionary *) _itemViews)[@(index)] = view;
- }
- #pragma mark -
- #pragma mark View layout
- - (void)updateScrollOffset
- {
- if (_wrapEnabled)
- {
- CGFloat itemsWide = (_numberOfItems == 1) ? 1.0f : 3.0f;
- if (_vertical)
- {
- CGFloat scrollHeight = _scrollView.contentSize.height / itemsWide;
- if (_scrollView.contentOffset.y < scrollHeight)
- {
- _previousContentOffset.y += scrollHeight;
- [self setContentOffsetWithoutEvent:CGPointMake(0.0f, _scrollView.contentOffset.y + scrollHeight)];
- }
- else if (_scrollView.contentOffset.y >= scrollHeight * 2.0f)
- {
- _previousContentOffset.y -= scrollHeight;
- [self setContentOffsetWithoutEvent:CGPointMake(0.0f, _scrollView.contentOffset.y - scrollHeight)];
- }
- _scrollOffset = [self clampedOffset:_scrollOffset];
- }
- else
- {
- CGFloat scrollWidth = _scrollView.contentSize.width / itemsWide;
- if (_scrollView.contentOffset.x < scrollWidth)
- {
- _previousContentOffset.x += scrollWidth;
- [self setContentOffsetWithoutEvent:CGPointMake(_scrollView.contentOffset.x + scrollWidth, 0.0f)];
- }
- else if (_scrollView.contentOffset.x >= scrollWidth * 2.0f)
- {
- _previousContentOffset.x -= scrollWidth;
- [self setContentOffsetWithoutEvent:CGPointMake(_scrollView.contentOffset.x - scrollWidth, 0.0f)];
- }
- _scrollOffset = [self clampedOffset:_scrollOffset];
- }
- }
- if (_vertical && fabs(_scrollView.contentOffset.x) > 0.0001f)
- {
- [self setContentOffsetWithoutEvent:CGPointMake(0.0f, _scrollView.contentOffset.y)];
- }
- else if (!_vertical && fabs(_scrollView.contentOffset.y) > 0.0001f)
- {
- [self setContentOffsetWithoutEvent:CGPointMake(_scrollView.contentOffset.x, 0.0f)];
- }
- }
- - (void)updateScrollViewDimensions
- {
- CGRect frame = self.bounds;
- CGSize contentSize = frame.size;
- if (_vertical)
- {
- contentSize.width -= (_scrollView.contentInset.left + _scrollView.contentInset.right);
- }
- else
- {
- contentSize.height -= (_scrollView.contentInset.top + _scrollView.contentInset.bottom);
- }
- switch (_alignment)
- {
- case FWSwipeViewAlignmentCenter:
- {
- if (_vertical)
- {
- frame = CGRectMake(0.0f, (self.bounds.size.height - _itemSize.height * _itemsPerPage) / 2.0f,
- self.bounds.size.width, _itemSize.height * _itemsPerPage);
- contentSize.height = _itemSize.height * _numberOfItems;
- }
- else
- {
- frame = CGRectMake((self.bounds.size.width - _itemSize.width * _itemsPerPage) / 2.0f,
- 0.0f, _itemSize.width * _itemsPerPage, self.bounds.size.height);
- contentSize.width = _itemSize.width * _numberOfItems;
- }
- break;
- }
- case FWSwipeViewAlignmentEdge:
- {
- if (_vertical)
- {
- frame = CGRectMake(0.0f, 0.0f, self.bounds.size.width, _itemSize.height * _itemsPerPage);
- contentSize.height = _itemSize.height * _numberOfItems - (self.bounds.size.height - frame.size.height);
- }
- else
- {
- frame = CGRectMake(0.0f, 0.0f, _itemSize.width * _itemsPerPage, self.bounds.size.height);
- contentSize.width = _itemSize.width * _numberOfItems - (self.bounds.size.width - frame.size.width);
- }
- break;
- }
- }
- if (_wrapEnabled)
- {
- CGFloat itemsWide = (_numberOfItems == 1) ? 1.0f : _numberOfItems * 3.0f;
- if (_vertical)
- {
- contentSize.height = _itemSize.height * itemsWide;
- }
- else
- {
- contentSize.width = _itemSize.width * itemsWide;
- }
- }
- else if (_pagingEnabled && !_truncateFinalPage)
- {
- if (_vertical)
- {
- contentSize.height = ceilf(contentSize.height / frame.size.height) * frame.size.height;
- }
- else
- {
- contentSize.width = ceilf(contentSize.width / frame.size.width) * frame.size.width;
- }
- }
- if (!CGRectEqualToRect(_scrollView.frame, frame))
- {
- _scrollView.frame = frame;
- }
- if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize))
- {
- _scrollView.contentSize = contentSize;
- }
- }
- - (CGFloat)offsetForItemAtIndex:(NSInteger)index
- {
- //calculate relative position
- CGFloat offset = index - _scrollOffset;
- if (_wrapEnabled)
- {
- if (_alignment == FWSwipeViewAlignmentCenter)
- {
- if (offset > _numberOfItems / 2)
- {
- offset -= _numberOfItems;
- }
- else if (offset < -_numberOfItems / 2)
- {
- offset += _numberOfItems;
- }
- }
- else
- {
- CGFloat width = _vertical ? self.bounds.size.height : self.bounds.size.width;
- CGFloat x = _vertical ? _scrollView.frame.origin.y : _scrollView.frame.origin.x;
- CGFloat itemWidth = _vertical ? _itemSize.height : _itemSize.width;
- if (offset * itemWidth + x > width)
- {
- offset -= _numberOfItems;
- }
- else if (offset * itemWidth + x < -itemWidth)
- {
- offset += _numberOfItems;
- }
- }
- }
- return offset;
- }
- - (void)setFrameForView:(UIView *)view atIndex:(NSInteger)index
- {
- if (self.window)
- {
- CGPoint center = view.center;
- if (_vertical)
- {
- center.y = ([self offsetForItemAtIndex:index] + 0.5f) * _itemSize.height + _scrollView.contentOffset.y;
- }
- else
- {
- center.x = ([self offsetForItemAtIndex:index] + 0.5f) * _itemSize.width + _scrollView.contentOffset.x;
- }
- BOOL disableAnimation = !CGPointEqualToPoint(center, view.center);
- BOOL animationEnabled = [UIView areAnimationsEnabled];
- if (disableAnimation && animationEnabled)
- [UIView setAnimationsEnabled:NO];
- if (_vertical)
- {
- view.center = CGPointMake(_scrollView.frame.size.width / 2.0f, center.y);
- }
- else
- {
- view.center = CGPointMake(center.x, _scrollView.frame.size.height / 2.0f);
- }
- view.bounds = CGRectMake(0.0f, 0.0f, _itemSize.width, _itemSize.height);
- if (disableAnimation && animationEnabled)
- [UIView setAnimationsEnabled:YES];
- }
- }
- - (void)layOutItemViews
- {
- for (UIView *view in self.visibleItemViews)
- {
- [self setFrameForView:view atIndex:[self indexOfItemView:view]];
- }
- }
- - (void)updateLayout
- {
- [self updateScrollOffset];
- [self loadUnloadViews];
- [self layOutItemViews];
- }
- - (void)layoutSubviews
- {
- [super layoutSubviews];
- [self updateItemSizeAndCount];
- [self updateScrollViewDimensions];
- [self updateLayout];
- if (_pagingEnabled && !_scrolling)
- {
- [self scrollToItemAtIndex:self.currentItemIndex duration:0.25];
- }
- }
- #pragma mark -
- #pragma mark View queing
- - (void)queueItemView:(UIView *)view
- {
- if (view)
- {
- [_itemViewPool addObject:view];
- }
- }
- - (UIView *)dequeueItemView
- {
- UIView *view = [_itemViewPool anyObject];
- if (view)
- {
- [_itemViewPool removeObject:view];
- }
- return view;
- }
- #pragma mark -
- #pragma mark Scrolling
- - (void)didScroll
- {
- //handle wrap
- [self updateScrollOffset];
- //update view
- [self layOutItemViews];
- [_delegate swipeViewDidScroll:self];
- if (!_defersItemViewLoading || fabs([self minScrollDistanceFromOffset:_lastUpdateOffset toOffset:_scrollOffset]) >= 1.0f)
- {
- //update item index
- _currentItemIndex = [self clampedIndex:roundf(_scrollOffset)];
- //load views
- _lastUpdateOffset = _currentItemIndex;
- [self loadUnloadViews];
- //send index update event
- if (_previousItemIndex != _currentItemIndex)
- {
- _previousItemIndex = _currentItemIndex;
- [_delegate swipeViewCurrentItemIndexDidChange:self];
- }
- }
- }
- - (CGFloat)easeInOut:(CGFloat)time
- {
- return (time < 0.5f) ? 0.5f * powf(time * 2.0f, 3.0f) : 0.5f * powf(time * 2.0f - 2.0f, 3.0f) + 1.0f;
- }
- - (void)step
- {
- NSTimeInterval currentTime = CFAbsoluteTimeGetCurrent();
- double delta = _lastTime - currentTime;
- _lastTime = currentTime;
- if (_scrolling)
- {
- NSTimeInterval time = fminf(1.0f, (currentTime - _startTime) / _scrollDuration);
- delta = [self easeInOut:time];
- _scrollOffset = [self clampedOffset:_startOffset + (_endOffset - _startOffset) * delta];
- if (_vertical)
- {
- [self setContentOffsetWithoutEvent:CGPointMake(0.0f, _scrollOffset * _itemSize.height)];
- }
- else
- {
- [self setContentOffsetWithoutEvent:CGPointMake(_scrollOffset * _itemSize.width, 0.0f)];
- }
- [self didScroll];
- if (time == 1.0f)
- {
- _scrolling = NO;
- [self didScroll];
- [_delegate swipeViewDidEndScrollingAnimation:self];
- }
- }
- else if (_autoscroll)
- {
- if (!_scrollView.dragging)
- self.scrollOffset = [self clampedOffset:_scrollOffset + delta * _autoscroll];
- }
- else
- {
- [self stopAnimation];
- }
- }
- - (void)startAnimation
- {
- if (!_timer)
- {
- self.timer = [NSTimer timerWithTimeInterval:1.0 / 60.0
- target:self
- selector:@selector(step)
- userInfo:nil
- repeats:YES];
- [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
- [[NSRunLoop mainRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
- }
- }
- - (void)stopAnimation
- {
- [_timer invalidate];
- self.timer = nil;
- }
- - (NSInteger)clampedIndex:(NSInteger)index
- {
- if (_wrapEnabled)
- {
- return _numberOfItems ? (index - floorf((CGFloat) index / (CGFloat) _numberOfItems) * _numberOfItems) : 0;
- }
- else
- {
- return MIN(MAX(0, index), MAX(0, _numberOfItems - 1));
- }
- }
- - (CGFloat)clampedOffset:(CGFloat)offset
- {
- CGFloat returnValue = 0;
- if (_wrapEnabled)
- {
- returnValue = _numberOfItems ? (offset - floorf(offset / (CGFloat) _numberOfItems) * _numberOfItems) : 0.0f;
- }
- else
- {
- returnValue = fminf(fmaxf(0.0f, offset), fmaxf(0.0f, (CGFloat) _numberOfItems - 1.0f));
- }
- return returnValue;
- }
- - (void)setContentOffsetWithoutEvent:(CGPoint)contentOffset
- {
- if (!CGPointEqualToPoint(_scrollView.contentOffset, contentOffset))
- {
- BOOL animationEnabled = [UIView areAnimationsEnabled];
- if (animationEnabled)
- [UIView setAnimationsEnabled:NO];
- _suppressScrollEvent = YES;
- _scrollView.contentOffset = contentOffset;
- _suppressScrollEvent = NO;
- if (animationEnabled)
- [UIView setAnimationsEnabled:YES];
- }
- }
- - (NSInteger)currentPage
- {
- if (_itemsPerPage > 1 && _truncateFinalPage && !_wrapEnabled &&
- _currentItemIndex > (_numberOfItems / _itemsPerPage - 1) * _itemsPerPage)
- {
- return self.numberOfPages - 1;
- }
- return roundf((float) _currentItemIndex / (float) _itemsPerPage);
- }
- - (NSInteger)numberOfPages
- {
- return ceilf((float) self.numberOfItems / (float) _itemsPerPage);
- }
- - (NSInteger)minScrollDistanceFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex
- {
- NSInteger directDistance = toIndex - fromIndex;
- if (_wrapEnabled)
- {
- NSInteger wrappedDistance = MIN(toIndex, fromIndex) + _numberOfItems - MAX(toIndex, fromIndex);
- if (fromIndex < toIndex)
- {
- wrappedDistance = -wrappedDistance;
- }
- return (ABS(directDistance) <= ABS(wrappedDistance)) ? directDistance : wrappedDistance;
- }
- return directDistance;
- }
- - (CGFloat)minScrollDistanceFromOffset:(CGFloat)fromOffset toOffset:(CGFloat)toOffset
- {
- CGFloat directDistance = toOffset - fromOffset;
- if (_wrapEnabled)
- {
- CGFloat wrappedDistance = fminf(toOffset, fromOffset) + _numberOfItems - fmaxf(toOffset, fromOffset);
- if (fromOffset < toOffset)
- {
- wrappedDistance = -wrappedDistance;
- }
- return (fabs(directDistance) <= fabs(wrappedDistance)) ? directDistance : wrappedDistance;
- }
- return directDistance;
- }
- - (void)setCurrentItemIndex:(NSInteger)currentItemIndex
- {
- _currentItemIndex = currentItemIndex;
- self.scrollOffset = currentItemIndex;
- }
- - (void)setCurrentPage:(NSInteger)currentPage
- {
- if (currentPage * _itemsPerPage != _currentItemIndex)
- {
- [self scrollToPage:currentPage duration:0.0];
- }
- }
- - (void)setScrollOffset:(CGFloat)scrollOffset
- {
- if (fabs(_scrollOffset - scrollOffset) > 0.0001f)
- {
- _scrollOffset = scrollOffset;
- _lastUpdateOffset = _scrollOffset - 1.0f; //force refresh
- _scrolling = NO; //stop scrolling
- [self updateItemSizeAndCount];
- [self updateScrollViewDimensions];
- [self updateLayout];
- CGPoint contentOffset = _vertical ? CGPointMake(0.0f, [self clampedOffset:scrollOffset] * _itemSize.height) : CGPointMake([self clampedOffset:scrollOffset] * _itemSize.width, 0.0f);
- [self setContentOffsetWithoutEvent:contentOffset];
- [self didScroll];
- }
- }
- - (void)scrollByOffset:(CGFloat)offset duration:(NSTimeInterval)duration
- {
- if (duration > 0.0)
- {
- _scrolling = YES;
- _startTime = [[NSDate date] timeIntervalSinceReferenceDate];
- _startOffset = _scrollOffset;
- _scrollDuration = duration;
- _endOffset = _startOffset + offset;
- if (!_wrapEnabled)
- {
- _endOffset = [self clampedOffset:_endOffset];
- }
- [self startAnimation];
- }
- else
- {
- self.scrollOffset += offset;
- }
- }
- - (void)scrollToOffset:(CGFloat)offset duration:(NSTimeInterval)duration
- {
- [self scrollByOffset:[self minScrollDistanceFromOffset:_scrollOffset toOffset:offset] duration:duration];
- }
- - (void)scrollByNumberOfItems:(NSInteger)itemCount duration:(NSTimeInterval)duration
- {
- if (duration > 0.0)
- {
- CGFloat offset = 0.0f;
- if (itemCount > 0)
- {
- offset = (floorf(_scrollOffset) + itemCount) - _scrollOffset;
- }
- else if (itemCount < 0)
- {
- offset = (ceilf(_scrollOffset) + itemCount) - _scrollOffset;
- }
- else
- {
- offset = roundf(_scrollOffset) - _scrollOffset;
- }
- [self scrollByOffset:offset duration:duration];
- }
- else
- {
- self.scrollOffset = [self clampedIndex:_previousItemIndex + itemCount];
- }
- }
- - (void)scrollToItemAtIndex:(NSInteger)index duration:(NSTimeInterval)duration
- {
- [self scrollToOffset:index duration:duration];
- }
- - (void)scrollToPage:(NSInteger)page duration:(NSTimeInterval)duration
- {
- NSInteger index = page * _itemsPerPage;
- if (_truncateFinalPage)
- {
- index = MIN(index, _numberOfItems - _itemsPerPage);
- }
- [self scrollToItemAtIndex:index duration:duration];
- }
- #pragma mark -
- #pragma mark View loading
- - (UIView *)loadViewAtIndex:(NSInteger)index
- {
- UIView *view = [_dataSource swipeView:self viewForItemAtIndex:index reusingView:[self dequeueItemView]];
- if (view == nil)
- {
- view = [[UIView alloc] init];
- }
- UIView *oldView = [self itemViewAtIndex:index];
- if (oldView)
- {
- [self queueItemView:oldView];
- [oldView removeFromSuperview];
- }
- [self setItemView:view forIndex:index];
- [self setFrameForView:view atIndex:index];
- view.userInteractionEnabled = YES;
- [_scrollView addSubview:view];
- return view;
- }
- - (void)updateItemSizeAndCount
- {
- //get number of items
- _numberOfItems = [_dataSource numberOfItemsInFWSwipeView:self];
- //get item size
- CGSize size = [_delegate swipeViewItemSize:self];
- if (!CGSizeEqualToSize(size, CGSizeZero))
- {
- _itemSize = size;
- }
- else if (_numberOfItems > 0)
- {
- UIView *view = [[self visibleItemViews] lastObject] ?: [_dataSource swipeView:self viewForItemAtIndex:0 reusingView:[self dequeueItemView]];
- _itemSize = view.frame.size;
- }
- //prevent crashes
- if (_itemSize.width < 0.0001)
- _itemSize.width = 1;
- if (_itemSize.height < 0.0001)
- _itemSize.height = 1;
- }
- - (void)loadUnloadViews
- {
- //check that item size is known
- CGFloat itemWidth = _vertical ? _itemSize.height : _itemSize.width;
- if (itemWidth)
- {
- //calculate offset and bounds
- CGFloat width = _vertical ? self.bounds.size.height : self.bounds.size.width;
- CGFloat x = _vertical ? _scrollView.frame.origin.y : _scrollView.frame.origin.x;
- //calculate range
- CGFloat startOffset = [self clampedOffset:_scrollOffset - x / itemWidth];
- NSInteger startIndex = floorf(startOffset);
- NSInteger numberOfVisibleItems = ceilf(width / itemWidth + (startOffset - startIndex));
- if (_defersItemViewLoading)
- {
- startIndex = _currentItemIndex - ceilf(x / itemWidth) - 1;
- numberOfVisibleItems = ceilf(width / itemWidth) + 3;
- }
- //create indices
- numberOfVisibleItems = MIN(numberOfVisibleItems, _numberOfItems);
- NSMutableSet *visibleIndices = [NSMutableSet setWithCapacity:numberOfVisibleItems];
- for (NSInteger i = 0; i < numberOfVisibleItems; i++)
- {
- NSInteger index = [self clampedIndex:i + startIndex];
- [visibleIndices addObject:@(index)];
- }
- //remove offscreen views
- for (NSNumber *number in [_itemViews allKeys])
- {
- if (![visibleIndices containsObject:number])
- {
- UIView *view = _itemViews[number];
- [self queueItemView:view];
- [view removeFromSuperview];
- [_itemViews removeObjectForKey:number];
- }
- }
- //add onscreen views
- for (NSNumber *number in visibleIndices)
- {
- UIView *view = _itemViews[number];
- if (view == nil)
- {
- [self loadViewAtIndex:[number integerValue]];
- }
- }
- }
- }
- - (void)reloadItemAtIndex:(NSInteger)index
- {
- //if view is visible
- if ([self itemViewAtIndex:index])
- {
- //reload view
- [self loadViewAtIndex:index];
- }
- }
- - (void)reloadData
- {
- //remove old views
- for (UIView *view in self.visibleItemViews)
- {
- [view removeFromSuperview];
- }
- //reset view pools
- self.itemViews = [NSMutableDictionary dictionary];
- self.itemViewPool = [NSMutableSet set];
- //get number of items
- [self updateItemSizeAndCount];
- //layout views
- [self setNeedsLayout];
- //fix scroll offset
- if (_numberOfItems > 0 && _scrollOffset < 0.0f)
- {
- self.scrollOffset = 0;
- }
- }
- - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
- {
- UIView *view = [super hitTest:point withEvent:event];
- if ([view isEqual:self])
- {
- for (UIView *subview in _scrollView.subviews)
- {
- CGPoint offset = CGPointMake(point.x - _scrollView.frame.origin.x + _scrollView.contentOffset.x - subview.frame.origin.x,
- point.y - _scrollView.frame.origin.y + _scrollView.contentOffset.y - subview.frame.origin.y);
- if ((view = [subview hitTest:offset withEvent:event]))
- {
- return view;
- }
- }
- return _scrollView;
- }
- return view;
- }
- - (void)didMoveToSuperview
- {
- if (self.superview)
- {
- [self setNeedsLayout];
- if (_scrolling)
- {
- [self startAnimation];
- }
- }
- else
- {
- [self stopAnimation];
- }
- }
- #pragma mark -
- #pragma mark Gestures and taps
- - (NSInteger)viewOrSuperviewIndex:(UIView *)view
- {
- if (view == nil || view == _scrollView)
- {
- return NSNotFound;
- }
- NSInteger index = [self indexOfItemView:view];
- if (index == NSNotFound)
- {
- return [self viewOrSuperviewIndex:view.superview];
- }
- return index;
- }
- - (BOOL)viewOrSuperviewHandlesTouches:(UIView *)view
- {
- //thanks to @mattjgalloway and @shaps for idea
- //https://gist.github.com/mattjgalloway/6279363
- //https://gist.github.com/shaps80/6279008
- Class class = [view class];
- while (class && class != [UIView class])
- {
- unsigned int numberOfMethods;
- Method *methods = class_copyMethodList(class, &numberOfMethods);
- for (unsigned int i = 0; i < numberOfMethods; i++)
- {
- if (method_getName(methods[i]) == @selector(touchesBegan:withEvent:))
- {
- free(methods);
- return YES;
- }
- }
- if (methods)
- free(methods);
- class = [class superclass];
- }
- if (view.superview && view.superview != _scrollView)
- {
- return [self viewOrSuperviewHandlesTouches:view.superview];
- }
- return NO;
- }
- - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldReceiveTouch:(UITouch *)touch
- {
- if ([gesture isKindOfClass:[UITapGestureRecognizer class]])
- {
- //handle tap
- NSInteger index = [self viewOrSuperviewIndex:touch.view];
- if (index != NSNotFound)
- {
- if ((_delegate && ![_delegate swipeView:self shouldSelectItemAtIndex:index]) ||
- [self viewOrSuperviewHandlesTouches:touch.view])
- {
- return NO;
- }
- else
- {
- return YES;
- }
- }
- }
- return NO;
- }
- - (void)didTap:(UITapGestureRecognizer *)tapGesture
- {
- CGPoint point = [tapGesture locationInView:_scrollView];
- NSInteger index = _vertical ? (point.y / (_itemSize.height)) : (point.x / (_itemSize.width));
- if (_wrapEnabled)
- {
- index = index % _numberOfItems;
- }
- if (index >= 0 && index < _numberOfItems)
- {
- [_delegate swipeView:self didSelectItemAtIndex:index];
- }
- }
- #pragma mark -
- #pragma mark UIScrollViewDelegate methods
- - (void)scrollViewDidScroll:(__unused UIScrollView *)scrollView
- {
- if (!_suppressScrollEvent)
- {
- //stop scrolling animation
- _scrolling = NO;
- //update scrollOffset
- CGFloat delta = _vertical ? (_scrollView.contentOffset.y - _previousContentOffset.y) : (_scrollView.contentOffset.x - _previousContentOffset.x);
- _previousContentOffset = _scrollView.contentOffset;
- _scrollOffset += delta / (_vertical ? _itemSize.height : _itemSize.width);
- //update view and call delegate
- [self didScroll];
- }
- else
- {
- _previousContentOffset = _scrollView.contentOffset;
- }
- }
- - (void)scrollViewWillBeginDragging:(__unused UIScrollView *)scrollView
- {
- [_delegate swipeViewWillBeginDragging:self];
- //force refresh
- _lastUpdateOffset = self.scrollOffset - 1.0f;
- [self didScroll];
- }
- - (void)scrollViewDidEndDragging:(__unused UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
- {
- if (!decelerate)
- {
- //force refresh
- _lastUpdateOffset = self.scrollOffset - 1.0f;
- [self didScroll];
- }
- [_delegate swipeViewDidEndDragging:self willDecelerate:decelerate];
- }
- - (void)scrollViewWillBeginDecelerating:(__unused UIScrollView *)scrollView
- {
- [_delegate swipeViewWillBeginDecelerating:self];
- }
- - (void)scrollViewDidEndDecelerating:(__unused UIScrollView *)scrollView
- {
- //prevent rounding errors from accumulating
- CGFloat integerOffset = roundf(_scrollOffset);
- if (fabs(_scrollOffset - integerOffset) < 0.01f)
- {
- _scrollOffset = integerOffset;
- }
- //force refresh
- _lastUpdateOffset = self.scrollOffset - 1.0f;
- [self didScroll];
- [_delegate swipeViewDidEndDecelerating:self];
- }
- @end
|