2012-11-21 86 views
71

我在我的應用程序中有一個非常簡單的collectionView(只是一行方形縮略圖)。targetContentOffsetForProposedContentOffset:withScrollingVelocity without subclassing UICollectionViewFlowLayout

我想攔截滾動,以便偏移總是在左側留下完整的圖像。目前它滾動到任何地方,並會留下切斷的圖像。

不管怎樣,我知道我需要使用的功能

- (CGPoint)targetContentOffsetForProposedContentOffset:withScrollingVelocity 

這樣做,但我只是用一個標準UICollectionViewFlowLayout。我沒有繼承它。

有沒有任何方法可以攔截這個沒有繼承UICollectionViewFlowLayout

感謝

回答

82

OK,答案是否定的,有沒有辦法做到這一點沒有子UICollectionViewFlowLayout。

但是,對於將來閱讀此內容的人來說,對其進行子類化非常容易。

首先我設置子類調用MyCollectionViewFlowLayout然後在界面生成器中,我將集合視圖佈局更改爲自定義並選擇了我的流佈局子類。

因爲你這樣做是這樣,你不能指定項目的大小,等等......在IB所以在MyCollectionViewFlowLayout.m我有這個...

- (void)awakeFromNib 
{ 
    self.itemSize = CGSizeMake(75.0, 75.0); 
    self.minimumInteritemSpacing = 10.0; 
    self.minimumLineSpacing = 10.0; 
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal; 
    self.sectionInset = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); 
} 

這將設置所有大小對我和滾動方向。

則...

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity 
{ 
    CGFloat offsetAdjustment = MAXFLOAT; 
    CGFloat horizontalOffset = proposedContentOffset.x + 5; 

    CGRect targetRect = CGRectMake(proposedContentOffset.x, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height); 

    NSArray *array = [super layoutAttributesForElementsInRect:targetRect]; 

    for (UICollectionViewLayoutAttributes *layoutAttributes in array) { 
     CGFloat itemOffset = layoutAttributes.frame.origin.x; 
     if (ABS(itemOffset - horizontalOffset) < ABS(offsetAdjustment)) { 
      offsetAdjustment = itemOffset - horizontalOffset; 
     } 
    } 

    return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y); 
} 

這確保了滾動以5.0對左手邊緣餘量結束。

這就是我需要做的。我不需要在代碼中設置流佈局。

+0

感謝您的問題和答案。完全幫助我,開始喜歡UICollectionViews! – nicktones

+1

正確使用它確實很強大。您是否觀看過WWDC 2012的Collection View會話?他們真的值得一看。一些不可思議的東西。 – Fogmeister

+1

我有很棒的東西。您的解決方案效果很好,除了第一個和最後一個單元格,它們可以在屏幕上「粘貼」一半。任何想法如何解決?我有一個水平集合視圖,很像App Store中的應用程序列表。 – nicktones

3

Fogmeisters答案爲我工作,除非我滾動到行的末尾。我的單元格不適合整齊地放在屏幕上,因此它會滾動到最後並以跳動跳回,以便最後一個單元格總是與屏幕的右邊緣重疊。

爲了防止這種情況添加以下代碼行的targetcontentoffset方法

if(proposedContentOffset.x>self.collectionViewContentSize.width-320-self.sectionInset.right) 
    return proposedContentOffset; 
+0

我想320是你的收藏視圖寬度:) –

+0

有回頭看舊的代碼。我想那個幻數是。 – Ajaxharg

16

雖然this answer一直對我有很大的幫助的開始,有一個明顯的閃爍,當你在一個小的距離刷快。在設備上重現它要容易得多。

我發現這總是發生在collectionView.contentOffset.x - proposedContentOffset.xvelocity.x有不同的唱歌。

我的解決方案是確保proposedContentOffset大於contentOffset.x如果速度是正值,如果是負值,則更少。這是在C#中,但應該是相當簡單的翻譯到目標C:

