UICollectionView scrollToItem()滚动到上一个单元格无法正常工作

时间:2019-01-04 20:38:42

标签: ios swift cocoa-touch uicollectionview uikit

在过去的几周里,我一直在为此而烦恼,但我没有想法。

背景短

我使用自定义UICollectionView构建了自己的UICollectionViewFlowLayout框架,用于垂直卡片分页和创建卡片堆叠效果。

以下是其外观的gif:

framework gif example

问题

我正在尝试实现一项功能,该功能允许用户滚动到特定索引处的特定卡(例如使用scrollToItem功能)。

现在问题是由于某种原因,当尝试滚动回上一张卡片时,它无法正常工作。

下面是发生的情况的记录,我在上面附加了tapGestureRecognizer,当我点击聚焦(当前居中)的卡时,它应该滚动到该索引-1(因此上一个卡)。
出于某种原因,只有当底部卡片的frame.origin.y位于聚焦的卡片(当前居中的卡片)的frame.origin.maxY上方时,才这样做。

Problem demo gif

请注意,我要在卡片上点按两次以使其正常工作,因为对于某些情况,底部卡片的frame.origin.y值必须低于顶部卡片的frame.origin.maxY它起作用的原因。

到目前为止我已经尝试过的基本答案

collectionView.scrollToItem(at: convertIndexToIndexPath(for: index), at: .top, animated: animated)

if let frame = collectionView.layoutAttributesForItem(at: convertIndexToIndexPath(for: index))?.frame {
    collectionView.scrollRectToVisible(frame, animated: true)
}

一些重要的知识

我已经弄清楚是哪一行导致了问题,在UICollectionViewFlowLayout的子类(称为VerticalCardSwiperFlowLayout)中,有一个名为updateCellAttributes的函数,该函数修改了框架就能达到这种效果。该问题发生在以下行:

let finalY = max(cvMinY, cardMinY)

以下是有问题的功能,其中包括在代码注释顶部最详细的解释:

/**
 Updates the attributes.
 Here manipulate the zIndex of the cards here, calculate the positions and do the animations.

 Below we'll briefly explain how the effect of scrolling a card to the background instead of the top is achieved.
 Keep in mind that (x,y) coords in views start from the top left (x: 0,y: 0) and increase as you go down/to the right,
 so as you go down, the y-value increases, and as you go right, the x value increases.

 The two most important variables we use to achieve this effect are cvMinY and cardMinY.
 * cvMinY (A): The top position of the collectionView + inset. On the drawings below it's marked as "A".
 This position never changes (the value of the variable does, but the position is always at the top where "A" is marked).
 * cardMinY (B): The top position of each card. On the drawings below it's marked as "B". As the user scrolls a card,
 this position changes with the card position (as it's the top of the card).
 When the card is moving down, this will go up, when the card is moving up, this will go down.

 We then take the max(cvMinY, cardMinY) to get the highest value of those two and set that as the origin.y of the card.
 By doing this, we ensure that the origin.y of a card never goes below cvMinY, thus preventing cards from scrolling upwards.

 ```
 +---------+   +---------+
 |         |   |         |
 | +-A=B-+ |   |  +-A-+  | ---> The top line here is the previous card
 | |     | |   | +--B--+ |      that's visible when the user starts scrolling.
 | |     | |   | |     | |
 | |     | |   | |     | |  |  As the card moves down,
 | |     | |   | |     | |  v  cardMinY ("B") goes up.
 | +-----+ |   | |     | |
 |         |   | +-----+ |
 | +--B--+ |   | +--B--+ |
 | |     | |   | |     | |
 +-+-----+-+   +-+-----+-+
 ```

 - parameter attributes: The attributes we're updating.
 */
fileprivate func updateCellAttributes(_ attributes: UICollectionViewLayoutAttributes) {

    guard let collectionView = collectionView else { return }

    var cvMinY = collectionView.bounds.minY + collectionView.contentInset.top
    let cardMinY = attributes.frame.minY
    var origin = attributes.frame.origin
    let cardHeight = attributes.frame.height

    if cvMinY > cardMinY + cardHeight + minimumLineSpacing + collectionView.contentInset.top {
        cvMinY = 0
    }

    let finalY = max(cvMinY, cardMinY)

    let deltaY = (finalY - cardMinY) / cardHeight
    transformAttributes(attributes: attributes, deltaY: deltaY)

    // Set the attributes frame position to the values we calculated
    origin.x = collectionView.frame.width/2 - attributes.frame.width/2 - collectionView.contentInset.left
    origin.y = finalY
    attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
    attributes.zIndex = attributes.indexPath.row
}

// Creates and applies a CGAffineTransform to the attributes to recreate the effect of the card going to the background.
fileprivate func transformAttributes(attributes: UICollectionViewLayoutAttributes, deltaY: CGFloat) {

    let translationScale = CGFloat((attributes.zIndex + 1) * 10)

    if let itemTransform = firstItemTransform {
        let scale = 1 - deltaY * itemTransform

        var t = CGAffineTransform.identity

        t = t.scaledBy(x: scale, y: 1)
        if isPreviousCardVisible {
            t = t.translatedBy(x: 0, y: (deltaY * translationScale))
        }
        attributes.transform = t
    }
}

以下是调用该特定代码的函数:

internal override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

    let items = NSArray(array: super.layoutAttributesForElements(in: rect)!, copyItems: true)
    for object in items {
        if let attributes = object as? UICollectionViewLayoutAttributes {
            self.updateCellAttributes(attributes)
        }
    }
    return items as? [UICollectionViewLayoutAttributes]
}

如果您想看看自己的样子,可以在此处下载完整的项目: https://github.com/JoniVR/VerticalCardSwiper/archive/development.zip

(如果您通过github签出,请确保下载开发分支)

Github特定问题,其中包含一些讨论和可能的其他信息,here

感谢您的宝贵时间,对于此问题的任何帮助或信息将深表感谢!另外,如果您还有其他问题或缺少任何关键信息,请告诉我

修改1: 注意:怪异的效果仅在滚动到currentIndex - 1时发生,currentIndex + 1没有出现任何问题,我认为这可以解释为什么它在let finalY = max(cvMinY, cardMinY)上发生的原因,但我还没有弄清楚尚未找到合适的解决方案。

1 个答案:

答案 0 :(得分:0)

通过使用setContentOffset并手动计算偏移量,我设法使其工作了另一种方式。

guard index >= 0 && index < verticalCardSwiperView.numberOfItems(inSection: 0) else { return }

let y = CGFloat(index) * (flowLayout.cellHeight + flowLayout.minimumLineSpacing) - topInset
let point = CGPoint(x: verticalCardSwiperView.contentOffset.x, y: y)
verticalCardSwiperView.setContentOffset(point, animated: animated)

verticalCardSwiperView基本上只是UICollectionView的子类,我这样做是因为我想缩小用户可以访问的范围,因为这是一个特定的库,并且允许完全collectionView的访问会使事情变得混乱

如果您确实知道是什么导致了此问题,或者我如何更好地解决它,我仍然很乐意找出:)