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

Если все ваши пути к файлам имеют тот же формат, что и ваши примеры, вы можете довольно легко создать столбец даты, выполнив:

update date:"D"$8#'103_'filePaths from filesInDir

Затем сопоставьте свои даты с помощью этого столбца.

84
задан Cœur 27 May 2019 в 17:03
поделиться

10 ответов

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

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

-1
ответ дан zpesk 24 November 2019 в 08:26
поделиться

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

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

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

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

1
ответ дан philsquared 24 November 2019 в 08:26
поделиться

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

116
ответ дан 24 November 2019 в 08:26
поделиться

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

0
ответ дан 24 November 2019 в 08:26
поделиться

Необходимо сбросить _isHorizontalScroll на НЕТ в touchesEnded и touchCancelled .

1
ответ дан 24 November 2019 в 08:26
поделиться

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

Я хочу отобразить на экране содержимое, но позволить пользователю прокручивать его на один из 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);
    }
}
13
ответ дан 24 November 2019 в 08:26
поделиться

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

4
ответ дан 24 November 2019 в 08:26
поделиться

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

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);
55
ответ дан 24 November 2019 в 08:26
поделиться

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

self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);
2
ответ дан 24 November 2019 в 08:26
поделиться

Мое представление состоит из 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
ответ дан 24 November 2019 в 08:26
поделиться