UIScrollView: підкачування горизонтально, прокрутка вертикально?


88

Як я можу змусити a, UIScrollViewв якому виконується підкачка та прокрутка, рухатися лише вертикально або горизонтально в даний момент?

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

Редагувати: щоб бути зрозумілішим, я хотів би дозволити користувачеві прокручувати горизонтально АБО вертикально, але не одночасно.


Ви хочете обмежити подання, яке прокручується ТІЛЬКИ Horiz / Vert, або ви хочете, щоб користувач прокручував Horiz OR Vert, але не обидва одночасно?
Will Hartung,

Чи можете ви зробити мені послугу і змінити заголовок, щоб він виглядав трохи цікавішим і нетривіальнішим? Щось на зразок "UIScrollView: підкачування горизонтально, прокрутка вертикально?"
Андрій Таранцов

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

Відповіді:


117

Треба сказати, що у вас дуже важка ситуація.

Зверніть увагу, що pagingEnabled=YESдля перемикання між сторінками вам потрібно використовувати UIScrollView , але вам потрібно pagingEnabled=NOпрокручувати вертикально.

Існує 2 можливі стратегії. Не знаю, який з них буде працювати / його легше реалізувати, тому спробуйте обидва.

Перший: вкладені 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), так що замість поведінки за замовчуванням він негайно перенаправляє всі події у внутрішній вигляд, і лише коли виявляється значне горизонтальне переміщення, він прокручується.

Як зробити так, щоб RemorsefulScrollView поводився так?

  • Схоже, відключення вертикальної прокрутки та встановлення delaysContentTouchesзначення НІ повинно змусити вкладені UIScrollViews працювати. На жаль, це не так; Здається, UIScrollView виконує додаткову фільтрацію для швидких рухів (які неможливо відключити), так що навіть якщо UIScrollView можна прокручувати лише горизонтально, він завжди буде з'їдати (і ігнорувати) досить швидкі вертикальні рухи.

    Ефект настільки сильний, що вертикальна прокрутка всередині вкладеного подання прокрутки непридатна. (Здається, у вас точно така настройка, тому спробуйте: утримуйте палець протягом 150 мс, а потім рухайте його у вертикальному напрямку - вкладений UIScrollView працює, як очікувалося тоді!)

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

  • Однак вам доведеться перейти touchesBeganдо UIScrollView, оскільки ви хочете, щоб він запам'ятав базову координату для майбутньої горизонтальної прокрутки (якщо пізніше ви вирішите, що це горизонтальна прокрутка). touchesBeganПізніше ви не зможете надіслати до UIScrollView, оскільки ви не можете зберегти touchesаргумент: він містить об’єкти, які будуть мутовані до наступної touchesMovedподії, і ви не зможете відтворити старий стан.

    Тому вам слід негайно перейти touchesBeganдо UIScrollView, але ви будете приховувати touchesMovedвід нього всі подальші події, поки не вирішите прокрутити горизонтально. Немає touchesMovedні в якому разі НЕ прокрутка, так що початковий touchesBeganне буде ніякого шкоди. Але встановіть delaysContentTouchesзначення НІ, щоб ніякі додаткові таймери сюрпризів не заважали.

    (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

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

Єдина прихована фішка, яку я бачу, полягає в тому, що якщо ви додасте до RemorsefulScrollView будь-які дочірні перегляди, які не є UIScrollView, події дотику, які ви пересилаєте дитині, можуть повернутися вам через ланцюжок відповідей, якщо дитина не завжди обробляє дотики, як це робить UIScrollView. Куленепробивна реалізація RemorsefulScrollView захистить від touchesXxxповторного входу .

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

Щоб запобігти прокрутці по діагоналі, спершу спробуйте запам’ятати contentOffset in scrollViewWillBeginDraggingта перевірити та скинути contentOffset всередині, scrollViewDidScrollякщо виявите рух по діагоналі. Ще однією стратегією, яку потрібно спробувати, є скидання contentSize, щоб увімкнути прокрутку лише в одному напрямку, коли ви вирішите, в якому напрямку рухається палець користувача. (UIScrollView здається досить пробачливим щодо возиння з contentSize і contentOffset з методів делегатів.)

Якщо це не працює або призводить до недбалих візуалів , вам доведеться перевизначити touchesBeganі touchesMovedт.д., а не пересилати події діагональних рухів до UIScrollView. (Однак користувальницький досвід у цьому випадку буде неоптимальним, оскільки вам доведеться ігнорувати діагональні рухи, замість того, щоб змушувати їх рухатись в одному напрямку. Якщо ви відчуваєте справжню авантюру, ви можете написати свій власний UITouch, схожий на щось на зразок RevengeTouch. Мета) -C - це звичайний старий C, і в світі немає нічого більш качкоподібного, ніж C; поки ніхто не перевіряє реальний клас об'єктів, чого, я вважаю, ніхто не робить, ви можете зробити будь-який клас схожим на будь-який інший клас. Це відкриває можливість синтезувати будь-які дотики, які ви хочете, з будь-якими координатами, які ви хочете.)

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

Гаразд, цей невеликий нарис став занадто довгим. Я просто все ще користуюся іграми UIScrollView після роботи над ScrollingMadness кілька днів тому.

PS Якщо ви отримуєте якісь - небудь з цих робочих і відчувати себе , як обмін, будь ласка , по електронній пошті мені відповідний код на andreyvit@gmail.com, я б з задоволенням включити його в мою ScrollingMadness хитрощі .

PPS Додавання цього невеликого есе до ScrollingMadness README.


7
Зверніть увагу, що ця вкладена поведінка прокрутки працює нестандартно зараз у SDK, ніяких смішних справ не потрібно
andygeers

1
Поставив +1 коментарям andygeers, а потім зрозумів, що це неточно; ви все ще можете "зламати" звичайний UIScrollView, перетягнувши його по діагоналі від початкової точки, а потім ви "потягнули скролер програти", і він буде ігнорувати спрямовану блокування, поки ви не відпустите і в кінцевому підсумку не знаєте куди.
Калле,

Дякуємо за чудове закулісне пояснення! Я пішов із рішенням Маттіаса Вадмана, яке нижче виглядає трохи менш інвазивним IMO.
Ortwin Gentz

Було б чудово, якби цю відповідь оновили зараз, коли UIScrollView виставляє розпізнавач жестів панорами. (Але, можливо, це виходить за рамки початкової сфери.)
jtbandes

Чи існує можливість оновлення цієї публікації? Чудовий пост і дякую за ваш внесок.
Паван

56

Це працює у мене щоразу ...

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

20
Для тих, хто ще натрапляє на це питання: я думаю, що ця відповідь була розміщена до редагування, яке роз'яснило питання. Це дозволить вам прокручувати лише горизонтально, це не вирішує питання про можливість прокручувати вертикально або горизонтально, але не по діагоналі.
andygeers

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

Відмінно! Але хтось може пояснити, як це працює (встановивши contentSize height на 1)!
Praveen

Це справді працює, але чому і як це працює? Хто-небудь може зрозуміти це?
Marko Francekovic

27

Для таких ледачих, як я:

набір 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;
}

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

