2

I have a collection view that holds cells with image views. I trigger an animation when the user presses down on the image view (using touchesBegan), and another animation for when the user releases (using touchesEnded).

The animations work perfectly only if I hold down on my click then release (delayed click), but when I fast click, the animation jumps as if the duration was set to 0.

I believe the issue is because collection view is subclass of scroll view, and scrollViews "temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement. If the timer fires without a significant change in position, the scroll view sends tracking events to the touched subview of the content view."

https://developer.apple.com/documentation/uikit/uiscrollview#//apple_ref/doc/uid/TP40006922

From what I can gather, I think that the touch interception from the collection view is causing problems with the animation if the click is faster than the touch timer. When I test using a regular view instead of a collection view as the superview, the animation works perfectly and doesn't require a delayed click.

If this is the case, then how is the animation triggered for a fast-click at all? Moreover, how might I be able to tigger the animation without having to use a delayed click?

If this is not the case, then what might be the reason for this issue?

Here is my code for animation and touches:

func animateClickerAndBallPoint(newXpositionForClicker: CGFloat, newXpositionForBallPoint: CGFloat, ballPoint: UIImageView) {
    UIView.animate(withDuration: 0.1) {
        self.frame.origin.x = newXpositionForClicker
        ballPoint.frame.origin.x = newXpositionForBallPoint
    }
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let ballPoint = self.ballPoint else {return}
    self.animateClickerAndBallPoint(newXpositionForClicker: 288, newXpositionForBallPoint: 11, ballPoint: ballPoint)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let ballPoint = self.ballPoint else {return}

    if isInWritingMode == true {
        animateClickerAndBallPoint(newXpositionForClicker: 306, newXpositionForBallPoint: 26,  ballPoint: ballPoint)
        isInWritingMode = false

    } else {
        animateClickerAndBallPoint(newXpositionForClicker: 297, newXpositionForBallPoint: 17, ballPoint: ballPoint)
        isInWritingMode = true
    }
}
leedex
  • 963
  • 8
  • 19
  • Without seeing your code all that can be done is speculate. Can you include some code to show your animation, touchesBegan, and any other related code? – Jake Mar 29 '18 at 22:38
  • @Rob thank you for the suggestion but I don't think didHighlight methods would be my best choice, mainly because the imageView to touch is very small in comparison to the cell its inside. – leedex Mar 29 '18 at 22:45

3 Answers3

2

As an alternative, rather than doing your own touchesBegan and touchesEnded, you might consider hooking into the button's beginTracking and endTracking.

For example, you could subclass the button and provide whatever animation you wanted:

class AnimatedButton: UIButton {

    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        UIView.animate(withDuration: 0.25) {
            self.transform = .init(scaleX: 0.8, y: 0.8)
        }

        return true
    }

    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
        UIView.animate(withDuration: 0.25) {
            self.transform = .identity
        }
    }

}

That yields:

enter image description here

Or, if you wanted to do it based upon the section of the cell, itself, you could hook into the collection view's "highlighting" mechanism as illustrated in https://stackoverflow.com/a/45664054/1271826.


BTW, you're assuming that the problem is the container view messing with touches. Are you 100% sure that's the problem? It could be a basic animation problem. E.g.

  • For example, if you start a second animation while the first one is still running, it won't finish the first animation but will rather immediately start the second animation from wherever it was mid-animation (and in modern iOS versions, using whatever speed at which it was currently traveling) and transition to the new destination.

    For example, here are two views that I'm animating precisely the same distance down for one second down and then back up for another second. But for the view on the right, I started the second animation 0.1 seconds into the first animation (i.e. interrupting the first animation):

    enter image description here

    As you can see, because this second example is interrupting your animations, it looks like it's just snapping back.

  • I don't think it's likely in this scenario, but you have to be careful if you're using autolayout. If you do anything that triggers the auto layout engine to re-apply its constraints (and this could be practically any UI action, such as something as innocuous as setting the text in some unrelated label), that will stop whatever animations you have underway and will snap the views back to where the constraints dictated they should be.

    If the views were laid out using auto layout, you may want to consider animating them using auto layout, too. For example, rather than adjusting the frame (or whatever), create IBOutlet for your constraint, change the constant for that constraint, and then animate the call to layoutIfNeeded.