public override PointF TargetContentOffset (PointF proposedContentOffset, PointF scrollingVelocity) 
{ 
    /* Determine closest edge */ 

    float offSetAdjustment = float.MaxValue; 
    float horizontalCenter = (float) (proposedContentOffset.X + (this.CollectionView.Bounds.Size.Width/2.0)); 

    RectangleF targetRect = new RectangleF (proposedContentOffset.X, 0.0f, this.CollectionView.Bounds.Size.Width, this.CollectionView.Bounds.Size.Height); 
    var array = base.LayoutAttributesForElementsInRect (targetRect); 

    foreach (var layoutAttributes in array) { 
     float itemHorizontalCenter = layoutAttributes.Center.X; 
     if (Math.Abs (itemHorizontalCenter - horizontalCenter) < Math.Abs (offSetAdjustment)) { 
      offSetAdjustment = itemHorizontalCenter - horizontalCenter; 
     } 
    } 

    float nextOffset = proposedContentOffset.X + offSetAdjustment; 

    /* 
    * ... unless we end up having positive speed 
    * while moving left or negative speed while moving right. 
    * This will cause flicker so we resort to finding next page 
    * in the direction of velocity and use it. 
    */ 

    do { 
     proposedContentOffset.X = nextOffset; 

     float deltaX = proposedContentOffset.X - CollectionView.ContentOffset.X; 
     float velX = scrollingVelocity.X; 

     // If their signs are same, or if either is zero, go ahead 
     if (Math.Sign (deltaX) * Math.Sign (velX) != -1) 
      break; 

     // Otherwise, look for the closest page in the right direction 
     nextOffset += Math.Sign (scrollingVelocity.X) * SnapStep; 
    } while (IsValidOffset (nextOffset)); 

    return proposedContentOffset; 
} 

bool IsValidOffset (float offset) 
{ 
    return (offset >= MinContentOffset && offset <= MaxContentOffset); 
} 

該代碼使用MinContentOffsetMaxContentOffsetSnapStep這應該是平凡的你來定義。在我的情況下,他們竟然是

float MinContentOffset { 
    get { return -CollectionView.ContentInset.Left; } 
} 

float MaxContentOffset { 
    get { return MinContentOffset + CollectionView.ContentSize.Width - ItemSize.Width; } 
} 

float SnapStep { 
    get { return ItemSize.Width + MinimumLineSpacing; } 
} 
+7

這工作得很好。我將它轉換爲Objective-C感興趣的人:https://gist.github.com/rkeniger/7687301 –

+0

只是,需要評論這個!這真是太棒了!工作得很好! –

4

我遇到了同時使用targetContentOffsetForProposedContentOffset是根據新的點,我回到最後一個單元格不調整的問題一個小問題。
我發現了CGPoint我回到了一個Y值更大然後允許,所以我用下面的代碼在我targetContentOffsetForProposedContentOffset執行結束:

// if the calculated y is bigger then the maximum possible y we adjust accordingly 
CGFloat contentHeight = self.collectionViewContentSize.height; 
CGFloat collectionViewHeight = self.collectionView.bounds.size.height; 
CGFloat maxY = contentHeight - collectionViewHeight; 
if (newY > maxY) 
{ 
    newY = maxY; 
} 

return CGPointMake(0, newY); 

只是爲了更清楚,這是我的完整佈局實現其只是模仿垂直分頁行爲:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity 
{ 
    return [self targetContentOffsetForProposedContentOffset:proposedContentOffset]; 
} 

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset 
{ 
    CGFloat heightOfPage = self.itemSize.height; 
    CGFloat heightOfSpacing = self.minimumLineSpacing; 

    CGFloat numOfPage = lround(proposedContentOffset.y/(heightOfPage + heightOfSpacing)); 
    CGFloat newY = numOfPage * (heightOfPage + heightOfSpacing); 

    // if the calculated y is bigger then the maximum possible y we adjust accordingly 
    CGFloat contentHeight = self.collectionViewContentSize.height; 
    CGFloat collectionViewHeight = self.collectionView.bounds.size.height; 
    CGFloat maxY = contentHeight - collectionViewHeight; 
    if (newY > maxY) 
    { 
     newY = maxY; 
    } 

    return CGPointMake(0, newY); 
} 

希望這會節約一些時間和頭痛

+1

同樣的問題,似乎集合視圖會忽略無效值而不是將它們舍入到邊界。 –

54

丹的解決方案是有缺陷的。它不能很好地處理用戶彈跳。用戶快速滑動並滾動的情況沒有那麼多,出現動畫故障。

我提出的替代方案實現了像以前提出的相同分頁,但處理用戶的頁面之間輕彈。

