UIScrollView: листание по горизонтали, прокрутка по вертикали?

Как я могу заставить UIScrollView включить пейджинг и прокрутку только для перемещения по вертикали или по горизонтали в данный момент?

Я понимаю, что свойство directionalLockEnabled должно достичь этого, но диагональное прокручивание все еще вызывает просмотр прокрутки по диагонали вместо ограничения движения на одну ось.

Изменить: чтобы быть яснее, я хотел бы разрешить пользователю прокручивать по горизонтали или по вертикали, но не оба одновременно.

Ответ 1

Вы должны в очень тяжелой ситуации, я должен сказать.

Обратите внимание, что вам нужно использовать UIScrollView с pagingEnabled=YES для переключения между страницами, но вам нужно pagingEnabled=NO прокручивать по вертикали.

Существует две возможные стратегии. Я не знаю, какой из них будет работать/проще реализовать, поэтому попробуйте оба.

Сначала: вложенные UIScrollViews. Честно говоря, я еще должен увидеть человека, у которого есть это, чтобы работать. Тем не менее, я не очень старался лично, и моя практика показывает, что когда вы достаточно стараетесь, вы можете сделать UIScrollView делать что угодно,.

Таким образом, стратегия заключается в том, чтобы внешний вид прокрутки обрабатывал только горизонтальную прокрутку, а внутренние виды прокрутки обрабатывали только вертикальную прокрутку. Для этого вы должны знать, как работает UIScrollView. Он переопределяет метод hitTest и всегда возвращает себя, так что все события касания переходят в UIScrollView. Затем внутри touchesBegan, touchesMoved и т.д. Он проверяет, заинтересован ли он в событии, и либо обрабатывает, либо передает его внутренним компонентам.

Чтобы решить, нужно ли обрабатывать или пересылать касание, UIScrollView запускает таймер при первом касании:

  • Если вы не перенесли палец значительно в течение 150 мс, он передает событие во внутренний вид.

  • Если вы перенесли палец значительно в течение 150 мс, он начинает прокрутку (и никогда не передает событие во внутренний вид).

    Обратите внимание, что когда вы касаетесь таблицы (которая является подклассом прокрутки) и начинайте прокрутку сразу, строка, которую вы коснулись, никогда не выделяется.

  • Если вы не перенесли палец значительно в течение 150 мс, а UIScrollView начал передавать события во внутренний вид, но затем вы перенесли палец достаточно далеко, чтобы начать прокрутку, UIScrollView вызывает touchesCancelled на внутренней просматривать и запускать прокрутку.

    Обратите внимание, что при касании таблицы немного держите палец, а затем начинайте прокрутку, строка, которую вы коснулись, сначала подсвечивается, а затем выделяется.

Эта последовательность событий может быть изменена с помощью конфигурации UIScrollView:

  • Если delaysContentTouches нет, то таймер не используется - события немедленно переходят во внутренний элемент управления (но затем отменяются, если вы перемещаете палец достаточно далеко).
  • Если cancelsTouches НЕТ, то, как только события будут отправлены в элемент управления, прокрутки никогда не произойдет.

Обратите внимание, что это UIScrollView, который получает все события touchesBegin, touchesMoved, touchesEnded и touchesCanceled из CocoaTouch (потому что его hitTest говорит ему об этом). Затем он пересылает их во внутренний вид, если захочет, до тех пор, пока он хочет.

Теперь, когда вы знаете все о UIScrollView, вы можете изменить его поведение. Я могу поспорить, что вы хотите отдать предпочтение вертикальной прокрутке, так что, как только пользователь коснется представления и начнет перемещать свой палец (даже слегка), вид начинает прокручиваться в вертикальном направлении; но когда пользователь перемещает палец в горизонтальном направлении достаточно далеко, вы хотите отменить вертикальную прокрутку и начать горизонтальную прокрутку.

