UIScrollView的delegate方法妙用之讓UICollectionView滑動到某個你想要的位置

一個UICollectionView有好多個cell,滑動一下,誰也不知道會停留在哪一個cell,滑的快一點,就會多滑一段距離,反之則會滑的比較近,這正是UIScrollview用戶體驗好的地方。
若是想要UICollectionView停留到某個cell的位置,能夠用
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated;
這個方法,還能用scrollPosition這個參數控制cell具體停留在上下左右中到底哪一個位置。
那麼問題來了:若是我只是隨便滑了一下,我也不知道它會停在位於哪一個indexPath的cell上,但無論它停在哪一個cell上,我都但願這個cell恰好在屏幕中間,應該怎麼辦呢?(這個場景在coverFlow的效果裏比較常見)
 
以前知道的作法是:
scrollViewDidEndDecelerating或其餘delegate方法裏,經過當前 contentOffset 計算最近的整數頁及其對應的 contentOffset,而後經過動畫移動到這個位置。
可是這個作法有問題,就是動畫不連貫,徹底沒有「恰好停到那裏」的感受。
 
今天在想有沒有其餘更好的辦法時,忽然發現一個以前歷來沒用功的方法:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset NS_AVAILABLE_IOS(5_0);
一看這參數名,再看看這文檔,真是讓人喜不自禁吶!這不就是讓scrollView「恰好停到某個位置」的方法嘛!!!(系統5.0就提供了,如今纔看到。。。。。。)
targetContentOffset 是個指針,能夠修改這個參數的值,讓scrollView最終中止在目標位置。
(2016年12月7日更新)
注意:scrollView的pagingEnable屬性必須爲NO時這個方法纔會被調用。
感謝@  ZeroOnet 評論中指出,這個方法在pagingEnable==YES的時候也會調用;
可是pagingEnable的效果會覆蓋這個方法的效果,達不到咱們想要的「恰好停到指定位置的效果」,因此仍是須要注意將pagingEnable設置爲NO!
 
例:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    CGPoint originalTargetContentOffset = CGPointMake(targetContentOffset->x, targetContentOffset->y);
    CGPoint targetCenter = CGPointMake(originalTargetContentOffset.x + CGRectGetWidth(self.collectionView.bounds)/2, CGRectGetHeight(self.collectionView.bounds) / 2);
    NSIndexPath *indexPath = nil;
    NSInteger i = 0;
    while (indexPath == nil) {
        targetCenter = CGPointMake(originalTargetContentOffset.x + CGRectGetWidth(self.collectionView.bounds)/2 + 10*i, CGRectGetHeight(self.collectionView.bounds) / 2);
        indexPath = [self.collectionView indexPathForItemAtPoint:targetCenter];
        i++;
    }
    self.selectedIndex = indexPath;
    //這裏用attributes比用cell要好不少,由於cell可能由於不在屏幕範圍內致使cellForItemAtIndexPath返回nil
    UICollectionViewLayoutAttributes *attributes = [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
    if (attributes) {
        *targetContentOffset = CGPointMake(attributes.center.x - CGRectGetWidth(self.collectionView.bounds)/2, originalTargetContentOffset.y);
    } else {
        DLog(@"center is %@; indexPath is {%@, %@}; cell is %@",NSStringFromCGPoint(targetCenter), @(indexPath.section), @(indexPath.item), attributes);
    }
   
}
 
這樣scrollView就會逐漸減速,最終中止在itemCenterOffsetWithOriginalTargetContentOffset方法算出來的位置上了,效果槓槓的~
 
原本覺得這個方法沒多少人知道,結果百度一搜,發現原來已經有大神寫過詳細的文章了( http://tech.glowing.com/cn/practice-in-uiscrollview/),這個就當記錄一下吧
 
另外發現一個直接用NSObject就實現相似效果的庫: https://github.com/nicklockwood/iCarousel   乍看之下沒看懂。。。等有空再仔細研究
 
更新(2015-06-19)
原來UICollectionViewLayout已經提供了兩個方法能夠實現這個功能:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset NS_AVAILABLE_IOS(7_0);
效果與上面的delegate方法徹底同樣,不過這個是UICollectionViewLayout的方法,須要在本身的layout子類裏重載。
好處是:這樣就不用再在viewController裏寫scrollView的delegate方法了,viewController更加簡潔;跟佈局相關的代碼都轉移到了layout的類中 
 
相關文章
相關標籤/搜索