UPCarouselFlowLayout icon indicating copy to clipboard operation
UPCarouselFlowLayout copied to clipboard

Deceleration is instantaneous at times

Open iwasrobbed opened this issue 8 years ago • 18 comments
trafficstars

  • Expected Behavior: For a normal paging UICollectionView using a standard flow layout, you can swipe the cell quickly and let go (before it reaches the threshold to show the next view) and it will gently bounce back.
  • Actual Behavior: For this layout, it seems to bounce back instantaneously without any animation. (If you drag gently and let go, it will bounce back gently... if you flick it quickly and let go quickly, it will be instantaneous).

Have you come across this before?

I tested both on device and simulator and see it both places. I also tested with different deceleration rates and it doesn't seem to change anything

I'm using a vanilla UICollectionView w/ vertical scrolling and this as the layout:

final class SwipeableLessonLayout: UPCarouselFlowLayout {
    
    // MARK: - Instantiation
    
    override init() {
        super.init()
        
        itemSize = CGSize(width: UIScreen.main.bounds.size.width, height: LessonSectionCell.cellHeight)
        scrollDirection = .vertical
        minimumLineSpacing = 0
        minimumInteritemSpacing = 0
        
        sideItemScale = 1
        sideItemAlpha = 0.4
        spacingMode = UPCarouselFlowLayoutSpacingMode.fixed(spacing: 0)
    }
    
    // MARK: - Unsupported Initializers
    
    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }

}

iwasrobbed avatar Feb 05 '17 22:02 iwasrobbed

@iwasrobbed You meant you can only swipe one by one not able to swipe multiple items at once like this:

https://dribbble.com/shots/3132598-Carousel

dangthaison91 avatar Feb 06 '17 21:02 dangthaison91

@dangthaison91 No; the paging behavior is fine. I mean the scroll view underlying the collection view should always bounce nicely and come to a slow stop.

Unfortunately, the way this is implemented is causing it to sometimes come to an instantaneous stop and it skips the deceleration animation all together. I haven't been able to debug it yet so wanted to check if this was known behavior

iwasrobbed avatar Feb 06 '17 21:02 iwasrobbed

Will changing ScrollDecelerate help?

dangthaison91 avatar Feb 06 '17 21:02 dangthaison91

@dangthaison91 In the original issue I wrote:

I also tested with different deceleration rates and it doesn't seem to change anything

iwasrobbed avatar Feb 06 '17 22:02 iwasrobbed

Seeing this as well. I think the issue is in the targetContentOffset function.

matthew-reilly avatar Feb 06 '17 23:02 matthew-reilly

Yup, this only happens when withScrollingVelocity is non-nil.

matthew-reilly avatar Feb 06 '17 23:02 matthew-reilly

@iwasrobbed I've been playing around with this and found something that may help. Manually calling: collectionView.setContentOffset(targetContentOffset, animated: true) at the end of the targetContentOffset if the velocity > 0 seems to make things smooth.

I think setting offsets + velocity mucks up collectionviews, but I could be wrong.

Playing around with the velocity/offset could provide a way to make this work flawlessly. Right now with this fix, if I flick it - it advances to the next page smoothly.

matthew-reilly avatar Feb 07 '17 00:02 matthew-reilly

@matthew-reilly That's unfortunately jittery for me when I don't flick it hard enough to advance to the next page. It does seem to be an issue w/ velocity and offset though.

The thing I don't understand about this function is that it's only returning a point, but does nothing with the velocity. I'm guessing they recalculate the velocity based on what point you return.

By calling setContentOffset, I think you're just overriding whatever effect targetContentOffset would otherwise have so that would point more towards using the regular UIScrollViewDelegate methods instead of this one.

Still haven't had time to dig into this, but I appreciate the help

iwasrobbed avatar Feb 07 '17 07:02 iwasrobbed