Вы хотите подклассифицировать внешний UIScrollView (например, вы назовете свой класс RemorsefulScrollView), так что вместо поведения по умолчанию он сразу пересылает все события во внутренний вид и только когда обнаружено значительное горизонтальное движение, оно прокручивается.

Как сделать make RemorsefulScrollView таким образом?

  • Похоже на отмену вертикальной прокрутки и установку delaysContentTouches на NO, чтобы работать с вложенными UIScrollViews. К сожалению, это не так; UIScrollView, похоже, выполняет некоторую дополнительную фильтрацию для быстрых движений (которые не могут быть отключены), так что даже если UIScrollView можно прокручивать только по горизонтали, он всегда будет достаточно быстро (и игнорировать) вертикальные движения.

    Эффект настолько серьезен, что вертикальная прокрутка внутри вложенного вида прокрутки непригодна. (Кажется, у вас есть именно эта настройка, поэтому попробуйте: держите палец на 150 мс, а затем переместите его в вертикальном направлении - вложенный UIScrollView работает так, как ожидалось!)

  • Это означает, что вы не можете использовать код UIScrollView для обработки событий; вы должны переопределить все четыре метода обработки касания в RemorsefulScrollView и сначала выполнить свою собственную обработку, только переадресовывая событие в super (UIScrollView), если вы решили пойти с горизонтальной прокруткой.

  • Однако вам нужно передать touchesBegan в UIScrollView, потому что вы хотите, чтобы он запоминал базовую координату для будущей горизонтальной прокрутки (если позже вы решите, что это горизонтальная прокрутка). Вы не сможете отправить touchesBegan в UIScrollView позже, потому что вы не можете сохранить аргумент touches: он содержит объекты, которые будут мутированы перед следующим событием touchesMoved, и вы не сможете воспроизвести старое состояние.

    Итак, вы должны передать touchesBegan в UIScrollView немедленно, но вы будете скрывать от него еще touchesMoved события, пока вы не решите прокручивать по горизонтали. Нет touchesMoved означает отсутствие прокрутки, поэтому этот начальный touchesBegan не повредит. Но установите delaysContentTouches на NO, чтобы никакие дополнительные таймеры не мешали.

    (Offtopic - в отличие от вас, UIScrollView может корректно хранить штрихи и может воспроизводить и пересылать исходное событие touchesBegan позже. У него есть несправедливое преимущество использования неопубликованных API-интерфейсов, поэтому они могут клонировать объекты касания, прежде чем они будут мутированы.)

  • Учитывая, что вы всегда пересылаете touchesBegan, вам также нужно переслать touchesCancelled и touchesEnded. Однако вам нужно превратить touchesEnded в touchesCancelled, поскольку UIScrollView интерпретирует последовательность touchesBegan, touchesEnded как сенсорный щелчок и пересылает ее во внутреннее представление. Вы уже сами отправляете правильные события, поэтому никогда не хотите, чтобы UIScrollView перенаправлял что-либо.

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

// RemorsefulScrollView.h

@interface RemorsefulScrollView : UIScrollView {
  CGPoint _originalPoint;
  BOOL _isHorizontalScroll, _isMultitouch;
  UIView *_currentChild;
}
@end

// RemorsefulScrollView.m

// the numbers from an example in Apple docs, may need to tune them
#define kThresholdX 12.0f
#define kThresholdY 4.0f

@implementation RemorsefulScrollView

