【问题标题】:UICollectionview custom layout: some indexes have more visible cells than others?UICollectionview 自定义布局:某些索引比其他索引具有更多可见单元格?
【发布时间】:2019-03-15 07:36:13
【问题描述】:

我遇到了一个奇怪的问题,我似乎无法弄清楚或在网上找到任何相关信息。

因此,我尝试使用 UICollectionView 和自定义 UICollectionViewFlowlayout 复制 Shazam 发现 UI。

到目前为止,一切都运行良好,但是在添加“卡片堆叠”效果时,我(或者更确切地说是实现它的人)注意到在某些情况下(或者更确切地说,当特定索引可见时,在示例中为第 5、9 行)将有 4 个可见单元格而不是 3 个。我的猜测是这与单元格重用有关,但我不确定为什么它正在这样做。我查看了各个单元格的尺寸,它们似乎都相同,所以并不是单元格的大小不同。

有人知道为什么会发生这种情况吗?非常感谢任何帮助或建议。

我将在下面添加自定义流程布局和屏幕截图的代码 sn-p。 您可以download the full project here,或者查看the PR on Github

这是一个视觉比较:

自定义flowlayout源码:

import UIKit

/// Custom `UICollectionViewFlowLayout` that provides the flowlayout information like paging and `CardCell` movements.
internal class VerticalCardSwiperFlowLayout: UICollectionViewFlowLayout {

    /// This property sets the amount of scaling for the first item.
    internal var firstItemTransform: CGFloat?
    /// This property enables paging per card. The default value is true.
    internal var isPagingEnabled: Bool = true
    /// Stores the height of a CardCell.
    internal var cellHeight: CGFloat!

    internal override func prepare() {
        super.prepare()

        assert(collectionView!.numberOfSections == 1, "Number of sections should always be 1.")
        assert(collectionView!.isPagingEnabled == false, "Paging on the collectionview itself should never be enabled. To enable cell paging, use the isPagingEnabled property of the VerticalCardSwiperFlowLayout instead.")
    }

    internal override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let items = NSMutableArray (array: super.layoutAttributesForElements(in: rect)!, copyItems: true)

        items.enumerateObjects(using: { (object, index, stop) -> Void in
            let attributes = object as! UICollectionViewLayoutAttributes

            self.updateCellAttributes(attributes)
        })

        return items as? [UICollectionViewLayoutAttributes]
    }

    // We invalidate the layout when a "bounds change" happens, for example when we scale the top cell. This forces a layout update on the flowlayout.
    internal override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }

    // Cell paging
    internal override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

        // If the property `isPagingEnabled` is set to false, we don't enable paging and thus return the current contentoffset.
        guard isPagingEnabled else {
            let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
            return latestOffset
        }

        // Page height used for estimating and calculating paging.
        let pageHeight = cellHeight + self.minimumLineSpacing

        // Make an estimation of the current page position.
        let approximatePage = self.collectionView!.contentOffset.y/pageHeight

        // Determine the current page based on velocity.
        let currentPage = (velocity.y < 0.0) ? floor(approximatePage) : ceil(approximatePage)

        // Create custom flickVelocity.
        let flickVelocity = velocity.y * 0.4

        // Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
        let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)

        // Calculate newVerticalOffset.
        let newVerticalOffset = ((currentPage + flickedPages) * pageHeight) - self.collectionView!.contentInset.top

        return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
    }

    internal override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

        // make sure the zIndex of the next card is higher than the one we're swiping away.
        let nextIndexPath = IndexPath(row: itemIndexPath.row + 1, section: itemIndexPath.section)
        let nextAttr = self.layoutAttributesForItem(at: nextIndexPath)
        nextAttr?.zIndex = nextIndexPath.row

        // attributes for swiping card away
        let attr = self.layoutAttributesForItem(at: itemIndexPath)

        return attr
    }

    /**
     Updates the attributes.
     Here manipulate the zIndex of the cards here, calculate the positions and do the animations.
     - parameter attributes: The attributes we're updating.
     */
    fileprivate func updateCellAttributes(_ attributes: UICollectionViewLayoutAttributes) {
        let minY = collectionView!.bounds.minY + collectionView!.contentInset.top
        let maxY = attributes.frame.origin.y

        let finalY = max(minY, maxY)
        var origin = attributes.frame.origin
        let deltaY = (finalY - origin.y) / attributes.frame.height
        let translationScale = CGFloat((attributes.zIndex + 1) * 10)

        // create stacked effect (cards visible at bottom
        if let itemTransform = firstItemTransform {
            let scale = 1 - deltaY * itemTransform
            var t = CGAffineTransform.identity
            t = t.scaledBy(x: scale, y: 1)
            t = t.translatedBy(x: 0, y: (translationScale + deltaY * translationScale))

            attributes.transform = t
        }

        origin.x = (self.collectionView?.frame.width)! / 2 - attributes.frame.width / 2 - (self.collectionView?.contentInset.left)!
        origin.y = finalY
        attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
        attributes.zIndex = attributes.indexPath.row
    }
}

