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

Jon*_*iVR 7 cocoa-touch uikit ios uicollectionview swift

在过去的几个星期里,我一直在打破这个问题,而且我的想法很糟糕......

背景短

UICollectionView使用自定义UICollectionViewFlowLayout垂直卡分页和创建卡堆栈效果构建了自己的框架.

这是它的外观的gif:

框架gif示例

问题

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

现在的问题是,由于某种原因,当试图滚动回到前一张卡时,它无法正常工作.

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

问题演示gif

请注意,我在卡上点击两次以使其正常工作,因为frame.origin.y底卡的价值需要低于frame.origin.maxY顶卡的价值,因为它有效.

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

collectionView.scrollToItem(at: convertIndexToIndexPath(for: index), at: .top, animated: animated)
Run Code Online (Sandbox Code Playgroud)

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

一些重要的事情要知道

我已经找出导致问题的那一行,在UICollectionViewFlowLayout(被调用VerticalCardSwiperFlowLayout)的子类中,有一个被调用的函数updateCellAttributes,它修改了帧的一些属性以便能够实现这种效果.问题发生在以下行:

let finalY = max(cvMinY, cardMinY)
Run Code Online (Sandbox Code Playgroud)

这是有问题的功能,包括对代码注释中顶部如何工作的非常扩展的解释:

/**
 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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]
}
Run Code Online (Sandbox Code Playgroud)

如果你想看看自己,你可以在这里下载完整的项目: https ://github.com/JoniVR/VerticalCardSwiper/archive/development.zip

(如果您通过github结账,请务必下载开发分支)

具体Github上发布了一些讨论和可能额外的信息可以发现在这里.

感谢您的时间,任何有关此问题的帮助或信息都将深表感谢!另外,如果您有任何进一步的问题或缺少任何重要信息,请告诉我

编辑1: 注意:奇怪的效果只会在滚动时发生currentIndex - 1,currentIndex + 1不会产生任何问题,我认为这有点解释了为什么会发生这种情况let finalY = max(cvMinY, cardMinY),但我还没有找到适合它的解决方案.

Jon*_*iVR 2

我已经设法让它以另一种方式工作,通过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)
Run Code Online (Sandbox Code Playgroud)

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

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

  • 你是一位传奇人物,也是一位学者。 (2认同)