- (id)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (id)initWithCoder:(NSCoder *)coder {
  if (self = [super initWithCoder:coder]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (UIView *)honestHitTest:(CGPoint)point withEvent:(UIEvent *)event {
  UIView *result = nil;
  for (UIView *child in self.subviews)
    if ([child pointInside:point withEvent:event])
      if ((result = [child hitTest:point withEvent:event]) != nil)
        break;
  return result;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event]; // always forward touchesBegan -- there no way to forward it later
    if (_isHorizontalScroll)
      return; // UIScrollView is in charge now
    if ([touches count] == [[event touchesForView:self] count]) { // initial touch
      _originalPoint = [[touches anyObject] locationInView:self];
    _currentChild = [self honestHitTest:_originalPoint withEvent:event];
    _isMultitouch = NO;
    }
  _isMultitouch |= ([[event touchesForView:self] count] > 1);
  [_currentChild touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  if (!_isHorizontalScroll && !_isMultitouch) {
    CGPoint point = [[touches anyObject] locationInView:self];
    if (fabsf(_originalPoint.x - point.x) > kThresholdX && fabsf(_originalPoint.y - point.y) < kThresholdY) {
      _isHorizontalScroll = YES;
      [_currentChild touchesCancelled:[event touchesForView:self] withEvent:event]
    }
  }
  if (_isHorizontalScroll)
    [super touchesMoved:touches withEvent:event]; // UIScrollView only kicks in on horizontal scroll
  else
    [_currentChild touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  if (_isHorizontalScroll)
    [super touchesEnded:touches withEvent:event];
    else {
    [super touchesCancelled:touches withEvent:event];
    [_currentChild touchesEnded:touches withEvent:event];
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  [super touchesCancelled:touches withEvent:event];
  if (!_isHorizontalScroll)
    [_currentChild touchesCancelled:touches withEvent:event];
}

@end

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

Единственный скрытый улов, который я вижу, заключается в том, что если вы добавите дочерние представления, отличные от UIScrollView, в RemorsefulScrollView, события касания, которые вы пересылаете ребенку, могут вернуться к вам через цепочку ответчиков, если ребенок не всегда обрабатывает такие касания, как UIScrollView делает. Пуленепробиваемая реализация RemorsefulScrollView защитит от повторного запуска touchesXxx.

Вторая стратегия: Если по какой-то причине вложенные UIScrollViews не работают или слишком сложно получить право, вы можете попытаться договориться только с одним UIScrollView, переключив его свойство pagingEnabled на муха из вашего метода делегата scrollViewDidScroll.

Чтобы предотвратить диагональную прокрутку, сначала попробуйте запомнить contentOffset в scrollViewWillBeginDragging, а также проверить и сбросить содержимоеOffset внутри scrollViewDidScroll, если вы обнаружите диагональное движение. Еще одна стратегия - reset contentSize, чтобы включить только прокрутку в одном направлении, как только вы решите, в каком направлении движется палец пользователя. (UIScrollView кажется довольно прощающим о том, как возиться с contentSize и contentOffset по его методам делегатов.)

Если это не работает или приводит к неаккуратным визуальным эффектам, вы должны переопределить touchesBegan, touchesMoved и т.д., а не пересылать диагональные события движения в UIScrollView. (В этом случае пользовательский опыт будет субоптимальным, потому что вам придется игнорировать диагональные движения, а не форсировать их в одном направлении. Если вы чувствуете себя очень авантюрно, вы можете написать свой собственный UITouch-альбом, похожий на RevengeTouch. Objective-C является обычным старым C, и в мире нет ничего более глупого, чем C; до тех пор, пока никто не проверяет реальный класс объектов, что, как я считаю, никто не делает, вы можете сделать любой класс похожим на любой другой класс. возможность синтезировать любые штрихи, которые вы хотите, с любыми координатами, которые вы хотите.)

Стратегия резервного копирования: есть TTScrollView, разумная переоценка UIScrollView в библиотека Three20. К сожалению, он чувствует себя очень неестественным и не-iphonish для пользователя. Но если каждая попытка использования UIScrollView терпит неудачу, вы можете вернуться к пользовательскому кодированному представлению прокрутки. Я рекомендую против этого, если это вообще возможно; использование UIScrollView гарантирует, что вы получите собственный внешний вид, независимо от того, как он развивается в будущих версиях ОС iPhone.

Хорошо, это небольшое эссе получилось немного слишком длинным. Я просто все еще в играх UIScrollView после работы над ScrollingMadness несколько дней назад.

P.S. Если вы получите какую-либо из этих работ и почувствуете, что хотите поделиться, напишите мне соответствующий код на [email protected], я бы с радостью включил его в мой ScrollingMadness мешок трюков.

P.P.S. Добавление этого небольшого эссе в ScrollingMadness README.

Ответ 2

Это работает для меня каждый раз...

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);

Ответ 3

Для ленивых людей, таких как я:

Установите scrollview.directionalLockEnabled в YES

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

- (void) scrollViewDidScroll: (UIScrollView *) scrollView
{
    if (scrollView.contentOffset.x != self.oldContentOffset.x)
    {
        scrollView.pagingEnabled = YES;
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                               self.oldContentOffset.y);
    }
    else
    {
        scrollView.pagingEnabled = NO;
    }
}

- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

Ответ 4

Я использовал следующий метод для решения этой проблемы, надеюсь, что это вам поможет.

Сначала определите следующие переменные в файле заголовков контроллеров.

CGPoint startPos;
int     scrollDirection;

startPos сохранит значение contentOffset, когда ваш делегат получит сообщение scrollViewWillBeginDragging. Итак, в этом методе мы делаем это:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    startPos = scrollView.contentOffset;
    scrollDirection=0;
}

то мы используем эти значения для определения направления движения прокрутки в сообщении scrollViewDidScroll.

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

   if (scrollDirection==0){//we need to determine direction
       //use the difference between positions to determine the direction.
       if (abs(startPos.x-scrollView.contentOffset.x)<abs(startPos.y-scrollView.contentOffset.y)){          
          NSLog(@"Vertical Scrolling");
          scrollDirection=1;
       } else {
          NSLog(@"Horitonzal Scrolling");
          scrollDirection=2;
       }
    }
//Update scroll position of the scrollview according to detected direction.     
    if (scrollDirection==1) {
       [scrollView setContentOffset:CGPointMake(startPos.x,scrollView.contentOffset.y) animated:NO];
    } else if (scrollDirection==2){
       [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x,startPos.y) animated:NO];
    }
 }