Хм ... Насправді, після подальшого розслідування - це працює досить добре, але біда в тому, що він втрачає контроль над напрямком під час фази уповільнення - тому, якщо ви почнете рухати пальцем по діагоналі, то, здається, він рухатиметься лише горизонтально або вертикально до тих пір, поки ви не відпустите, в цей момент він буде на деякий час сповільнюватися в діагональному напрямку
andygeers

@andygeers, це могло б працювати краще, якщо б - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }його змінили на - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { scrollDirection=0; } }та- (void)scrollViewDidEndDecelerating{ scrollDirection=0; }
cduck

Не працював у мене. Встановлення contentOffset в польоті, здається, руйнує поведінку підкачки. Я пішов із рішенням Маттіаса Вадмана.
Ortwin Gentz

13

Можливо, я тут могили, але сьогодні я натрапив на цю посаду, намагаючись вирішити ту саму проблему. Ось моє рішення, схоже, чудово працює.

Я хочу відобразити екранний вміст, але дозволити користувачеві прокрутити до одного з 4 інших екранів, вгору вниз вліво та вправо. У мене є один UIScrollView розміром 320x480 та розміром content 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);
    }
}

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

4

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


2
Для тих, хто ще натрапляє на це питання: я думаю, що ця відповідь була розміщена до редагування, яке роз'яснило питання. Це дозволить вам прокручувати лише горизонтально, це не стосується питання можливості прокрутки вертикально або горизонтально, але не по діагоналі.
andygeers

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. Дякую!
Ortwin Gentz

3

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

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

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

2

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

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

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

2

Для подальшої довідки я спирався на відповідь Андрія Танасова і придумав таке рішення, яке чудово працює.

Спочатку визначте змінну для зберігання 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
        }

    }

}

Сподіваюся, це допомагає.


1

З документів:

"значення за замовчуванням - НІ, це означає, що прокрутка дозволена як в горизонтальному, так і у вертикальному напрямках. Якщо значення ТАК і користувач починає перетягування в одному загальному напрямку (горизонтально або вертикально), режим прокрутки вимикає прокрутку в іншому напрямку. "

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

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


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



0

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


0

Для тих, хто шукає у Свіфті другого рішення @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

Сподіваюся, що корисно для нових бажаючих!


0

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

Примітка. За допомогою цього рішення 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
    }
}

-3

Якщо ви використовуєте конструктор інтерфейсів для проектування свого інтерфейсу - це можна зробити досить просто.

У конструкторі інтерфейсів просто клацніть на UIScrollView і виберіть опцію (в інспекторі), яка обмежує її лише одним способом прокрутки.


1
У IB немає варіантів зробити це. Два варіанти, на які ви можете посилатися, пов’язані з відображенням смуги прокрутки, а не з фактичною прокруткою. Крім того, Блокування напрямку блокує напрямок лише після початку прокрутки.
Sophtware
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.