Пошук напрямку прокрутки в UIScrollView?


183

У мене UIScrollViewдозволено лише горизонтальне прокручування, і я хотів би знати, у якому напрямку (ліворуч, праворуч) користувач прокручує. Що я зробив, щоб підкласифікувати UIScrollViewта замінити touchesMovedметод:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];

    UITouch *touch = [touches anyObject];
    float now = [touch locationInView:self].x;
    float before = [touch previousLocationInView:self].x;
    NSLog(@"%f %f", before, now);
    if (now > before){
        right = NO;
        NSLog(@"LEFT");
    }
    else{
        right = YES;
        NSLog(@"RIGHT");

    }

}

Але цей метод іноді взагалі не викликають, коли я рухаюся. Що ти думаєш?


Дивіться мою відповідь нижче - для цього вам слід скористатися делегатами перегляду прокрутки.
пам’ятки

Найкращою відповіддю тут: stackoverflow.com/questions/11262583 / ...
iksnae

Відповіді:


392

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

Для визначення напрямку вам потрібно скористатися UIScrollView scrollViewDidScrollделегатом. У цьому зразку я створив змінну, lastContentOffsetяку я використовую для порівняння поточного зміщення змісту з попереднім. Якщо вона більша, то scrollView прокручується праворуч. Якщо менше, то scrollView прокручує ліворуч:

// somewhere in the private class extension
@property (nonatomic, assign) CGFloat lastContentOffset;

// somewhere in the class implementation
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    ScrollDirection scrollDirection;

    if (self.lastContentOffset > scrollView.contentOffset.x) {
        scrollDirection = ScrollDirectionRight;
    } else if (self.lastContentOffset < scrollView.contentOffset.x) {
        scrollDirection = ScrollDirectionLeft;
    }

    self.lastContentOffset = scrollView.contentOffset.x;

    // do whatever you need to with scrollDirection here.    
}

Я використовую наступний перелік для визначення напрямку. Встановлення першого значення для ScrollDirectionNone має додаткову перевагу - зробити цей напрямок за замовчуванням при ініціалізації змінних:

typedef NS_ENUM(NSInteger, ScrollDirection) {
    ScrollDirectionNone,
    ScrollDirectionRight,
    ScrollDirectionLeft,
    ScrollDirectionUp,
    ScrollDirectionDown,
    ScrollDirectionCrazy,
};

1
Як я можу отримати lastContentOffset
Dev

@JasonZhao Heh - Я отримував дивні результати, коли тестував код по суті, тому що я не врахував, що відскак прокрутки викликає напрямок прокрутки до..er..bounce. Отже, я додав це до перерахунків, коли знайшов несподівані результати.
пам’ятки

1
@Dev Це в кодіlastContentOffset = scrollView.contentOffset.x;
пам’ятки

@ akivag29 Хороший улов на lastContentOffsetтип. Що стосується властивостей та статичних змінних: будь-яке рішення є хорошим вибором. У мене немає реальних уподобань. Це хороший момент.
пам’ятки

2
@JosueEspinosa Щоб отримати правильний напрямок прокрутки, ви помістите його в метод scrollViewWillBeginDragging: метод для останнього виклику програми встановлення lastContentOffset.
strawnut

73

... Я хотів би знати, в якому напрямку (ліворуч, праворуч) користувач прокручує.

У цьому випадку на iOS 5 і вище використовуйте, UIScrollViewDelegateщоб визначити напрямок жесту користувача:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{ 
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
        // handle dragging to the right
    } else {
        // handle dragging to the left
    }
}

1
може бути найкращим рішенням, але при дійсно повільному старті dx буде дорівнює 0.
trickster77777

Звичайно, це і є. Це слід більше оцінювати, оскільки це більше "рідний" або належний спосіб, головним чином тому, що потрібно використовувати будь-які збережені властивості.
Мічі

Хіба це не має тієї ж проблеми, що і stackoverflow.com/a/26192103/62, коли вона не працюватиме належним чином після того, як прокрутка імпульсу починається після того, як користувач перестане панувати?
Лірон Ягдав

59

Використання scrollViewDidScroll: - хороший спосіб знайти поточний напрямок.

Якщо ви хочете знати напрямок після закінчення прокрутки, скористайтеся наступним:

@property (nonatomic) CGFloat lastContentOffset;

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {

    self.lastContentOffset = scrollView.contentOffset.x;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

    if (self.lastContentOffset < scrollView.contentOffset.x) {
        // moved right
    } else if (self.lastContentOffset > scrollView.contentOffset.x) {
        // moved left
    } else {
        // didn't move
    }
}