Bottom line, you might want to see if you can verify whether the problem is really related to touch events and not some unrelated animation problem.

Unfortunately, there's not enough in your example for us to diagnose what the source of the problem is. You might consider creating a MVCE, a minimal, verifiable, and complete example of the problem. And I'd suggest creating a MVCE without a collection view and another with, so you can confirm whether the collection view is actually the source of the problem. But, bottom line, until we can reproduce your problem, it's hard for us to help you solve the problem.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Just wondering, but would this be any different from subclassing UIImageView and using touchesBegan/Ended? Since begin/endTracking gets called when a touch event enters the control’s bounds, wouldn't the touch event get intercepted by the CollectionView just like with touchesBegan/Ended? At which point the button or view would require a long click to animate correctly? – leedex Mar 29 '18 at 23:50
  • The common theme in both of my solutions (button tracking and cell highlighting) is that I hook into the highest level of abstraction the UIKit provides. E.g. Apple has solved dealing with touches in a button in a cell and buttons have imageviews built in, so why should I reinvent the wheel? I only dive into touches where I absolutely need to (e.g. drawing app where I need coalesced or predicted touches). I avoid dealing with touches where I can because I just got tired of the sorts of hassles to which you allude. But you can try handling touches in `UIImageView` if you want. – Rob Mar 30 '18 at 00:16
  • @leedex - Because we can't see the problem, it's hard for us to diagnose it, but I'd suggest you might want to confirm that the problem is really related to touches in the collection view and not some simple animation problem. See my notes at the end of my revised answer. – Rob Mar 30 '18 at 17:55
  • Hey Rob! thanks for the update. much appreciated. So the problem turns out to be a mix of both touch interception and triggering new animation on the same view before the first animation ends (solved). I am fairly certain I have a touch intercept problem because I tested when scrolling is disabled and the animation triggeres the instant I click the view (which is the desired behavior), whereas when scrolling is enabled, there's a delay between the click and animation. right now im just using a jerry-rigged solution of just disable/enable scrolling when I need to. – leedex Mar 31 '18 at 18:33
  • 1
    Hey @leedex it's usually good practice to upvote posts like the one from Rob as he certainly spent valuable time to write it. It was helpful for you, for me, so let's be thankful ;) – RomOne Apr 12 '18 at 04:56
1

I had the same kind of issues when playing with animations in scrollviews. I fixed it by using that parameter in the animation: UIViewAnimationOptions.allowUserInteraction

So your animation block would end up like that:

UIView.animate(withDuration: duration,
                                   delay: 0,
                                   usingSpringWithDamping: CGFloat(0.30),
                                   initialSpringVelocity: CGFloat(10.0),
                                   options: UIViewAnimationOptions.allowUserInteraction,
                                   animations: {

                                    applyWhatEverTransform()

                    },completion: completion)

I'm a bit speculating on your issue, as we don't have code from you. But of course, if you use different sort of animations this could not work ;)

Also if that doesn't help. To help you debug, you could try to disable the scrolling of your collection view to see if that the event that intercept your touch events.

RomOne
  • 2,065
  • 17
  • 29
  • Thank you for this, I've disabled scrolling to test and it looks like once scrolling is disabled, the animations perform as desired! hmm this is interesting... i guess that means that once scrolling is disabled, whatever magic scrollviews use to detect touches stops being used. – leedex Mar 29 '18 at 23:00
  • it looks like your animation is cancel somehow... Have you try to override your touchCancel func? – RomOne Mar 29 '18 at 23:22
  • Yes. Ive tried overriding touchesCancelled. Actually, the animation always triggers, it just jumps as if the duration was 0 if I click too fast. When testing touchesCancelled it only get called if the user drags the finger after touch down. – leedex Mar 29 '18 at 23:27
1

Have you tried delaysContentTouches = false?

https://developer.apple.com/documentation/uikit/uiscrollview/1619398-delayscontenttouches

Tells the scrollView/collectionView to not delay touches on the cells. Worked for me to make responsive cells immediately when I started tapping on them. Didn't make my scrolling buggy either.

siefix
  • 916
  • 1
  • 10
  • 19