1

When scrolling with my "springy flow layout" (meant to replicate the scrolling effect in Messages), the initial scroll works well, but repeated scrolling ends with all of the cells constantly bouncing all over the screen, and off the vertical axis.

I don't understand why the cells are moving off the vertical axis, since there is no movement applied in the horizontal axis. I'm simply applying this flow layout to my collection view which is currently only setup to create a bunch of dummy cells.

How do I prevent movement in the horizontal axis and how do I ensure that the cells always come to a rest eventually

class SpringyColumnLayout: UICollectionViewFlowLayout {
    private lazy var dynamicAnimator = UIDynamicAnimator(collectionViewLayout: self)

    override func prepare() {
        super.prepare()

        guard let cv = collectionView else { return }

        let availableWidth = cv.bounds.inset(by: cv.layoutMargins).width

        let minColumnWidth: CGFloat = 300
        let maxNumberOfColumns = Int(availableWidth / minColumnWidth)
        let cellWidth = (availableWidth / CGFloat(maxNumberOfColumns))
            .rounded(.down)

        self.itemSize = CGSize(width: cellWidth, height: 70)

        self.sectionInset = UIEdgeInsets(
            top: minimumInteritemSpacing,
            left: 0,
            bottom: 0,
            right: 0
        )

        self.sectionInsetReference = .fromSafeArea

        if dynamicAnimator.behaviors.isEmpty {
            let contentSize = collectionViewContentSize
            let contentBounds = CGRect(origin: .zero, size: contentSize)
            guard let items = super.layoutAttributesForElements(in: contentBounds)
                else { return }

            for item in items {
                let spring = UIAttachmentBehavior(
                    item: item,
                    attachedToAnchor: item.center
                )
                spring.length = 0
                spring.damping = 0.8
                spring.frequency = 1

                self.dynamicAnimator.addBehavior(spring)
            }
        }
    }

    override func layoutAttributesForElements(
        in rect: CGRect
    ) -> [UICollectionViewLayoutAttributes]? {
        return dynamicAnimator.items(in: rect) as? [UICollectionViewLayoutAttributes]
    }

    override func layoutAttributesForItem(
        at indexPath: IndexPath
    ) -> UICollectionViewLayoutAttributes? {
        return dynamicAnimator.layoutAttributesForCell(at: indexPath)
    }

    override func shouldInvalidateLayout(
        forBoundsChange newBounds: CGRect
    ) -> Bool {
        let scrollView = self.collectionView!

        let scrollDelta = newBounds.origin.y - scrollView.bounds.origin.y
        let touchLocation = scrollView.panGestureRecognizer
            .location(in: scrollView)

        for case let spring as UIAttachmentBehavior in dynamicAnimator.behaviors {
            let anchorPoint = spring.anchorPoint
            let yDistanceFromTouch = abs(touchLocation.y - anchorPoint.y)
            let xDistanceFromTouch = abs(touchLocation.x - anchorPoint.x)
            let scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / 1500

            let item = spring.items.first!
            var center = item.center
            if scrollDelta < 0 {
                center.y += max(scrollDelta, scrollDelta * scrollResistance)
            } else {
                center.y += min(scrollDelta, scrollDelta * scrollResistance)
            }
            item.center = center

            dynamicAnimator.updateItem(usingCurrentState: item)
        }

        return false
    }
}
jjatie
  • 5,152
  • 5
  • 34
  • 56
  • To restrict the movement so that it can only be along the vertical axis, use a _sliding_ attachment. – matt May 09 '19 at 14:45
  • I would think that simply changing the attachment behaviour to a `.slidingAttachment` (maintaining the spring properties) would fix this, but instead I lose all of the spring dynamics. – jjatie May 28 '19 at 10:48
  • By the way there is a WWDC video where Apple explains how they do the Messages bounce. – matt May 28 '19 at 14:30
  • That is where this code sample comes from. It seems that something changed with the dynamics system changed in iOS 9 as many other code samples (including from objc.io) experience the same problem. – jjatie May 28 '19 at 22:19

0 Answers0