BSLoopView 實現了常用的 banner 無限循環輪播圖,支持 2D、3D 樣式
效果圖
主要知識點:
- 無限輪播原理
- 自動輪播 timer 導致頁面無法釋放問題
- collectionView 自定義分頁大小及UICollectionViewFlowLayout其他問題
1、無限輪播原理
輪播原始數據為 NSArray *dataArr = @[1,2,3]
,構造新數據,添加三次數據源
[self.newDataArr removeAllObjects];
[self.newDataArr addObjectsFromArray:dataArr];
[self.newDataArr addObjectsFromArray:dataArr];
[self.newDataArr addObjectsFromArray:dataArr];
新數據為 self.NewDataArr = @[1,2,3,1,2,3,1,2,3]
新數據構造完成后,將collectionView的初始位置更改為中間的數據源起始位置,也就是第二個1的位置(下標為3)
self.currentPageIndex = dataArr.count;
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath
indexPathForRow:self.currentPageIndex inSection:0]
atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
這樣就確保了scrollVeiw的 兩邊都是有數據的,我們要做的就是利用setContentOffset
方法,讓 collectionView 始終展示中間的數據源即可
示例代碼
NSInteger newPageIndex = self.currentPageIndex;
if (self.currentPageIndex < self.dataArr.count) {
newPageIndex = self.dataArr.count + self.currentPageIndex;
}else if (self.currentPageIndex >= self.dataArr.count*2){
newPageIndex = self.currentPageIndex - self.dataArr.count;
}
計算了當前頁面的pageIndex后,通過 pageIndex 計算實際偏移量,然后 setContentOffset
※※※
為什么這里要用 setContentOffset ,而不用 scrollToItemAtIndexPath 呢,因為我在使用 scrollToItemAtIndexPath 發現如果 使用3d效果,就會出現UI錯亂的問題,使用3D效果+ scrollToItemAtIndexPath,打印出來的偏移量發現并不正確,具體原因沒找到
2、自動輪播 timer 導致頁面無法釋放問題
如果 調用 scheduledTimerWithTimeInterval 方法執行timer時 ,repeats:YES的話,timer就會強持self,導致self無法釋放。一般情況下我們只要在需要的時候,調用
[self.timer invalidate];
self.timer = nil;
就可以停止timer,然后釋放 self
但是很多時候,我們無法在確定的某個點去做 timer 的置空,所以我們需要使用其他方法,讓 timer 不在強持 self 。
使用NSObject 類提供的方法,實現消息轉發可以解決此問題
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
- (void)forwardInvocation:(NSInvocation *)invocation
具體做法:自定義一個 中間類,繼承NSObject,然后引入 weak 聲明需要發送消息的對象
中間類 TimerTarget 如下:
@interface TimerTarget : NSObject
@property (nonatomic ,weak) BSLooperView * target;
@end
#pragma mark -
@implementation TimerTarget
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
我們在初始化timer的時候 ,將self 作為 TimerTarget 的target,然后將TimerTarget 作為timer的target 即可
self.timerTarget = [[TimerTarget alloc]init];
self.timerTarget.target = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.duration
target:self.timerTarget selector:@selector(looperTime) userInfo:nil
repeats:YES];
3、collectionView 自定義分頁大小及UICollectionViewFlowLayout其他問題
自定義分頁
自定義 UICollectionViewFlowLayout,重寫 prepareLayout 方法 ,利用消息轉發,重新設置頁面間距,頁面坐標
/// collectionView 的 寬度 去掉 item 寬度 CGFloat contentInset = self.collectionView.width - self.itemSize.width; /// 減速模式 self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast; /// 算出內邊距 self.collectionView.contentInset = UIEdgeInsetsMake(0, contentInset*0.5, 0, contentInset*0.5); /// 設置每個頁面之間的間距 if ([self.collectionView respondsToSelector:NSSelectorFromString(@"_setInterpageSpacing:")]) { ((void(*)(id,SEL,CGSize))objc_msgSend)(self.collectionView,NSSelectorFromString(@"_setInterpageSpacing:"),CGSizeMake(-(contentInset-self.minimumLineSpacing), 0)); } /// 設置 page 的坐標(原理:正常坐標是 0.0,如果想讓左邊留下 30 的寬度,就需要 page 左移 30) /// 左移如果想要 item 的邊距均分,就需要 左移 contentInset*0.5 if ([self.collectionView respondsToSelector:NSSelectorFromString(@"_setPagingOrigin:")]) { ((void(*)(id,SEL,CGPoint))objc_msgSend)(self.collectionView,NSSelectorFromString(@"_setPagingOrigin:"),CGPointMake(-contentInset*0.5, 0)); }
- layoutAttributesForElementsInRect 方法
我們在重寫 layoutAttributesForElementsInRect 時NSArray *originArr = [super layoutAttributesForElementsInRect:rect];
不能直接修改originArr數組內容,需要將他copy后再返回
NSArray *originArr = [super layoutAttributesForElementsInRect:rect]; NSArray * array = [[NSArray alloc]initWithArray:originArr copyItems:YES]; for (UICollectionViewLayoutAttributes * attrs in array) { /// 修改 }
如果你的布局需要動態更新,則需要調用此方法
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{ return YES; }