[英]How to detect a UIScrollView has finished scrolling when animation is false?
[英]How to detect when a UIScrollView has finished scrolling
UIScrollViewDelegate 有两个委托方法scrollViewDidScroll:
和scrollViewDidEndScrollingAnimation:
但这些都不会告诉你滚动何时完成。 scrollViewDidScroll
仅通知您滚动视图确实滚动了,而不是它已完成滚动。
另一种方法scrollViewDidEndScrollingAnimation
似乎只有在您以编程方式移动滚动视图时才会触发,而不是在用户滚动时触发。
有谁知道检测滚动视图何时完成滚动的方案?
320 的实现要好得多——这里有一个补丁可以让滚动的开始/结束保持一致。
-(void)scrollViewDidScroll:(UIScrollView *)sender
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
//ensure that the end of scroll is fired.
[self performSelector:@selector(scrollViewDidEndScrollingAnimation:) withObject:sender afterDelay:0.3];
...
}
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
...
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self stoppedScrolling];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
[self stoppedScrolling];
}
}
- (void)stoppedScrolling {
// ...
}
对于与拖动交互相关的所有滚动,这就足够了:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
_isScrolling = NO;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
_isScrolling = NO;
}
}
现在,如果您的滚动是由于编程 setContentOffset/scrollRectVisible( animated
= YES 或者您显然知道滚动何时结束):
- (void)scrollViewDidEndScrollingAnimation {
_isScrolling = NO;
}
如果您的滚动是由于其他原因(例如键盘打开或键盘关闭),您似乎必须使用 hack 来检测事件,因为scrollViewDidEndScrollingAnimation
也没有用。
分页滚动视图的情况:
因为,我想,苹果申请的加速曲线, scrollViewDidEndDecelerating
被调用每一个阻力,所以没有必要使用scrollViewDidEndDragging
在这种情况下。
这已在其他一些答案中进行了描述,但这里(在代码中)如何组合scrollViewDidEndDecelerating
和scrollViewDidEndDragging:willDecelerate
在滚动完成时执行一些操作:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self stoppedScrolling];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
[self stoppedScrolling];
}
}
- (void)stoppedScrolling
{
// done, do whatever
}
我认为 scrollViewDidEndDecelerating 是你想要的。 它的 UIScrollViewDelegates 可选方法:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
告诉委托滚动视图已结束减速滚动移动。
我刚刚发现了这个问题,这与我问的几乎相同: 如何确切知道 UIScrollView 的滚动何时停止?
虽然 didEndDecelerating 在滚动时有效,但使用固定释放的平移不会注册。
我最终找到了解决方案。 didEndDragging 有一个参数 WillDecelerate,在静止释放情况下为 false。
通过检查 DidEndDragging 中的 !decelerate 并结合 didEndDecelerating,您可以获得滚动结束的两种情况。
如果有人需要,这是 Swift 中的 Ashley Smart 答案
func scrollViewDidScroll(_ scrollView: UIScrollView) {
NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation), with: nil, afterDelay: 0.3)
...
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
NSObject.cancelPreviousPerformRequests(withTarget: self)
...
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollingFinished(scrollView: scrollView)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if decelerate {
//didEndDecelerating will be called for sure
return
}
scrollingFinished(scrollView: scrollView)
}
func scrollingFinished(scrollView: UIScrollView) {
// Your code
}
如果你喜欢 Rx,你可以像这样扩展 UIScrollView:
import RxSwift
import RxCocoa
extension Reactive where Base: UIScrollView {
public var didEndScrolling: ControlEvent<Void> {
let source = Observable
.merge([base.rx.didEndDragging.map { !$0 },
base.rx.didEndDecelerating.mapTo(true)])
.filter { $0 }
.mapTo(())
return ControlEvent(events: source)
}
}
这将允许您这样做:
scrollView.rx.didEndScrolling.subscribe(onNext: {
// Do what needs to be done here
})
这将同时考虑拖动和减速。
已接受答案的 Swift 版本:
func scrollViewDidScroll(scrollView: UIScrollView) {
// example code
}
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// example code
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView!, atScale scale: CGFloat) {
// example code
}
刚刚开发的解决方案来检测何时滚动完成应用程序: https : //gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e
它基于跟踪runloop模式变化的思想。 并在滚动后至少 0.2 秒后执行块。
这是跟踪运行循环模式更改 iOS10+ 的核心思想:
- (void)tick {
[[NSRunLoop mainRunLoop] performInModes:@[ UITrackingRunLoopMode ] block:^{
[self tock];
}];
}
- (void)tock {
self.runLoopModeWasUITrackingAgain = YES;
[[NSRunLoop mainRunLoop] performInModes:@[ NSDefaultRunLoopMode ] block:^{
[self tick];
}];
}
以及针对 iOS2+ 等低部署目标的解决方案:
- (void)tick {
[[NSRunLoop mainRunLoop] performSelector:@selector(tock) target:self argument:nil order:0 modes:@[ UITrackingRunLoopMode ]];
}
- (void)tock {
self.runLoopModeWasUITrackingAgain = YES;
[[NSRunLoop mainRunLoop] performSelector:@selector(tick) target:self argument:nil order:0 modes:@[ NSDefaultRunLoopMode ]];
}
我已经尝试过 Ashley Smart 的答案,它的效果非常好。 这是另一个想法,只使用 scrollViewDidScroll
-(void)scrollViewDidScroll:(UIScrollView *)sender
{
if(self.scrollView_Result.contentOffset.x == self.scrollView_Result.frame.size.width) {
// You have reached page 1
}
}
我只有两页,所以它对我有用。 但是,如果您有多个页面,则可能会出现问题(您可以检查当前偏移量是否是宽度的倍数,但您将不知道用户是停在第 2 页还是正在前往第 3 页或更多的)
回顾(和新手)。 这不是那么痛苦。 只需添加协议,然后添加检测所需的功能。
在包含 UIScrolView 的视图(类)中,添加协议,然后将此处的任何函数添加到您的视图(类)中。
// --------------------------------
// In the "h" file:
// --------------------------------
@interface myViewClass : UIViewController <UIScrollViewDelegate> // <-- Adding the protocol here
// Scroll view
@property (nonatomic, retain) UIScrollView *myScrollView;
@property (nonatomic, assign) BOOL isScrolling;
// Protocol functions
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView;
// --------------------------------
// In the "m" file:
// --------------------------------
@implementation BlockerViewController
- (void)viewDidLoad {
CGRect scrollRect = self.view.frame; // Same size as this view
self.myScrollView = [[UIScrollView alloc] initWithFrame:scrollRect];
self.myScrollView.delegate = self;
self.myScrollView.contentSize = CGSizeMake(scrollRect.size.width, scrollRect.size.height);
self.myScrollView.contentInset = UIEdgeInsetsMake(0.0,22.0,0.0,22.0);
// Allow dragging button to display outside the boundaries
self.myScrollView.clipsToBounds = NO;
// Prevent buttons from activating scroller:
self.myScrollView.canCancelContentTouches = NO;
self.myScrollView.delaysContentTouches = NO;
[self.myScrollView setBackgroundColor:[UIColor darkGrayColor]];
[self.view addSubview:self.myScrollView];
// Add stuff to scrollview
UIImage *myImage = [UIImage imageNamed:@"foo.png"];
[self.myScrollView addSubview:myImage];
}
// Protocol functions
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
NSLog(@"start drag");
_isScrolling = YES;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(@"end decel");
_isScrolling = NO;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
NSLog(@"end dragging");
if (!decelerate) {
_isScrolling = NO;
}
}
// All of the available functions are here:
// https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html
我有一个点击和拖动动作的案例,我发现拖动正在调用 scrollViewDidEndDecelerating
并且使用代码手动更改偏移量 ([_scrollView setContentOffset:contentOffset animation:YES];) 正在调用 scrollViewDidEndScrollingAnimation。
//This delegate method is called when the dragging scrolling happens, but no when the tapping
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//do whatever you want to happen when the scroll is done
}
//This delegate method is called when the tapping scrolling happens, but no when the dragging
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
//do whatever you want to happen when the scroll is done
}
另一种方法是使用scrollViewWillEndDragging:withVelocity:targetContentOffset
每当用户抬起手指并包含滚动将停止的目标内容偏移量时调用它。 在scrollViewDidScroll:
使用此内容偏移量正确识别滚动视图何时停止滚动。
private var targetY: CGFloat?
public func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
targetY = targetContentOffset.pointee.y
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y == targetY) {
print("finished scrolling")
}
在一些早期的IOS版本(例如iOS 9,10), scrollViewDidEndDecelerating
不会,如果滚动视图突然被停止接触引发的。
但是在当前版本(iOS 13)中, scrollViewDidEndDecelerating
会被触发(据我所知)。
因此,如果您的应用程序也针对早期版本,您可能需要像 Ashley Smart 提到的那样的解决方法,或者您可以使用以下方法。
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if !scrollView.isTracking, !scrollView.isDragging, !scrollView.isDecelerating { // 1
scrollViewDidEndScrolling(scrollView)
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate, scrollView.isTracking, !scrollView.isDragging, !scrollView.isDecelerating { // 2
scrollViewDidEndScrolling(scrollView)
}
}
func scrollViewDidEndScrolling(_ scrollView: UIScrollView) {
// Do something here
}
UIScrollView 将通过三种方式停止:
- 快速滚动并自行停止
- 通过手指触摸快速滚动和停止(如紧急制动)
- 缓慢滚动并停止
第一个可以通过scrollViewDidEndDecelerating
和其他类似方法检测到,而其他两个则不能。
幸运的是, UIScrollView
有三个状态我们可以用来识别它们,分别用在“//1”和“//2”注释的两行中。
我需要一个回调,当所有缩放和滚动手势结束并且所有动画(如减速和缩放反弹)完成时触发。
UIScrollView
有几个我们可以检查的标志,比如isTracking
和isDecelerating
。 我们还有各种scrollViewDidEnd...
委托回调。 但是,这些标志和回调的更新顺序不一致,覆盖所有边缘情况看起来非常困难。
我现在正在做的是将滚动视图标志的检查推迟到下一个运行循环。 这确保所有标志都已更新。
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
dispatchScrollViewInteractionUpdate()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
dispatchScrollViewInteractionUpdate()
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
dispatchScrollViewInteractionUpdate()
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
dispatchScrollViewInteractionUpdate()
}
private func dispatchScrollViewInteractionUpdate() {
DispatchQueue.main.async {
self.updateScrollViewInteraction()
}
}
private func updateScrollViewInteraction() {
if !scrollView.isZooming
&& !scrollView.isDragging
&& !scrollView.isDecelerating
&& !scrollView.isZoomBouncing
&& !scrollView.isTracking {
print("All animations and interactions have ended.")
}
}
UIScrollview 有一个委托方法
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
在委托方法中添加以下代码行
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGSize scrollview_content=scrollView.contentSize;
CGPoint scrollview_offset=scrollView.contentOffset;
CGFloat size=scrollview_content.width;
CGFloat x=scrollview_offset.x;
if ((size-self.view.frame.size.width)==x) {
//You have reached last page
}
}
有一种UIScrollViewDelegate
方法可用于在滚动真正完成时检测(或更好地说“预测”):
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
的UIScrollViewDelegate
可用于在滚动真正完成时检测(或更好地说“预测”)。
在我的情况下,我将它与水平滚动一起使用,如下所示(在Swift 3 中):
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
perform(#selector(self.actionOnFinishedScrolling), with: nil, afterDelay: Double(velocity.x))
}
func actionOnFinishedScrolling() {
print("scrolling is finished")
// do what you need
}
使用 Ashley Smart 逻辑,正在转换为 Swift 4.0 及更高版本。
func scrollViewDidScroll(_ scrollView: UIScrollView) {
NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation(_:)), with: scrollView, afterDelay: 0.3)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
NSObject.cancelPreviousPerformRequests(withTarget: self)
}
上面的逻辑解决了诸如用户从 tableview 滚动时等问题。 如果没有逻辑,当您滚动 tableview 时, didEnd
将被调用,但它不会执行任何操作。 目前在 2020 年使用它。
RXSwift 版本
commentsTableView.rx.didEndScrollingAnimation
.bind(
onNext: { _ in
debugPrint(":DEBUG:", "didEndScrollingAnimation")
}
)
.disposed(by: disposeBag)
我经历了所有的排列,我发现的最好的代码就是这个。 有趣的部分是在“scrollViewWillBeginDragging”中并检测速度是否为零。 当用户在动画滚动期间将手指放在屏幕上以制动滚动时,可能会发生这种情况。 出于某种原因,这样做不会触发任何其他委托“停止”API。
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView.superview)
if (velocity.equalTo(CGPoint.zero)){
scrollViewDidEndScrollingAnimation(scrollView)
}
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if (decelerate == false){
scrollViewDidEndScrollingAnimation(scrollView)
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollViewDidEndScrollingAnimation(scrollView)
}
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
// do whatever when scrolling stops
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.