#pragma mark - Pagination 
- (CGFloat)pageWidth { 
    return self.itemSize.width + self.minimumLineSpacing; 
} 

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity 
{   
     CGFloat rawPageValue = self.collectionView.contentOffset.x/self.pageWidth; 
     CGFloat currentPage = (velocity.x > 0.0) ? floor(rawPageValue) : ceil(rawPageValue); 
     CGFloat nextPage = (velocity.x > 0.0) ? ceil(rawPageValue) : floor(rawPageValue); 

     BOOL pannedLessThanAPage = fabs(1 + currentPage - rawPageValue) > 0.5; 
     BOOL flicked = fabs(velocity.x) > [self flickVelocity]; 
     if (pannedLessThanAPage && flicked) { 
      proposedContentOffset.x = nextPage * self.pageWidth; 
     } else { 
      proposedContentOffset.x = round(rawPageValue) * self.pageWidth; 
     } 

     return proposedContentOffset; 
} 

- (CGFloat)flickVelocity { 
    return 0.3; 
} 
+0

謝謝!這工作像一個魅力。有點難以理解,但到達那裏。 –

+0

我遇到此錯誤:無法在'proposedContentOffset'中分配給'x'?使用swift?我如何分配給x值? – TomSawyer

+0

@TomSawyer參數默認爲'let'。嘗試在Swift中聲明函數(在var param之前使用var):override func targetContentOffsetForProposedContentOffset(var proposedContentOffset:CGPoint) - > CGPoint – DarthMike

19

長的測試後,我發現溶液卡扣與自定義單元格寬度爲中央(每個小區具有差異。寬度),其固定所述的閃爍。隨意改進腳本。

- (CGPoint) targetContentOffsetForProposedContentOffset: (CGPoint) proposedContentOffset withScrollingVelocity: (CGPoint)velocity 
{ 
    CGFloat offSetAdjustment = MAXFLOAT; 
    CGFloat horizontalCenter = (CGFloat) (proposedContentOffset.x + (self.collectionView.bounds.size.width/2.0)); 

    //setting fastPaging property to NO allows to stop at page on screen (I have pages lees, than self.collectionView.bounds.size.width) 
    CGRect targetRect = CGRectMake(self.fastPaging ? proposedContentOffset.x : self.collectionView.contentOffset.x, 
            0.0, 
            self.collectionView.bounds.size.width, 
            self.collectionView.bounds.size.height); 

    NSArray *attributes = [self layoutAttributesForElementsInRect:targetRect]; 
    NSPredicate *cellAttributesPredicate = [NSPredicate predicateWithBlock: ^BOOL(UICollectionViewLayoutAttributes * _Nonnull evaluatedObject, 
                      NSDictionary<NSString *,id> * _Nullable bindings) 
    { 
     return (evaluatedObject.representedElementCategory == UICollectionElementCategoryCell); 
    }];   

    NSArray *cellAttributes = [attributes filteredArrayUsingPredicate: cellAttributesPredicate]; 

    UICollectionViewLayoutAttributes *currentAttributes; 

    for (UICollectionViewLayoutAttributes *layoutAttributes in cellAttributes) 
    { 
     CGFloat itemHorizontalCenter = layoutAttributes.center.x; 
     if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offSetAdjustment)) 
     { 
      currentAttributes = layoutAttributes; 
      offSetAdjustment = itemHorizontalCenter - horizontalCenter; 
     } 
    } 

    CGFloat nextOffset   = proposedContentOffset.x + offSetAdjustment; 

    proposedContentOffset.x  = nextOffset; 
    CGFloat deltaX    = proposedContentOffset.x - self.collectionView.contentOffset.x; 
    CGFloat velX    = velocity.x; 

    // detection form gist.github.com/rkeniger/7687301 
    // based on http://stackoverflow.com/a/14291208/740949 
    if (fabs(deltaX) <= FLT_EPSILON || fabs(velX) <= FLT_EPSILON || (velX > 0.0 && deltaX > 0.0) || (velX < 0.0 && deltaX < 0.0)) 
    { 

    } 
    else if (velocity.x > 0.0) 
    { 
     // revert the array to get the cells from the right side, fixes not correct center on different size in some usecases 
     NSArray *revertedArray = [[array reverseObjectEnumerator] allObjects]; 

     BOOL found = YES; 
     float proposedX = 0.0; 

     for (UICollectionViewLayoutAttributes *layoutAttributes in revertedArray) 
     { 
      if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) 
      { 
       CGFloat itemHorizontalCenter = layoutAttributes.center.x; 
       if (itemHorizontalCenter > proposedContentOffset.x) { 
        found = YES; 
        proposedX = nextOffset + (currentAttributes.frame.size.width/2) + (layoutAttributes.frame.size.width/2); 
       } else { 
        break; 
       } 
      } 
     } 

     // dont set on unfound element 
     if (found) { 
      proposedContentOffset.x = proposedX; 
     } 
    } 
    else if (velocity.x < 0.0) 
    { 
     for (UICollectionViewLayoutAttributes *layoutAttributes in cellAttributes) 
     { 
      CGFloat itemHorizontalCenter = layoutAttributes.center.x; 
      if (itemHorizontalCenter > proposedContentOffset.x) 
      { 
       proposedContentOffset.x = nextOffset - ((currentAttributes.frame.size.width/2) + (layoutAttributes.frame.size.width/2)); 
       break; 
      } 
     } 
    } 

    proposedContentOffset.y = 0.0; 

    return proposedContentOffset; 
} 
+9