编辑 1: 作为一个额外的说明,最终的最终结果应该是这样的:

编辑 2: 从我的测试中,您每滚动 4-5 卡似乎就会发生这种情况。

【问题讨论】:

  • layoutAttributesForElements(in:)return 之前打印items。检查帧是否正确。类似:items.forEach({print("IndexPath:\($0.indexPath) frame: \($0.frame)}) 或类似的东西。检查框架,可能有一个没有正确更新。

标签: ios cocoa-touch uicollectionview uikit uicollectionviewflowlayout


【解决方案1】:

您有一个继承自流布局的布局。你覆盖了layoutAttributesForElements(in rect:),在那里你从super.layoutAttributesForElements获取所有元素,然后为每个元素修改updateCellAttributes方法中的属性。

这通常是创建流布局的子类的好方法。 UICollectionViewFlowLayout 正在做大部分艰苦的工作 - 找出每个元素应该在哪里,哪些元素在 rect 中,它们的基本属性是什么,应该如何填充它们等等,你可以在之后修改一些属性“艰苦”的工作已经完成。当您添加旋转或不透明度或其他不会更改项目位置的功能时,这可以正常工作。

使用updateCellAttributes 更改项目框架时会遇到麻烦。然后,您可能会遇到这样的情况:对于常规流布局,您有一个根本不会出现在框架中的单元格,但由于您的修改,现在应该出现。因此 super.layoutAttributesForElements(in rect: CGRect) 根本没有返回该属性,因此它们根本不会出现。您还可能遇到相反的问题,即根本不应该在框架中的单元格在视图中,但以一种用户看不到的方式进行了转换。

您没有充分解释您正在尝试做什么以及为什么您认为从 UIFlowLayout 继承对我来说是正确的,以便能够专门帮助您。但我希望我已经给了你足够的信息,你可以自己找到问题。

【讨论】:

  • 嗨,首先,感谢您的回答,它提供了一些有用的见解。我会尝试考虑这一点。我试图达到的效果与 shazam discover 效果相同。一个卡片堆栈,滚动将卡片滑下堆栈,之前的卡片在底部可见,如下图所示:i.stack.imgur.com/89Rko.jpg
【解决方案2】:

错误在于您如何为每个属性定义frame.origin.y。更具体地说,您在minY 中持有的值决定了您在屏幕上保留了多少个单元格。 (我将编辑此答案并稍后解释,但现在,请尝试替换以下代码)

var minY = collectionView!.bounds.minY + collectionView!.contentInset.top
let maxY = attributes.frame.origin.y

if minY > attributes.frame.origin.y + attributes.bounds.height + minimumLineSpacing + collectionView!.contentInset.top {
   minY = 0
}

【讨论】:

  • 做到了,多么简单的解决方案,我非常渴望听到解释!非常感谢!
  • 你有机会给我你承诺的解释吗?我并没有完全了解它是如何出错的,它可以为我提供一些有用的见解。
  • @JoniVR 很抱歉我忘记了这一点。我希望能在周末找到一些时间并回复您
猜你喜欢
  • 2013-09-10
  • 2017-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-11
相关资源
最近更新 更多