Як виявити, коли UIScrollView закінчила прокрутку


145

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

Інший метод, scrollViewDidEndScrollingAnimationздається, спрацьовує лише тоді, коли ви програматично переміщуєте подання прокрутки, а не якщо користувач прокручує.

Хтось знає про схему виявлення, коли перегляд прокрутки завершив прокрутку?


Дивіться також, якщо ви хочете виявити закінчену прокрутку після прокрутки програмно: stackoverflow.com/questions/2358046/…
— Тревор,

Відповіді:


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

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

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

13
Схоже, це працює лише в тому випадку, коли він сповільнюється. Якщо ви паніруєте повільно, тоді відпустіть, це не викликає didEndDecelerating.
— Шон Кларк Хесс

4
Хоча це офіційний API. Це насправді не завжди працює, як ми очікували. @Ashley Smart дало більш практичне рішення.
— Ді Ву

Працює чудово, і пояснення має сенс
— Стівен Елліотт

Це працює лише для прокрутки, які пов’язані з перетягуванням взаємодії. Якщо ваш прокрутка пов'язана з чимось іншим (наприклад, відкриттям клавіатури або закриттям клавіатури), схоже, вам доведеться виявити подію злом, і scrollViewDidEndScrollingAnimation теж не корисний.
— Aurelien Porte

176

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

Єдина корисна відповідь на SO для моєї проблеми прокрутки. Дякую!
— Ді Ву

4
повинен бути [self performSelector: @selector (scrollViewDidEndScrollingAnimation :) withObject: відправник afterDelay: 0.3]
— Мозковий

1
У 2015 році це все-таки найкраще рішення.
— лукас

2
Я, чесно кажучи, не можу повірити, що я в лютому 2016 року, iOS 9.3, і це все-таки найкраще рішення цієї проблеми. Спасибі, працювали як принадність
— gmogames

1
Ти врятував мене. Найкраще рішення.
— Баран Емре

21

Я думаю, що ви шукаєте scrollViewDidEndDecelerating. Його необов'язковий метод UIScrollViewDelegates:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Повідомляє делегату, що подання прокрутки закінчилось уповільненням руху прокрутки.

UIScrollViewDelegate документація


Е, я відповів таку ж відповідь. :)
— Сувеш Пратапа

До. Дещо криптовано названий, але його саме те, що я шукав. Треба було б краще прочитати документацію. Це був довгий день ...
— Майкл Гейлорд

Цей один лайнер вирішив мою проблему. Швидке виправлення для мене! Дякую.
— Dody Rachmat Wicaksono

20

Для всіх сувоїв, пов’язаних із перетягуванням взаємодій, цього буде достатньо:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    _isScrolling = NO;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        _isScrolling = NO;
    }
}

Тепер, якщо ваш прокручування обумовлений програмним наборомContentOffset / scrollRectVisible (з animated= ТАК або ви, очевидно, знаєте, коли прокрутка закінчилася):

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

Якщо ваш прокрутка пов'язана з чимось іншим (наприклад, відкриттям клавіатури або закриттям клавіатури), схоже, вам доведеться виявити подію злом, оскільки scrollViewDidEndScrollingAnimationце теж не корисно.

Випадок PAGINATED прокрутки:

Тому що, я думаю, Apple застосує криву прискорення, scrollViewDidEndDeceleratingзателефонувавши за кожне перетягування, тому scrollViewDidEndDraggingв цьому випадку не потрібно використовувати .


У вашому випадку болісного перегляду прокрутки є одна проблема: якщо ви випустите прокрутку саме на кінцевій позиції сторінки (коли прокрутка не потрібна), scrollViewDidEndDeceleratingвона не буде викликана
— Shadow Of

19

Це було описано в деяких інших відповідях, але ось (у коді), як поєднувати 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
}

7

Я тільки що знайшов це запитання, яке майже те саме я запитав: Як точно знати, коли зупинка прокрутки UIScrollView припинилась?

Хоча didEndDecelerating працює під час прокрутки, панорамування з нерухомим випуском не реєструється.

Зрештою я знайшов рішення. didEndDragging має параметр WillDecelerate, який є помилковим у стаціонарній ситуації з випуском.

Перевіривши на! Уповільнення в DidEndDragging, у поєднанні з didEndDecelerating, ви отримаєте обидві ситуації, які закінчуються прокруткою.


5

Якщо ви перебуваєте в 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
})

Це враховуватиме як перетягування, так і уповільнення.


Це саме те, що я шукав. Дякую!
— ноном

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

3

Я спробував відповідь Ешлі Смарт, і це спрацювало як шарм. Ось ще одна ідея, використовуючи лише scrollViewDidScroll

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

Я просто мав дві сторінки, щоб це працювало для мене. Однак якщо у вас є більше однієї сторінки, це може бути проблематично (ви можете перевірити, чи є поточне зміщення кратним ширині, але тоді ви б не знали, чи зупинився користувач на 2-й сторінці чи йде на 3-ту або більше)


3

Швидка версія прийнятої відповіді:

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
}

3

Якщо комусь потрібно, ось відповідь Ешлі Смарт у Свіфті

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

2

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

Він заснований на ідеї відстеження змін режимів запуску. І виконуйте блоки принаймні через 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 ]];
}

Виявлення прокрутки Lol у додатку - як у випадку, якщо будь-який з програм UIScrollVIew програми зараз прокручується ... видається марним ...
— Ісаак Керол Вайсберг

@IsaacCarolWeisberg ви можете затримати важкі операції до моменту, коли закінчиться плавне прокручування, щоб зберегти його гладко.
— k06a

важкі операції, ви кажете ... ті, які я виконую в глобальних паралельних чергових відправленнях, ймовірно
— Ісаак Керол Вайсберг

1

Резюмувати (і для новачків). Це не так боляче. Просто додайте протокол, а потім додайте функції, необхідні для виявлення.

У представлення (клас), який містить 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

1

У мене був випадок дій із натисканням та перетягуванням, і я з’ясував, що перетягування викликає scrollViewDidEndDecelerating

І зміщення зміщення вручну за допомогою коду ([_scrollView setContentOffset: contentOffset анімований: ТАК];) викликав 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
}

1

Альтернативою може бути використання, 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")
    }

0

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

0

Існує метод, за допомогою 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
}

0

Використовуючи логіку 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 році.


0

У деяких попередніх версіях iOS (наприклад, iOS 9, 10), scrollViewDidEndDecelerating не буде спрацьовувати, якщо дотик scrollView раптово зупиниться натисканням.

Але в поточній версії (iOS 13) scrollViewDidEndDeceleratingбуде спрацьовувати точно (наскільки я знаю).

Отже, якщо ваша програма також орієнтована на більш ранні версії, можливо, вам знадобиться рішення, як те, яке згадував Ешлі Смарт, або ви можете наступне.


    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".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.