最好的解決方案,謝謝!此外,對於未來的讀者,您必須關閉分頁才能使其工作。 – sridvijay

+0

如果有人想從左邊對齊它,而不是在中心對齊的單元格,我們將如何去改變它? – CyberMew

+0

不知道我是否理解正確,但如果您想在中心啓動項目,並將其與中心對齊,則需要更改contentInset。我使用這個:https://gist.github.com/pionl/432fc8059dee3b540e38 – Pion

4

我更喜歡允許用戶在多個頁面上滑動。因此,這裏是我的版本的targetContentOffsetForProposedContentOffset(這基於DarthMike答案)爲垂直佈局。

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { 
    CGFloat approximatePage = self.collectionView.contentOffset.y/self.pageHeight; 
    CGFloat currentPage = (velocity.y < 0.0) ? floor(approximatePage) : ceil(approximatePage); 

    NSInteger flickedPages = ceil(velocity.y/self.flickVelocity); 

    if (flickedPages) { 
     proposedContentOffset.y = (currentPage + flickedPages) * self.pageHeight; 
    } else { 
     proposedContentOffset.y = currentPage * self.pageHeight; 
    } 

    return proposedContentOffset; 
} 

- (CGFloat)pageHeight { 
    return self.itemSize.height + self.minimumLineSpacing; 
} 

- (CGFloat)flickVelocity { 
    return 1.2; 
} 
14

this answer by Dan Abramov這裏的SWIFT版本

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 
    var _proposedContentOffset = CGPoint(x: proposedContentOffset.x, y: proposedContentOffset.y) 
    var offSetAdjustment: CGFloat = CGFloat.max 
    let horizontalCenter: CGFloat = CGFloat(proposedContentOffset.x + (self.collectionView!.bounds.size.width/2.0)) 

    let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width: self.collectionView!.bounds.size.width, height: self.collectionView!.bounds.size.height) 

    let array: [UICollectionViewLayoutAttributes] = self.layoutAttributesForElementsInRect(targetRect)! as [UICollectionViewLayoutAttributes] 
    for layoutAttributes: UICollectionViewLayoutAttributes in array { 
     if (layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell) { 
      let itemHorizontalCenter: CGFloat = layoutAttributes.center.x 
      if (abs(itemHorizontalCenter - horizontalCenter) < abs(offSetAdjustment)) { 
       offSetAdjustment = itemHorizontalCenter - horizontalCenter 
      } 
     } 
    } 

    var nextOffset: CGFloat = proposedContentOffset.x + offSetAdjustment 

    repeat { 
     _proposedContentOffset.x = nextOffset 
     let deltaX = proposedContentOffset.x - self.collectionView!.contentOffset.x 
     let velX = velocity.x 

     if (deltaX == 0.0 || velX == 0 || (velX > 0.0 && deltaX > 0.0) || (velX < 0.0 && deltaX < 0.0)) { 
      break 
     } 

     if (velocity.x > 0.0) { 
      nextOffset = nextOffset + self.snapStep() 
     } else if (velocity.x < 0.0) { 
      nextOffset = nextOffset - self.snapStep() 
     } 
    } while self.isValidOffset(nextOffset) 

    _proposedContentOffset.y = 0.0 

    return _proposedContentOffset 
} 

