UIScrollView горизонтальный пейджинг, как вкладки Mobile Safari

Mobile Safari позволяет вам переключаться между страницами, введя вид горизонтального представления подкачки UIScrollView с нижним элементом управления страницей.

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

Приведенный Apple пример: PageControl показывает, как использовать UIScrollView для горизонтального поискового вызова, но все представления занимают всю ширину экрана.

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

Ответ 1

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

Затем, поскольку вы хотите увидеть другие страницы, конечно, установите clipsToBounds в NO, как указано в mhjoy. Теперь уловкой становится заставить ее прокручивать, когда пользователь начинает перетаскивание за пределы рамки UIScrollView. Мое решение (когда мне это нужно было сделать совсем недавно) было следующим:

Создайте подкласс UIView (т.е. ClipView), который будет содержать UIScrollView и его подпункты. По существу, он должен иметь структуру того, что вы предположили бы, что UIScrollView будет иметь нормальные обстоятельства. Поместите UIScrollView в центр ClipView. Убедитесь, что для параметра ClipView clipsToBounds установлено значение YES, если его ширина меньше, чем у его родительского представления. Кроме того, для ClipView требуется ссылка на UIScrollView.

Последний шаг - переопределить - (UIView *)hitTest:withEvent: внутри ClipView.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  return [self pointInside:point withEvent:event] ? scrollView : nil;
}

Это в основном расширяет область касания UIScrollView до фрейма его родительского представления, именно то, что вам нужно.

Другим вариантом будет подкласс UIScrollView и переопределить его метод - (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) event, однако для выполнения обрезки вам все равно понадобится представление контейнера, и может быть сложно определить, когда возвращать YES, основываясь только на UIScrollView.

ПРИМЕЧАНИЕ. Вы также должны взглянуть на Juri Pakaste hitTest: withEvent: modify, если у вас возникли проблемы с взаимодействием с пользователем subview.

Ответ 2

Решение ClipView выше работало для меня, но мне пришлось выполнить другую реализацию -[UIView hitTest:withEvent:]. Версия Ed Marty не обеспечивала взаимодействие пользователя с вертикальными прокрутками, которые у меня внутри горизонтального.

Для меня работала следующая версия:

-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* child = nil;
    if ((child = [super hitTest:point withEvent:event]) == self)
        return self.scrollView;     
    return child;
}

Ответ 3

Задайте размер кадра scrollView в качестве размера ваших страниц:

[self.view addSubview:scrollView];
[self.view addGestureRecognizer:mainScrollView.panGestureRecognizer];

Теперь вы можете панорамировать на self.view, а содержимое прокрутки будет прокручиваться.
Также используйте scrollView.clipsToBounds = NO; для предотвращения отсечения содержимого.

Ответ 4

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

Создайте класс CustomScrollView и подкласс UIScrollView. Тогда все, что вам нужно сделать, это добавить это в файл .m:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
  return (point.y >= 0 && point.y <= self.frame.size.height);
}

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

Ответ 5

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

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if ([self pointInside:point withEvent:event]) {
        for (id view in [self subviews]) {
            if ([view isKindOfClass:[UIScrollView class]]) {
                return view;
            }
        }
    }
    return nil;
}

Ответ 6

У меня есть еще одна потенциально полезная модификация для реализации ClipView hitTest. Мне не нравилось предоставлять ссылку на UIScrollView для ClipView. Моя реализация ниже позволяет вам повторно использовать класс ClipView для расширения области тестирования при тестировании, и не нужно указывать ссылку.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (([self pointInside:point withEvent:event]) && (self.subviews.count >= 1))
    {
        // An extended-hit view should only have one sub-view, or make sure the
        // first subview is the one you want to expand the hit test for.
        return [self.subviews objectAtIndex:0];
    }

    return nil;
}

Ответ 7

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

То, что я закончил, - это эмуляция поведения прокрутки, добавив метод ниже к делегату (или UICollectionViewLayout).

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{    
  if (velocity.x > 0) {
    proposedContentOffset.x = ceilf(self.collectionView.contentOffset.x / pageSize) * pageSize;
  }
  else {
    proposedContentOffset.x = floorf(self.collectionView.contentOffset.x / pageSize) * pageSize;
  }

  return proposedContentOffset;
}

Это полностью исключает делегирование действия салфетки, что также является бонусом. UIScrollViewDelegate имеет аналогичный метод под названием scrollViewWillEndDragging:withVelocity:targetContentOffset:, который можно использовать для создания UITableViews и UIScrollViews.

Ответ 8

Включить события срабатывания на дочерние представления прокрутки, поддерживая технику этого вопроса SO. Использует ссылку на просмотр прокрутки (self.scrollView) для удобства чтения.

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = nil;
    NSArray *childViews = [self.scrollView subviews];
    for (NSUInteger i = 0; i < childViews.count; i++) {
        CGRect childFrame = [[childViews objectAtIndex:i] frame];
        CGRect scrollFrame = self.scrollView.frame;
        CGPoint contentOffset = self.scrollView.contentOffset;
        if (childFrame.origin.x + scrollFrame.origin.x < point.x + contentOffset.x &&
            point.x + contentOffset.x < childFrame.origin.x + scrollFrame.origin.x + childFrame.size.width &&
            childFrame.origin.y + scrollFrame.origin.y < point.y + contentOffset.y &&
            point.y + contentOffset.y < childFrame.origin.y + scrollFrame.origin.y + childFrame.size.height
        ){
            hitView = [childViews objectAtIndex:i];
            return hitView;
        }
    }
    hitView = [super hitTest:point withEvent:event];
    if (hitView == self)
        return self.scrollView;
    return hitView;
}

Добавьте это в представление вашего ребенка, чтобы зафиксировать событие касания:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

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

Ответ 9

моя версия Нажатие кнопки на свитке - work =)

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView* child = nil;
    for (int i=0; i<[[[[self subviews] objectAtIndex:0] subviews] count];i++) {

        CGRect myframe =[[[[[self subviews] objectAtIndex:0] subviews]objectAtIndex:i] frame];
        CGRect myframeS =[[[self subviews] objectAtIndex:0] frame];
        CGPoint myscroll =[[[self subviews] objectAtIndex:0] contentOffset];
        if (myframe.origin.x < point.x && point.x < myframe.origin.x+myframe.size.width &&
            myframe.origin.y+myframeS.origin.y < point.y+myscroll.y && point.y+myscroll.y < myframe.origin.y+myframeS.origin.y +myframe.size.height){
            child = [[[[self subviews] objectAtIndex:0] subviews]objectAtIndex:i];
            return child;
        }


    }

    child = [super hitTest:point withEvent:event];
    if (child == self)
        return [[self subviews] objectAtIndex:0];
    return child;
    }

но только [[self subviews] objectAtIndex: 0] должен быть прокруткой

Ответ 10

Вот ответ Swift, основанный на ответе Эд Марти, но также включающий в себя модификацию Юри Пакасте, позволяющую нажимать кнопки и т.д. Внутри прокрутки.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let view = super.hitTest(point, with: event)
    return view == self ? scrollView : view
}