18
votes

I have a horizontal UICollectionView with a custom UICollectionViewFlowLayout that has a UIAttachmentBehavior set on each cell to give it a bouncy feel when scrolling left and right. The behavior has the following properties:

attachmentBehavior.length = 1.0f;
attachmentBehavior.damping = 0.5f;
attachmentBehavior.frequency = 1.9f;

When a new cell is added to the collection view it's added at the bottom and then animated to its position also using a UIAttachmentBehavior. Naturally it bounces up and down a bit till it rests in its position. Everything is working as expected till now.

enter image description here

The problem I have starts appearing when the collection view is scrolled left or right before the newly added cell has come to rest. The adds left and right bounciness to the up and down one the cell already has from being added. This results in a very weird circular motion in the cell.

enter image description here

My question is, is it possible to stop the vertical motion of a UIAttachmentBehavior while the collection view is being scrolled? I've tried different approaches like using multiple attachment behaviors and disabling scrolling in the collection view till the newly added cell has come to rest, but non of them seem to stop this.

6
Leave it like it is, that's hilarious. - stack356
Are you using a custom layout? Then make sure sure you're adding the motion affects in the layout rather than the cell. - Anil Kumar
did you ever solve this issue (in an elegant way)? - achi
No, my only solution so far is the one I posted below. - Hesham

6 Answers

4
votes

One way to solve this is to use the inherited .action property of the attachment behavior.

You will need to set up a couple of variables first, something like (going from memory, untested code):

BOOL limitVerticalMovement = TRUE;
CGFloat staticCenterY = CGRectGetHeight(self.collectionView.frame) / 2;

Set these as properties of your custom UICollectionViewFlowLayout

When you create your attachment behavior:

UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:center];
attachment.damping = 1.0f;
attachment.frequency = 1.5f;
attachment.action = ^{
    if (!limitVerticalMovement) return;

    CGPoint center = item.center;
    center.y = staticCenterY;
    item.center = center;
};

Then you can turn the limiting function on and off by setting limitVerticalMovement as appropriate.

2
votes

If you're using iOS 9 and above then sliding function within attachment class will work perfectly for that job:

class func slidingAttachmentWithItem(_ item: UIDynamicItem,
                attachmentAnchor point: CGPoint,
               axisOfTranslation axis: CGVector) -> Self

it can be used easily, and it's very effective for sliding Apple documentation

1
votes

Have you tried manually removing animations from cells with CALayer's removeAllAnimations?

1
votes

You'll want to remove the behaviour when the collection view starts scrolling, or perhaps greatly reduce the springiness so that it comes to rest smoothly, but quickly. If you think about it, what you're seeing is a realistic movement for the attachment behaviour you've described.

To keep the vertical bouncing at the same rate but prevent horizontal bouncing, you'd need to add other behaviours - like a collision behaviour with boundaries to the left and right of each added cell. This is going to increase the complexity of the physics a little, and may affect scrolling performance, but it would be worth a try.

1
votes

Here's how I managed to do it. The FloatRange limits the range of the attachment, so if you want it to go all the way up and down the screen you just set really large numbers.

This goes inside func recognizePanGesture(sender: UIPanGestureRecognizer) {}

let location = sender.location(in: yourView.superview)
var direction = "Y"

var center = CGPoint(x: 0, y: 0)
if self.direction == "Y" {center.y = 1}
if self.direction == "X" {center.x = 1}

let sliding = UIAttachmentBehavior.slidingAttachment(with: youView, attachmentAnchor: location, axisOfTranslation: CGVector(dx: center.x, dy: center.y))

sliding.attachmentRange = UIFloatRange(minimum: -2000, maximum: 2000)
animator = UIDynamicAnimator(referenceView: self.superview!)
animator.addBehavior(sliding)
0
votes

I've resorted to disabling scrolling in the collection view for a specific amount of time after a new cell is added, then removing the attachment behavior after that time has passed using its action property, then adding a new attachment behavior again immediately.

That way I make sure the upwards animation stops before the collection view is scrolled left or right, but also the left/right bounciness is still there when scrolling.

Certainly not the most elegant solution but it works.