func isValidOffset(offset: CGFloat) -> Bool { 
    return (offset >= CGFloat(self.minContentOffset()) && offset <= CGFloat(self.maxContentOffset())) 
} 

func minContentOffset() -> CGFloat { 
    return -CGFloat(self.collectionView!.contentInset.left) 
} 

func maxContentOffset() -> CGFloat { 
    return CGFloat(self.minContentOffset() + self.collectionView!.contentSize.width - self.itemSize.width) 
} 

func snapStep() -> CGFloat { 
    return self.itemSize.width + self.minimumLineSpacing; 
} 

或要點這裏https://gist.github.com/katopz/8b04c783387f0c345cd9

+3

Swift 3的更新版本:https://gist.github.com/mstubna/beed10327e00310d05f12bf4747266a4 – mstubna

+1

Dang it @mstubna,我繼續並複製了上述內容,將其更新爲swift 3,開始製作更新的要點,回到這裏收集筆記/標題,在這一點上我注意到你已經做出了一個快速的要點。謝謝!太糟糕了,我錯過了它。 – VaporwareWolf

15

我只是想在這裏把接受的答案的斯威夫特版本。

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 
    var offsetAdjustment = CGFloat.greatestFiniteMagnitude 
    let horizontalOffset = proposedContentOffset.x 
    let targetRect = CGRect(origin: CGPoint(x: proposedContentOffset.x, y: 0), size: self.collectionView!.bounds.size) 

    for layoutAttributes in super.layoutAttributesForElements(in: targetRect)! { 
     let itemOffset = layoutAttributes.frame.origin.x 
     if (abs(itemOffset - horizontalOffset) < abs(offsetAdjustment)) { 
      offsetAdjustment = itemOffset - horizontalOffset 
     } 
    } 

    return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y) 
} 

有效期夫特3

+0

這個版本工作的很好,如果你交換代碼,它對Y軸也能很好地工作。 – Chris

+0

這裏的主要作品很棒。但是如果我停止滾動,並且仔細擡起我的手指,它將不會滾動到任何頁面並停在那裏。 –

+0

@ChristianA.Strømmen奇怪,它適用於我的應用程序。 –

2

這裏是我的水平滾動的集合視圖迅速解決。它很簡單,甜美,避免任何閃爍。

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 
    guard let collectionView = collectionView else { return proposedContentOffset } 

    let currentXOffset = collectionView.contentOffset.x 
    let nextXOffset = proposedContentOffset.x 
    let maxIndex = ceil(currentXOffset/pageWidth()) 
    let minIndex = floor(currentXOffset/pageWidth()) 

    var index: CGFloat = 0 

    if nextXOffset > currentXOffset { 
     index = maxIndex 
    } else { 
     index = minIndex 
    } 

    let xOffset = pageWidth() * index 
    let point = CGPointMake(xOffset, 0) 

    return point 
    } 

    func pageWidth() -> CGFloat { 
    return itemSize.width + minimumInteritemSpacing 
    } 
+0

什麼是'itemSize'? –

+0

它是收集單元的大小。這些函數在UICollectionViewFlowLayout子類化時使用。 – DJSK

+0

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewFlowLayout_class/index.html#//apple_ref/occ/cl/UICollectionViewFlowLayout – DJSK

-1

對於那些希望在斯威夫特的解決方案:

class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout { 
    private let collectionViewHeight: CGFloat = 200.0 
    private let screenWidth: CGFloat = UIScreen.mainScreen().bounds.width 

    override func awakeFromNib() { 
     super.awakeFromNib() 

     self.itemSize = CGSize(width: [InsertItemWidthHere], height: [InsertItemHeightHere]) 
     self.minimumInteritemSpacing = [InsertItemSpacingHere] 
     self.scrollDirection = .Horizontal 
     let inset = (self.screenWidth - CGFloat(self.itemSize.width))/2 
     self.collectionView?.contentInset = UIEdgeInsets(top: 0, 
                 left: inset, 
                 bottom: 0, 
                 right: inset) 
    } 