Yeah, it's not perfect. I think the solution may lie the CV itself. Per: http://stackoverflow.com/questions/15800111/uiscrollview-revert-to-starting-position-without-snaps

matthew-reilly avatar Feb 07 '17 21:02 matthew-reilly

Any update on this?

kafejo avatar Mar 20 '17 11:03 kafejo

I have been able to improve the scrolling behaviour by taking the velocity into account when calculating targetContentOffset

let proposedContentOffsetCenterOrigin = (isHorizontal ? proposedContentOffset.x + (proposedContentOffset.x * velocity.x) : proposedContentOffset.y + (proposedContentOffset.y * velocity.y)) + midSide

It's much better but sometimes it still jumps.

kafejo avatar Mar 20 '17 12:03 kafejo

@iwasrobbed did you get any solution.. i'm also facing this issue.. can you help me

punithbm avatar Jun 09 '17 18:06 punithbm

@kafejo did you find any better solution for this

punithbm avatar Jun 29 '17 06:06 punithbm

I've ended up with the solution I described above. It works much better.

kafejo avatar Jul 01 '17 16:07 kafejo

So...... based on default behavior of scrollviews, if I flick my finger on a scrollview that is already scrolling, it will then accelerates, and goes faster.

What I noticed is that the proposedTargetOffset appears to think it's supposed to overshoot by a lot, probably based on the velocity it is going! The the scrollview appears to be compounding the velocity for every flick, by adding it to the velocity it already thinks the scrollview is going, and then overshoots into a monster bounce at the end when it reaches the boundary of it's content size.

Here are some interesting readings I got while flicking faster and faster.

Index -- proposedOffset ------------ nextPageOffset 1 ------ (0.0, 2542.0) ----------------- (0.0, 812.0) ------- Flick 1 2 ------ (0.0, 4292.0) ---------------- (0.0, 1624.0) ------ Flick 2 3 ------ (0.0, 4735.0) ---------------- (0.0, 2436.0) ------ Flick 3 4 ------ (0.0, 4872.0) ---------------- (0.0, 3248.0)
5 ------ (0.0, 4872.0) ---------------- (0.0, 4060.0) 6 ------ (0.0, 4872.0) ---------------- (0.0, 4872.0) 6 ------ (0.0, 4872.0) ---------------- (0.0, 4872.0)

With each flick, the proposed content offset kept growing, and by index 3, it already thought it was supposed to scroll nearly to the end, even though I was modifying the proposed content offset. This action should have set off some flag to start decelerating as soon as possible, but it doesn't. I sense this is a bug in the scrollview, or this was never intended to be used this way.

AntonTheDev avatar Apr 05 '18 03:04 AntonTheDev

Have anyone found solution for this issue? Except let proposedContentOffsetCenterOrigin = (isHorizontal ? proposedContentOffset.x + (proposedContentOffset.x * velocity.x) : proposedContentOffset.y + (proposedContentOffset.y * velocity.y)) + midSide it's not full solution

Banck avatar May 24 '18 05:05 Banck

Another improvement can be adding a threshold value to detect if it's a flickering or not:

var proposedContentOffsetCenterOrigin = (isHorizontal ? proposedContentOffset.x : proposedContentOffset.y) + midSide
let flickVelocity: CGFloat = 0.3 // threshold
if (isHorizontal && abs(velocity.x) > flickVelocity) || (!isHorizontal && abs(velocity.y) > flickVelocity) {
    proposedContentOffsetCenterOrigin = (isHorizontal
        ? proposedContentOffset.x + (proposedContentOffset.x * velocity.x)
        : proposedContentOffset.y + (proposedContentOffset.y * velocity.y)
    ) + midSide
}

This kind of solution for flickering I found on this SO thread.

chika-kasymov avatar Jun 11 '18 11:06 chika-kasymov

https://gist.github.com/romanfurman6/e907ccaed89976f8591b9cf736108619

romanfurman6 avatar Jun 14 '18 14:06 romanfurman6