Наконец, мы должны остановить все операции обновления при перетаскивании конечного пользователя;

 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if (decelerate) {
       scrollDirection=3;
    }
 }

Ответ 5

Я могу быть здесь могильщиком, но сегодня наткнулся на это сообщение, когда пытался решить ту же проблему. Здесь мое решение, кажется, отлично работает.

Я хочу отображать экранный контент, но позволяю пользователю прокручивать один из 4 других экранов, вверх и вниз влево и вправо. У меня есть один UIScrollView размером 320x480 и contentSize размером 960x1440. Смещение содержимого начинается с 320,480. В scrollViewDidEndDecelerating:, я перерисовываю 5 видов (в центре и 4 вокруг него) и reset смещение содержимого до 320 480.

Здесь мясо моего решения, метод scrollViewDidScroll:.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    if (!scrollingVertically && ! scrollingHorizontally)
    {
        int xDiff = abs(scrollView.contentOffset.x - 320);
        int yDiff = abs(scrollView.contentOffset.y - 480);

        if (xDiff > yDiff)
        {
            scrollingHorizontally = YES;
        }
        else if (xDiff < yDiff)
        {
            scrollingVertically = YES;
        }
    }

    if (scrollingHorizontally)
    {
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, 480);
    }
    else if (scrollingVertically)
    {
        scrollView.contentOffset = CGPointMake(320, scrollView.contentOffset.y);
    }
}

Ответ 6

Ребята это так же просто, как сделать высоту подзадача такой же, как высота просмотра прокрутки и высота размера содержимого. Затем вертикальная прокрутка отключена.

Ответ 7

