2009-04-07 5 views
77

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

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

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

+0

Вы хотите ограничить просмотр, который будет прокручиваться ТОЛЬКО Horiz/Vert, или вы хотите, чтобы пользователь прокрутил Horiz OR Vert, но не оба одновременно? –

+0

Можете ли вы сделать мне одолжение и изменить название, чтобы оно выглядело немного интереснее и нетривиально? Что-то вроде «UIScrollView: подкачка по горизонтали, прокрутка по вертикали?» –

+0

К сожалению, я разместил вопрос как незарегистрированный пользователь на компьютере, к которому у меня больше нет доступа (до регистрации под тем же именем, когда я вернулся домой), что означает, t отредактируйте его. Это также должно объяснять мне следующий снизу, вместо редактирования исходного вопроса. Извините за нарушение стэкового этикета на этот раз! – Nick

ответ

-1

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

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

+1

В IB нет вариантов для этого. Эти два параметра, которые вы можете иметь в виду, связаны с отображением полосы прокрутки, а не с фактической прокруткой. Кроме того, Direction Lock фиксирует направление только после прокрутки. – Sophtware

1

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

«значением по умолчанию является НЕТ, это означает, что прокрутка разрешена в горизонтальном и вертикальном направлениях Если значение ДА, и пользователь начинает перетаскивание в одном общем направлении (по горизонтали или по вертикали.), просмотр прокрутки отключает прокрутку в другом направлении. "

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

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

112

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

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

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

Первый: Вложенные UIScrollViews. Честно говоря, я еще должен увидеть человека, у которого есть это, чтобы работать. Однако я не очень старался лично, и моя практика показывает, что, когда вы достаточно стараетесь, вы можете make UIScrollView do anything you want.

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

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

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

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

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

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

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

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

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

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

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

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

Как это сделать, чтобы 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's 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, а также проверить и сбросить содержимоеОфис внутри scrollViewDidScroll, если вы обнаружите диагональное движение. Еще одна стратегия - сбросить contentSize, чтобы включить только прокрутку в одном направлении, как только вы решите, в каком направлении движется палец пользователя.(UIScrollView, кажется, довольно снисходителен о возился с contentSize и contentOffset из его методов делегата.)

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

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

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

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

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

+6

Обратите внимание, что это вложенное поведение scrollview работает из коробки теперь в SDK, никакой смешной бизнес не нужен – andygeers

+1

+ 1'd andygeers comment, а затем понял, что это неточно; вы все равно можете «разбить» обычный UIScrollView, перетаскивая по диагонали с начальной точки, а затем вы «вытащили скроллер», и он будет игнорировать направленную блокировку до тех пор, пока вы не отпустите и не закончите, бог знает где. – Kalle

+0

Спасибо за великолепное закулисное объяснение! Я пошел с решением Маттиаса Вадмана, ниже которого выглядит немного менее инвазивная ИМО. –

0

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

1

нужно сбросить _isHorizontalScroll к NO в touchesEnded и touchesCancelled.

13

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

Я хочу отобразить экранный контент, но разрешить пользователю прокручивать один из 4 других экранов вверх и вниз влево и вправо. У меня есть один UIScrollView размером 320x480 и contentSize размером 960x1440. Смещение содержимого начинается с 320,480. В scrollViewDidEndDecelerating :, я перерисовываю 5 представлений (просмотры центра и 4 вокруг него) и сбрасывает смещение содержимого до 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); 
    } 
} 
+0

Возможно, вы обнаружите, что этот код не «замедляется» красиво, когда пользователь позволяет перейти от просмотра прокрутки - он остановится. Если вы не хотите замедления, это может быть хорошо для вас. – andygeers

4

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

+2

Для кого-то другого наткнулся на этот вопрос: я думаю, что этот ответ был опубликован до редактирования, который разъяснил вопрос. Это позволит вам только прокручивать по горизонтали, это не относится к вопросу о возможности прокрутки по вертикали или по горизонтали, но не по диагонали. – andygeers

50

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

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

Для кого-то другого наткнулся на этот вопрос: я думаю, что этот ответ был опубликован до редактирования, который разъяснил вопрос. Это позволит вам * только * прокручивать по горизонтали, это не относится к вопросу о возможности прокрутки по вертикали или по горизонтали, но не по диагонали. – andygeers

+0

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

+0

Отлично! Но может кто-то объяснить, как это работает (настройка contentSize height до 1)! – Praveen

2

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

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

Мое мнение состоит из 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]; 
    } 
} 

работает как шарм для меня!

+1

не понимаю, как это может работать для вас ... вы можете загрузить образец-проект? – hfossli

16

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

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

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; 
    } 
} 
+0

Хм ...На самом деле, после более тщательного исследования - он хорошо работает *, но проблема в том, что он теряет контроль над направлением во время фазы замедления, поэтому, если вы начнете перемещать палец по диагонали, то он будет отображаться только горизонтально или вертикально * до тех пор, пока вы отпускаете *, в этот момент он будет замедляться в диагональном направлении на некоторое время – andygeers

+0

@andygeers, он может работать лучше, если '- (void) scrollViewDidEndDragging: (UIScrollView *) scrollView willDecelerate: (BOOL) замедляет { if (замедляет) { scrollDirection = 3; } } ' было изменено на ' - (ничтожной) scrollViewDidEndDragging: (UIScrollView *) Scrollview willDecelerate: (! Замедлиться) (BOOL) замедлиться { если { scrollDirection = 0; } } 'и ' - (void) scrollViewDidEndDecelerating { scrollDirection = 0; } ' – cduck

+0

Не работал для меня. Установка contentOffset в полете, кажется, разрушает поведение пейджинга. Я пошел с решением Маттиаса Вадмана. –

3

Вот моя реализация страничной 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 
+1

Отлично работает, также на iOS 7. Спасибо! –

2

То, что я сделал, было создать подкачки 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; 
}   
23

Для ленивых как я:

Набор 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; 
} 
+0

Жаль, что я не смог принять этот ответ. – maksa

3

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

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

Для тех, кто в 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 
     } 
    } 

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

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

2

Это улучшенное решение 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; 
} 
1

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

Сначала определит переменное для хранения 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 
     } 

    } 

} 

Надеется, что это помогает.

Смежные вопросы