    override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 
     var offsetAdjustment = CGFloat.max 
     let horizontalOffset = proposedContentOffset.x + ((self.screenWidth - self.itemSize.width)/2) 

     let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: self.screenWidth, height: self.collectionViewHeight) 
     var array = super.layoutAttributesForElementsInRect(targetRect) 

     for layoutAttributes in array! { 
      let itemOffset = layoutAttributes.frame.origin.x 
      if (abs(itemOffset - horizontalOffset) < abs(offsetAdjustment)) { 
       offsetAdjustment = itemOffset - horizontalOffset 
      } 
     } 

     return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y) 
    } 
} 
1

@安德烈·阿布雷烏的代碼

Swift3版本

class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout { 
    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 
     var offsetAdjustment = CGFloat.greatestFiniteMagnitude 
     let horizontalOffset = proposedContentOffset.x 
     let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: self.collectionView!.bounds.size.width, height: self.collectionView!.bounds.size.height) 
     for layoutAttributes in super.layoutAttributesForElements(in: targetRect)! { 
      let itemOffset = layoutAttributes.frame.origin.x 
      if abs(itemOffset - horizontalOffset) < abs(offsetAdjustment){ 
       offsetAdjustment = itemOffset - horizontalOffset 
      } 
     } 
     return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y) 
    } 
} 
+0

謝謝! 預期的最佳狀態 謝謝你的幫助! –

+0

你可以在@Andre中編輯自己。 – iDevAmit

1

斯威夫特4

與一個尺寸(水平滾動)的細胞收集視圖最簡單的辦法:

override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 
    guard let collectionView = collectionView else { return proposedContentOffset } 

    // Calculate width of your page 
    let pageWidth = calculatedPageWidth() 

    // Calculate proposed page 
    let proposedPage = round(proposedContentOffset.x/pageWidth) 

    // Adjust necessary offset 
    let xOffset = pageWidth * proposedPage - collectionView.contentInset.left 

    return CGPoint(x: xOffset, y: 0) 
} 

func calculatedPageWidth() -> CGFloat { 
    return itemSize.width + minimumInteritemSpacing 
} 
1

較短的解決方案(假設你緩存佈局屬性):

override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 
    let proposedEndFrame = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView!.bounds.width, height: collectionView!.bounds.height) 
    let targetLayoutAttributes = cache.max { $0.frame.intersection(proposedEndFrame).width < $1.frame.intersection(proposedEndFrame).width }! 
    return CGPoint(x: targetLayoutAttributes.frame.minX - horizontalPadding, y: 0) 
} 

要使這在上下文中:

class Layout : UICollectionViewLayout { 
    private var cache: [UICollectionViewLayoutAttributes] = [] 
    private static let horizontalPadding: CGFloat = 16 
    private static let interItemSpacing: CGFloat = 8 

    override func prepare() { 
     let (itemWidth, itemHeight) = (collectionView!.bounds.width - 2 * Layout.horizontalPadding, collectionView!.bounds.height) 
     cache.removeAll() 
     let count = collectionView!.numberOfItems(inSection: 0) 
     var x: CGFloat = Layout.horizontalPadding 
     for item in (0..<count) { 
      let indexPath = IndexPath(item: item, section: 0) 
      let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) 
      attributes.frame = CGRect(x: x, y: 0, width: itemWidth, height: itemHeight) 
      cache.append(attributes) 
      x += itemWidth + Layout.interItemSpacing 
     } 
    } 

    override var collectionViewContentSize: CGSize { 
     let width: CGFloat 
     if let maxX = cache.last?.frame.maxX { 
      width = maxX + Layout.horizontalPadding 
     } else { 
      width = collectionView!.width 
     } 
     return CGSize(width: width, height: collectionView!.height) 
    } 

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 
     return cache.first { $0.indexPath == indexPath } 
    } 

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 
     return cache.filter { $0.frame.intersects(rect) } 
    } 

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 
     let proposedEndFrame = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView!.bounds.width, height: collectionView!.bounds.height) 
     let targetLayoutAttributes = cache.max { $0.frame.intersection(proposedEndFrame).width < $1.frame.intersection(proposedEndFrame).width }! 
     return CGPoint(x: targetLayoutAttributes.frame.minX - Layout.horizontalPadding, y: 0) 
    } 
}