Вот моя реализация страничного UIScrollView, который разрешает только ортогональные свитки. Обязательно установите pagingEnabled на YES.

PagedOrthoScrollView.h

@interface PagedOrthoScrollView : UIScrollView
@end

PagedOrthoScrollView.m

#import "PagedOrthoScrollView.h"

typedef enum {
  PagedOrthoScrollViewLockDirectionNone,
  PagedOrthoScrollViewLockDirectionVertical,
  PagedOrthoScrollViewLockDirectionHorizontal
} PagedOrthoScrollViewLockDirection; 

@interface PagedOrthoScrollView ()
@property(nonatomic, assign) PagedOrthoScrollViewLockDirection dirLock;
@property(nonatomic, assign) CGFloat valueLock;
@end

@implementation PagedOrthoScrollView
@synthesize dirLock;
@synthesize valueLock;

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self == nil) {
    return self;
  }

  self.dirLock = PagedOrthoScrollViewLockDirectionNone;
  self.valueLock = 0;

  return self;
}

- (void)setBounds:(CGRect)bounds {
  int mx, my;

  if (self.dirLock == PagedOrthoScrollViewLockDirectionNone) {
    // is on even page coordinates, set dir lock and lock value to closest page 
    mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
    if (mx != 0) {
      self.dirLock = PagedOrthoScrollViewLockDirectionHorizontal;
      self.valueLock = (round(CGRectGetMinY(bounds) / CGRectGetHeight(self.bounds)) *
                        CGRectGetHeight(self.bounds));
    } else {
      self.dirLock = PagedOrthoScrollViewLockDirectionVertical;
      self.valueLock = (round(CGRectGetMinX(bounds) / CGRectGetWidth(self.bounds)) *
                        CGRectGetWidth(self.bounds));
    }

    // show only possible scroll indicator
    self.showsVerticalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionVertical;
    self.showsHorizontalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionHorizontal;
  }

  if (self.dirLock == PagedOrthoScrollViewLockDirectionHorizontal) {
    bounds.origin.y = self.valueLock;
  } else {
    bounds.origin.x = self.valueLock;
  }

  mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
  my = abs((int)CGRectGetMinY(bounds) % (int)CGRectGetHeight(self.bounds));

  if (mx == 0 && my == 0) {
    // is on even page coordinates, reset lock
    self.dirLock = PagedOrthoScrollViewLockDirectionNone;
  }

  [super setBounds:bounds];
}

@end

Ответ 8

Мне также пришлось решить эту проблему, и в то время как у Андрея Таранцова определенно есть много полезной информации для понимания того, как UIScrollViews работает, я чувствую, что решение немного сложнее. Мое решение, которое не связано с подклассом или касанием, выглядит следующим образом:

  • Создайте два вложенных вида прокрутки, по одному для каждого направления.
  • Создайте два фиктивных UIPanGestureRecognizers, снова один для каждого направления.
  • Создайте зависимости от свойств UIScrollViews 'panGestureRecognizer и манекен UIPanGestureRecognizer, соответствующие направлению, перпендикулярному вашему желаемому направлению прокрутки UIScrollView, с помощью селектора UIGestureRecognizer's -requireGestureRecognizerToFail:.
  • В обратных вызовах -gestureRecognizerShouldBegin: для ваших фиктивных идентификаторов UIPanGestureRecognizers вычислите начальное направление панорамирования с помощью жестов -translationInView: selector (ваш UIPanGestureRecognizers не будет вызывать этот обратный вызов делегата, пока ваш прикосновение не переведет достаточно, чтобы зарегистрироваться как кастрюля). Разрешить или запретить распознавание вашего жестов начинать в зависимости от вычисленного направления, которое, в свою очередь, должно контролировать, будет ли разрешено начинать перпендикулярный распознаватель жестов UIScrollView.
  • В -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: не допускайте, чтобы ваш манекен UIPanGestureRecognizers выполнялся вместе с прокруткой (если у вас нет дополнительного поведения, которое вы хотели добавить).

