Як виявити, коли 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.