3
Це чудове доповнення Джастіна, проте я хотів би додати одну редакцію. Якщо у вашому огляді прокрутки ввімкнено підключення сторінки, і ви здійснюєте перетягування, яке закінчується поверненням у початкове положення, поточна умова "ще" вважатиме, що "переміщено ліворуч", хоча воно не повинно бути "без змін".
Скотт Ліберман

1
@ScottLieberman ваше право, я оновив код відповідно.
Джастін Таннер

50

Не потрібно додавати додаткову змінну, щоб відстежувати це. Просто використовуйте властивість UIScrollView', panGestureRecognizerяк це. На жаль, це працює лише в тому випадку, якщо швидкість не 0:

CGFloat yVelocity = [scrollView.panGestureRecognizer velocityInView:scrollView].y;
if (yVelocity < 0) {
    NSLog(@"Up");
} else if (yVelocity > 0) {
    NSLog(@"Down");
} else {
    NSLog(@"Can't determine direction as velocity is 0");
}

Ви можете використовувати комбінацію x і y компонентів для виявлення вгору, вниз, вліво та вправо.


9
на жаль, це просто працює під час перетягування. швидкість дорівнює 0, коли дотики знімаються, навіть під час прокрутки.
heiko

Приємний +1. інша частина - швидкість 0, тому що користувач більше не затягується, тому швидкість жестів 0
Pavan

Ви можете зберігати напрямок у змінній. Змінюйтеся лише при швидкості! = 0. Для мене працює
Лешек Зарна

Працює з scrollViewWillBeginDragging, приголомшливо
demensdeum

40

Рішення

func scrollViewDidScroll(scrollView: UIScrollView) {
     if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
     {
         print("up")
     }
    else
    {
         print("down")
    } 
}

1
Спробуйте спробувати velocityInViewзамість цього translationInView. Це вирішує проблему, яка виникає при зміні напрямку в тому ж русі.
enreas

2
Це рішення працює ідеально. Найбільш прийнята відповідь не працює, коли я переходжу до наступного екрана і повертаюся, виконую прокрутки вгору та вниз.
Аньянейлулу Батула

Найкраще, thnx!
Бухарін

18

Швидкий 4:

Для горизонтальної прокрутки ви можете просто зробити:

if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 {
   print("left")
} else {
   print("right")
}

Для вертикальної прокрутки змініть .xна.y


14

У iOS8 Swift я використав цей метод:

override func scrollViewDidScroll(scrollView: UIScrollView){

    var frame: CGRect = self.photoButton.frame
    var currentLocation = scrollView.contentOffset.y

    if frame.origin.y > currentLocation{
        println("Going up!")
    }else if frame.origin.y < currentLocation{
        println("Going down!")
    }

    frame.origin.y = scrollView.contentOffset.y + scrollHeight
    photoButton.frame = frame
    view.bringSubviewToFront(photoButton)

}

У мене є динамічний вигляд, який змінює місця розташування, коли користувач прокручує так, що вигляд може здатися, що він залишився там же на екрані. Я також відстежую, коли користувач йде вгору чи вниз.

Ось також альтернативний спосіб:

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if targetContentOffset.memory.y < scrollView.contentOffset.y {
        println("Going up!")
    } else {
        println("Going down!")
    }
}

1
Використовувати x замість y?
Esqarrouth

Насправді я хочу виявити це для UiCollectionView. У мене горизонтальна прокрутка. Тож я виявлю crollViewDidEndDeceleratingправильно?
Умаїр Афзал

8

Це те, що працювало для мене (в Objective-C):

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{

        NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)?@"up":@"down";
        NSLog(@"%@",direction);
    }

7
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {

    CGPoint targetPoint = *targetContentOffset;
    CGPoint currentPoint = scrollView.contentOffset;

    if (targetPoint.y > currentPoint.y) {
        NSLog(@"up");
    }
    else {
        NSLog(@"down");
    }
}

2
Цей метод не викликається, коли значення властивості pagingEnabled перегляду прокрутки - YES.
Ако

5

Крім того, можна спостерігати ключовий шлях "contentOffset". Це корисно, коли вам неможливо встановити / змінити делегат подання прокрутки.

[yourScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

Після додавання спостерігача ви могли:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    CGFloat newOffset = [[change objectForKey:@"new"] CGPointValue].y;
    CGFloat oldOffset = [[change objectForKey:@"old"] CGPointValue].y;
    CGFloat diff = newOffset - oldOffset;
    if (diff < 0 ) { //scrolling down
        // do something
    }
}

Не забудьте зняти спостерігача при необхідності. наприклад, ви можете додати спостерігача в viewWillAppear і видалити його в viewWillDisappear


5

Швидко:

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0 {
        print("down")
    } else {
        print("up")
    }
}

Ви можете це зробити також у scrollViewDidScroll.


4

Ось моє рішення для поведінки, як у відповіді @followben, але без втрат з повільним запуском (коли dy 0)