Ответ 9

Спасибо Tonetel. Я немного изменил ваш подход, но это было именно то, что мне нужно было для предотвращения горизонтальной прокрутки.

self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);

Ответ 10

То, что я сделал, это создать пейджинговый UIScrollView с фреймом размером экрана просмотра и установить размер содержимого в ширину, необходимую для всего моего контента, и высоту 1 (спасибо Tonetel). Затем я создал меню страниц UIScrollView с кадрами, установленными на каждой странице, размером экрана просмотра и задал размер содержимого каждого по ширине экрана и необходимую высоту для каждого из них. Затем я просто добавил каждую страницу меню в просмотр подкачки. Убедитесь, что пейджинг включен в просмотре прокрутки пейджинга, но отключен в представлениях меню (должен быть отключен по умолчанию), и вам должно быть хорошо идти.

Прокрутка пейджинга теперь прокручивается горизонтально, но не вертикально из-за высоты содержимого. Каждая страница прокручивается по вертикали, но не по горизонтали, из-за ограничений по размеру содержимого.

Здесь код, если это объяснение оставляет желать лучшего:

UIScrollView *newPagingScrollView =  [[UIScrollView alloc] initWithFrame:self.view.bounds]; 
[newPagingScrollView setPagingEnabled:YES];
[newPagingScrollView setShowsVerticalScrollIndicator:NO];
[newPagingScrollView setShowsHorizontalScrollIndicator:NO];
[newPagingScrollView setDelegate:self];
[newPagingScrollView setContentSize:CGSizeMake(self.view.bounds.size.width * NumberOfDetailPages, 1)];
[self.view addSubview:newPagingScrollView];

float pageX = 0;

for (int i = 0; i < NumberOfDetailPages; i++)
{               
    CGRect pageFrame = (CGRect) 
    {
        .origin = CGPointMake(pageX, pagingScrollView.bounds.origin.y), 
        .size = pagingScrollView.bounds.size
    };
    UIScrollView *newPage = [self createNewPageFromIndex:i ToPageFrame:pageFrame]; // newPage.contentSize custom set in here        
    [pagingScrollView addSubview:newPage];

    pageX += pageFrame.size.width;  
}           

Ответ 11

Это улучшенное решение icompot, которое для меня было довольно неустойчивым. Это прекрасно работает и легко реализуется:

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

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

    float XOffset = fabsf(self.oldContentOffset.x - scrollView.contentOffset.x );
    float YOffset = fabsf(self.oldContentOffset.y - scrollView.contentOffset.y );

        if (scrollView.contentOffset.x != self.oldContentOffset.x && (XOffset >= YOffset) )
        {

            scrollView.pagingEnabled = YES;
            scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                                   self.oldContentOffset.y);

        }
        else
        {
            scrollView.pagingEnabled = NO;
               scrollView.contentOffset = CGPointMake( self.oldContentOffset.x,
                                                   scrollView.contentOffset.y);
        }

}


- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

Ответ 12

В будущем, я построил на Андрея Танасова ответ и придумал следующее решение, которое прекрасно работает.

Сначала определите переменную для хранения contentOffset и установите ее на 0,0 координаты

var positionScroll = CGPointMake(0, 0)

Теперь выполните в делегате scrollView следующее:

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    // Note that we are getting a reference to self.scrollView and not the scrollView referenced passed by the method
    // For some reason, not doing this fails to get the logic to work
    self.positionScroll = (self.scrollView?.contentOffset)!
}

