4

I have a UIScrollView which scrolls automatically by setting its content offset within via a UIViewPropertyAnimator. The auto-scrolling is working as expected, however I also want to be able to interrupt the animation to scroll manually.

This seems to be one of the selling points of UIViewPropertyAnimator:

...dynamically modify your animations before they finish

However it doesn't seem to play nicely with scroll views (unless I'm doing something wrong here).

For the most part, it is working. When I scroll during animation, it pauses, then resumes once deceleration has ended. However, as I scroll towards the bottom, it rubber bands as if it is already at the end of the content (even if it is nowhere near). This is not an issue while scrolling towards the top.

Having noticed this, I checked the value of scrollView.contentOffset and it seems that it is stuck at the maximum value + the rubber banding offset. I found this question/answer which seems to be indicate this could be a bug with UIViewPropertyAnimator.

My code is as follows:

private var maxYOffset: CGFloat = .zero
private var interruptedFraction: CGFloat = .zero

override func layoutSubviews() {
    super.layoutSubviews()
    
    self.maxYOffset = self.scrollView.contentSize.height - self.scrollView.frame.height
}

private func scrollToEnd() {
    let maxOffset = CGPoint(x: .zero, y: self.maxYOffset)
    let duration = (Double(self.script.wordCount) / Double(self.viewModel.wordsPerMinute)) * 60.0
    
    let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
        self.scrollView.contentOffset = maxOffset
    }
    animator.startAnimation()
    self.scrollAnimator = animator
}

extension UIAutoScrollView: UIScrollViewDelegate {

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        // A user initiated pan gesture will begin scrolling.
    
        if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
            self.interruptedFraction = scrollAnimator.fractionComplete
            scrollAnimator.pauseAnimation()
        }
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
            scrollAnimator.startAnimation()
        }
    }

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
            scrollAnimator.startAnimation()
        }
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        switch scrollView.panGestureRecognizer.state {
        case .changed:
            // A user initiated pan gesture triggered scrolling.
                    
            if let scrollAnimator = self.scrollAnimator {
                let fraction = (scrollView.contentOffset.y - self.maxYOffset) / self.maxYOffset
                let boundedFraction = min(max(.zero, fraction), 1)
            
                scrollAnimator.fractionComplete = boundedFraction + self.interruptedFraction
            }
        default:
        break
        }
    }
}

Is there anywhere obvious I'm going wrong here? Or any workarounds I can employ to make the scroll view stop rubber banding on scroll downwards?

4

1 回答 1

0

您可以添加点击手势识别器并调用此功能,

extension UIScrollView  {
    func stopDecelerating() {
        let contentOffset = self.contentOffset
        self.setContentOffset(contentOffset, animated: false)
    }
}
于 2021-08-13T10:40:14.193 回答