@property (assign, nonatomic) BOOL isFinding;
@property (assign, nonatomic) CGFloat previousOffset;

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    self.isFinding = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (self.isFinding) {
        if (self.previousOffset == 0) {
            self.previousOffset = self.tableView.contentOffset.y;

        } else {
            CGFloat diff = self.tableView.contentOffset.y - self.previousOffset;
            if (diff != 0) {
                self.previousOffset = 0;
                self.isFinding = NO;

                if (diff > 0) {
                    // moved up
                } else {
                    // moved down
                }
            }
        }
    }
}

1

Я перевірив частину відповіді і детально розробив відповідь AnswerBot, загорнувши все по краплі в категорію UIScrollView. "LastContentOffset" зберігається всередині uiscrollview, а потім його лише викликає:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  [scrollView setLastContentOffset:scrollView.contentOffset];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  if (scrollView.scrollDirectionX == ScrollDirectionRight) {
    //Do something with your views etc
  }
  if (scrollView.scrollDirectionY == ScrollDirectionUp) {
    //Do something with your views etc
  }
}

Вихідний код за адресою https://github.com/tehjord/UIScrollViewScrollingDirection


1

Я вважаю за краще зробити фільтрування на основі відповіді @ memmons

В Objective-C:

// in the private class extension
@property (nonatomic, assign) CGFloat lastContentOffset;

// in the class implementation
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    if (fabs(self.lastContentOffset - scrollView.contentOffset.x) > 20 ) {
        self.lastContentOffset = scrollView.contentOffset.x;
    }

    if (self.lastContentOffset > scrollView.contentOffset.x) {
        //  Scroll Direction Left
        //  do what you need to with scrollDirection here.
    } else {
        //  omitted 
        //  if (self.lastContentOffset < scrollView.contentOffset.x)

        //  do what you need to with scrollDirection here.
        //  Scroll Direction Right
    } 
}

При випробуванні на - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

NSLog(@"lastContentOffset: --- %f,   scrollView.contentOffset.x : --- %f", self.lastContentOffset, scrollView.contentOffset.x);

img

self.lastContentOffset змінюється дуже швидко, величина розриву становить майже 0,5f.

Це не обов'язково.

І час від часу, коли ви працюєте в точному стані, ваша орієнтація може загубитися. (заяви про реалізацію іноді пропускаються)

як от :

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    CGFloat viewWidth = scrollView.frame.size.width;

    self.lastContentOffset = scrollView.contentOffset.x;
    // Bad example , needs value filtering

    NSInteger page = scrollView.contentOffset.x / viewWidth;

    if (page == self.images.count + 1 && self.lastContentOffset < scrollView.contentOffset.x ){
          //  Scroll Direction Right
          //  do what you need to with scrollDirection here.
    }
   ....

У Swift 4:

var lastContentOffset: CGFloat = 0

func scrollViewDidScroll(_ scrollView: UIScrollView) {

     if (abs(lastContentOffset - scrollView.contentOffset.x) > 20 ) {
         lastContentOffset = scrollView.contentOffset.x;
     }

     if (lastContentOffset > scrollView.contentOffset.x) {
          //  Scroll Direction Left
          //  do what you need to with scrollDirection here.
     } else {
         //  omitted
         //  if (self.lastContentOffset < scrollView.contentOffset.x)

         //  do what you need to with scrollDirection here.
         //  Scroll Direction Right
    }
}

0

Коли підключення підключення, ви можете використовувати цей код.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    self.lastPage = self.currentPage;
    CGFloat pageWidth = _mainScrollView.frame.size.width;
    self.currentPage = floor((_mainScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
    if (self.lastPage < self.currentPage) {
        //go right
        NSLog(@"right");
    }else if(self.lastPage > self.currentPage){
        //go left
        NSLog(@"left");
    }else if (self.lastPage == self.currentPage){
        //same page
        NSLog(@"same page");
    }
}

0

Коди пояснює себе, я думаю. Різниця CGFloat1 та різниця2 оголошена в приватному інтерфейсі одного класу. Добре, якщо contentSize залишається однаковим.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
        {

        CGFloat contentOffSet = scrollView.contentOffset.y;
        CGFloat contentHeight = scrollView.contentSize.height;

        difference1 = contentHeight - contentOffSet;

        if (difference1 > difference2) {
            NSLog(@"Up");
        }else{
            NSLog(@"Down");
        }

        difference2 = contentHeight - contentOffSet;

       }

0

Гаразд, для мене ця реалізація працює дуже добре:

@property (nonatomic, assign) CGPoint lastContentOffset;


- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    _lastContentOffset.x = scrollView.contentOffset.x;
    _lastContentOffset.y = scrollView.contentOffset.y;

}


- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

    if (_lastContentOffset.x < (int)scrollView.contentOffset.x) {
        // moved right
        NSLog(@"right");
    }
    else if (_lastContentOffset.x > (int)scrollView.contentOffset.x) {
        // moved left
        NSLog(@"left");

    }else if (_lastContentOffset.y<(int)scrollView.contentOffset.y){
        NSLog(@"up");

    }else if (_lastContentOffset.y>(int)scrollView.contentOffset.y){
        NSLog(@"down");
        [self.txtText resignFirstResponder];

    }
}

Таким чином, це запустить textView для відхилення після закінчення перетягування


0
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSLog(@"px %f py %f",velocity.x,velocity.y);}

Використовуйте цей метод делегата прокрутки.

Якщо y координата швидкості дорівнює + ve прокрутка, прокручування прокручується вниз, а якщо вона - -ve scrollview прокручується вгору. Аналогічно прокрутка ліворуч і праворуч може бути виявлена ​​за допомогою x-координати.


0

Коротким і простим було б, просто перевірте значення швидкості, якщо його більше нуля, то його прокрутка ліворуч праворуч:

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

    var targetOffset = Float(targetContentOffset.memory.x)
    println("TargetOffset: \(targetOffset)")
    println(velocity)

    if velocity.x < 0 {
        scrollDirection = -1 //scrolling left
    } else {
        scrollDirection = 1 //scrolling right
    }
}

0

Якщо ви працюєте з UIScrollView і UIPageControl, цей метод також змінить подання сторінки PageControl.

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

    let targetOffset = targetContentOffset.memory.x
    let widthPerPage = scrollView.contentSize.width / CGFloat(pageControl.numberOfPages)

    let currentPage = targetOffset / widthPerPage
    pageControl.currentPage = Int(currentPage)
}

Завдяки @Esq's Swift код.


0

Swift 2.2 Просте рішення, яке відстежує поодинокі та декілька напрямків без втрат.

  // Keep last location with parameter
  var lastLocation:CGPoint = CGPointZero

  // We are using only this function so, we can
  // track each scroll without lose anyone
  override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    let currentLocation = scrollView.contentOffset

    // Add each direction string
    var directionList:[String] = []

    if lastLocation.x < currentLocation.x {
      //print("right")
      directionList.append("Right")
    } else if lastLocation.x > currentLocation.x {
      //print("left")
      directionList.append("Left")
    }

    // there is no "else if" to track both vertical
    // and horizontal direction
    if lastLocation.y < currentLocation.y {
      //print("up")
      directionList.append("Up")
    } else if lastLocation.y > currentLocation.y {
      //print("down")
      directionList.append("Down")
    }

    // scrolled to single direction
    if directionList.count == 1 {
      print("scrolled to \(directionList[0]) direction.")
    } else if directionList.count > 0  { // scrolled to multiple direction
      print("scrolled to \(directionList[0])-\(directionList[1]) direction.")
    }

    // Update last location after check current otherwise,
    // values will be same
    lastLocation = scrollView.contentOffset
  }

0

У всіх верхніх відповідях двома основними способами вирішення проблеми є використання panGestureRecognizerабо contentOffset. У обох методів є свої мінуси і плюси.

Спосіб 1: panGestureRecognizer

Якщо ви використовуєте, panGestureRecognizerяк те, що запропонував @followben, якщо ви не хочете програмно прокручувати подання прокрутки, воно працює належним чином.

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{ 
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
        // handle dragging to the right
    } else {
        // handle dragging to the left
    }
}

Мінуси

Але якщо ви перемістите подання прокрутки із наступним кодом, верхній код не може розпізнати його:

setContentOffset(CGPoint(x: 100, y: 0), animation: false)

Спосіб 2: contentOffset

var lastContentOffset: CGPoint = CGPoint.zero

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if (self.lastContentOffset.x > scrollView.contentOffset.x) {
        // scroll to right
    } else if self.lastContentOffset.x < scrollView.contentOffset.x {
        // scroll to left
    }
    self.lastContentOffset = self.scrollView.contentOffset
}

Мінуси

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


0
//Vertical detection
    var lastVelocityYSign = 0
            func scrollViewDidScroll(_ scrollView: UIScrollView) {
                let currentVelocityY =  scrollView.panGestureRecognizer.velocity(in: scrollView.superview).y
                let currentVelocityYSign = Int(currentVelocityY).signum()
                if currentVelocityYSign != lastVelocityYSign &&
                    currentVelocityYSign != 0 {
                    lastVelocityYSign = currentVelocityYSign
                }
                if lastVelocityYSign < 0 {
                    print("SCROLLING DOWN")
                } else if lastVelocityYSign > 0 {
                    print("SCOLLING UP")
                }
            }

Відповідь від Mos6y https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004

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