UIScrollViewDelegate有两个委托方法scrollViewDidScroll:
,scrollViewDidEndScrollingAnimation:
但是在滚动完成时,这两个都不告诉您。scrollViewDidScroll
仅通知您滚动视图已滚动,而不是已完成滚动。
scrollViewDidEndScrollingAnimation
仅当您以编程方式移动滚动视图时,其他方法似乎才触发,而不是在用户滚动时。
有谁知道检测滚动视图何时完成滚动的方案?
UIScrollViewDelegate有两个委托方法scrollViewDidScroll:
,scrollViewDidEndScrollingAnimation:
但是在滚动完成时,这两个都不告诉您。scrollViewDidScroll
仅通知您滚动视图已滚动,而不是已完成滚动。
scrollViewDidEndScrollingAnimation
仅当您以编程方式移动滚动视图时,其他方法似乎才触发,而不是在用户滚动时。
有谁知道检测滚动视图何时完成滚动的方案?
Answers:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self stoppedScrolling];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
[self stoppedScrolling];
}
}
- (void)stoppedScrolling {
// ...
}
在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];
...
}
我认为scrollViewDidEndDecelerating是您想要的。其UIScrollViewDelegates可选方法:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
告诉代表滚动视图已结束,从而降低了滚动运动。
对于与拖动交互有关的所有滚动,这将足够:
- (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
则不会被调用
在其他一些答案中对此进行了描述,但是这里(在代码中)介绍了滚动完成后如何组合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
}
我只是发现了这个问题,与我问的问题几乎相同: 如何确切知道UIScrollView的滚动何时停止?
尽管didEndDecelerating在滚动时有效,但是没有注册带有固定释放的平移。
我最终找到了解决方案。didEndDragging具有参数WillDecelerate,在固定释放情况下为false。
通过在DidEndDragging中检查!decelerate并结合didEndDecelerating,可以得到两种情况,这两种情况都是滚动结束的。
如果您喜欢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
})
这将同时考虑到拖动和减速。
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollingFinished(scrollView: scrollView)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if decelerate {
//didEndDecelerating will be called for sure
return
}
else {
scrollingFinished(scrollView: scrollView)
}
}
func scrollingFinished(scrollView: UIScrollView) {
// Your code
}
我已经尝试过Ashley Smart的答案,它的工作原理很吸引人。这是另一个想法,仅使用scrollViewDidScroll
-(void)scrollViewDidScroll:(UIScrollView *)sender
{
if(self.scrollView_Result.contentOffset.x == self.scrollView_Result.frame.size.width) {
// You have reached page 1
}
}
我只有两页,所以对我有用。但是,如果您有多个页面,则可能会出现问题(您可以检查当前偏移量是否为宽度的倍数,但是您不知道用户是在第二页上停留还是在第三页或第二页上停留)更多)
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
}
如果有人需要,这是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)
...
}
刚刚开发的解决方案可以检测整个应用程序何时滚动完成:https: //gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e
它基于跟踪运行循环模式变化的想法。并至少在滚动0.2秒后执行块。
这是跟踪iOS 10+的运行循环模式更改的核心思想:
- (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 ]];
}
回顾(以及新手)。并不是那么痛苦。只需添加协议,然后添加检测所需的功能。
在包含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")
}
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)
}
上面的逻辑解决了诸如用户何时滚动离开表视图之类的问题。没有逻辑,当您滚动离开表视图时,didEnd
将被调用,但不会执行任何操作。目前在2020年使用。
在某些较早的iOS版本(如iOS 9、10)上,scrollViewDidEndDecelerating
如果通过触摸突然停止scrollView ,则不会触发。
但在当前版本(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”注释的两行中使用。