Как обнаружить, когда UIScrollView завершил прокрутку

UIScrollViewDelegate имеет два метода делегата scrollViewDidScroll: и scrollViewDidEndScrollingAnimation:, но ни один из них не говорит вам о завершении прокрутки. scrollViewDidScroll уведомляет вас только о том, что прокрутка не прокручивала прокрутку.

Другой метод scrollViewDidEndScrollingAnimation только срабатывает, если вы программно перемещаете представление прокрутки, если пользователь не прокручивается.

Кто-нибудь знает о схеме обнаружения, когда прокрутка просмотрела прокрутку?

Ответ 1

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self stoppedScrolling];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        [self stoppedScrolling];
    }
}

- (void)stoppedScrolling {
    // ...
}

Ответ 2

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];
...
}

Ответ 3

Я думаю, что scrollViewDideEndDecelerating - это тот, который вы хотите. Его UIScrollViewDelegates необязательный метод:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Сообщает делегату, что представление прокрутки закончилось замедлением движения прокрутки.

Документация UIScrollViewDelegate

Ответ 4

Для всех свитков, связанных с перетаскиванием взаимодействий, этого будет достаточно:

- (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;
}

Если ваш свиток связан с чем-то другим (например, с открытием клавиатуры или закрытием клавиатуры), похоже, вам придется обнаруживать событие с помощью взлома, потому что scrollViewDidEndScrollingAnimation тоже не полезно.

Случай прокрутки PAGINATED:

Потому что, я думаю, Apple применяет кривую ускорения, scrollViewDidEndDecelerating вызывается для каждого перетаскивания, поэтому нет необходимости использовать scrollViewDidEndDragging в этом случае.

Ответ 5

Это описано в некоторых других ответах, но здесь (в коде), как объединить 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
}

Ответ 6

Я только что нашел этот вопрос, который почти такой же, как я спросил: Как узнать, когда остановилась прокрутка UIScrollView?

Хотя didEndDecelerating работает при прокрутке, панорамирование со стационарным выпуском не регистрируется.

В итоге я нашел решение. didEndDragging имеет параметр WillDecelerate, который является ложным в ситуации стационарного выпуска.

Проверяя для замедления в DidEndDragging, в сочетании с didEndDecelerating, вы получаете обе ситуации, которые являются завершением прокрутки.

Ответ 7

Я пробовал ответить Эшли Смарт, и это сработало как шарм. Здесь другая идея, используя только scrollViewDidScroll

-(void)scrollViewDidScroll:(UIScrollView *)sender 
{   
    if(self.scrollView_Result.contentOffset.x == self.scrollView_Result.frame.size.width)       {
    // You have reached page 1
    }
}

У меня только две страницы, так что это сработало для меня. Однако, если у вас более одной страницы, это может быть проблематично (вы можете проверить, является ли текущее смещение кратным ширине, но тогда вы не знаете, остановился ли пользователь на 2-й странице или находится на пути к 3-му или больше)

Ответ 8

Быстрая версия принятого ответа:

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
}

Ответ 9

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
}

Ответ 10

Просто разработанное решение для обнаружения при прокрутке готового приложения: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

Он основан на идее отслеживания изменений режимов runloop. И выполняйте блоки по крайней мере через 0,2 секунды после прокрутки.

Это основная идея для отслеживания изменений режима runloop 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 ]];
}

Ответ 11

Повторить (и для новичков). Это не так больно. Просто добавьте протокол, затем добавьте функции, необходимые для обнаружения.

В представлении (классе), который содержит 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

Ответ 12

У меня был случай нажатия и перетаскивания, и я узнал, что перетаскивание вызывало scrollViewDidEndDecelerating

И смещение изменения вручную с кодом ([_scrollView setContentOffset: contentOffset анимированный: 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
}

Ответ 13

Если кому-то нужно, вот ответ Эшли Smart в Swift

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)
    ...
}

Ответ 14

Альтернативой было бы использовать 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")
    }

Ответ 15

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
}
}

Ответ 16

Существует метод UIScrollViewDelegate, который можно использовать для обнаружения (или, лучше сказать, "предсказать" ), когда прокрутка действительно завершена:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)

of 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
}

Ответ 17

Если вы в 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
})

Это будет учитывать как перетаскивание и замедление.