override func scrollViewDidScroll(scrollView: UIScrollView) {

    // Check that contentOffset is not nil
    // We do this because the scrollViewDidScroll method is called on viewDidLoad
    if self.scrollView?.contentOffset != nil {

        // The user wants to scroll on the X axis
        if self.scrollView?.contentOffset.x > self.positionScroll.x || self.scrollView?.contentOffset.x < self.positionScroll.x {

            // Reset the Y position of the scrollView to what it was BEFORE scrolling started
            self.scrollView?.contentOffset = CGPointMake((self.scrollView?.contentOffset.x)!, self.positionScroll.y);
            self.scrollView?.pagingEnabled = true
        }

        // The user wants to scroll on the Y axis
        else {
            // Reset the X position of the scrollView to what it was BEFORE scrolling started
            self.scrollView?.contentOffset = CGPointMake(self.positionScroll.x, (self.scrollView?.contentOffset.y)!);
            self.collectionView?.pagingEnabled = false
        }

    }

}

Надеюсь, что это поможет.

Ответ 13

Из документов:

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

Я думаю, что важной частью является "если... пользователь начинает таскать в одном общем направлении". Поэтому, если они начнут таскать по диагонали, это не ударит. Не то, чтобы эти документы всегда были надежными, когда дело доходило до интерпретации, - но это похоже на то, что вы видите.

Это действительно кажется разумным. Я должен спросить - почему вы хотите в любое время ограничиться только горизонтальным или вертикальным? Возможно, UIScrollView не является для вас инструментом?

Ответ 14

Требуется reset _isHorizontalScroll для NO в touchesEnded и touchesCancelled.

Ответ 15

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


1.0   2.0   3.1    4.1
      2.1   3.1
      2.2
      2.3

С поддержкой пейджинга на горизонтальной и вертикальной оси

Вид также имеет следующие атрибуты:

[self setDelaysContentTouches:NO];
[self setMultipleTouchEnabled:NO];
[self setDirectionalLockEnabled:YES];

Теперь для предотвращения диагональной прокрутки выполните следующее:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
    int pageWidth = 768;
    int pageHeight = 1024;
    int subPage =round(self.contentOffset.y / pageHeight);
    if ((int)self.contentOffset.x % pageWidth != 0 && (int)self.contentOffset.y % pageHeight != 0) {
        [self setContentOffset:CGPointMake(self.contentOffset.x, subPage * pageHeight];
    } 
}

Работает как чары для меня!

Ответ 17

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

Ответ 18

Для тех, кто смотрит в Swift на @AndreyTarantsov второе решение, похожее на это в коде:

    var positionScroll:CGFloat = 0

    func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        positionScroll = self.theScroll.contentOffset.x
    }

    func scrollViewDidScroll(scrollView: UIScrollView) {
        if self.theScroll.contentOffset.x > self.positionScroll || self.theScroll.contentOffset.x < self.positionScroll{
             self.theScroll.pagingEnabled = true
        }else{
            self.theScroll.pagingEnabled = false
        }
    }

то, что мы делаем здесь, заключается в том, чтобы сохранить текущую позицию непосредственно перед перетаскиванием прокрутки, а затем проверить, увеличилось или уменьшилось или x по сравнению с текущей позицией x. Если да, то установите для pagingEnabled значение true, если нет (y увеличилось/уменьшилось), то установите для pagingEnabled значение false

Надеюсь, что это полезно для новых пользователей!

Ответ 19

Мне удалось решить эту проблему, реализовав scrollViewDidBeginDragging (_ :) и посмотрев на скорость в базовом UIPanGestureRecognizer.

NB. При таком решении scrollView игнорирует каждый панорамирование, которое было бы диагональным.

override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    super.scrollViewWillBeginDragging(scrollView)
    let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
    if velocity.x != 0 && velocity.y != 0 {
        scrollView.panGestureRecognizer.isEnabled = false
        scrollView.panGestureRecognizer.isEnabled = true
    }
}

Ответ 20

Если вы используете конструктор интерфейса для разработки интерфейса - это можно сделать довольно легко.

В построителе интерфейса просто нажмите на UIScrollView и выберите параметр (в инспекторе), который ограничивает его только прокруткой в ​​один проход.