Mobile Safari позволяет вам переключать страницы, вводя своего рода горизонтальное страничное представление UIScrollView с элементом управления страницей внизу.
Я пытаюсь воспроизвести это конкретное поведение, когда горизонтально прокручиваемый UIScrollView показывает часть содержимого следующего представления.
Приведенный Apple пример: PageControl показывает, как использовать UIScrollView для горизонтального разбиения по страницам, но все представления занимают всю ширину экрана.
Как мне заставить UIScrollView показывать некоторый контент следующего представления, как это делает мобильный Safari?
ios
iphone
cocoa-touch
uiscrollview
первый ответчик
источник
источник
Ответы:
А
UIScrollView
с включенным разбиением по страницам остановится на кратной ширине (или высоте) его кадра. Итак, первый шаг - выяснить, какой ширины вы хотите, чтобы ваши страницы были. Сделайте так, чтобы ширинаUIScrollView
. Затем установите размеры вашего subview, какими бы большими они вам ни были, и установите их центры на основе кратнойUIScrollView
ширины.Тогда, так как вы хотите , чтобы увидеть другие страницы, конечно, установить
clipsToBounds
наNO
как указано mhjoy. Уловка теперь заключается в том, чтобы заставить его прокручиваться, когда пользователь начинает перетаскивание за пределы диапазонаUIScrollView
рамки. Мое решение (когда мне пришлось делать это совсем недавно) было следующим:Создайте
UIView
подкласс (т.е.ClipView
), который будет содержатьUIScrollView
подвиды и его подвиды. По сути, он должен иметь те рамки, которые, как вы предполагали,UIScrollView
имели бы при нормальных обстоятельствах. ПоместитеUIScrollView
в центрClipView
. Убедитесь, что дляClipView
'sclipsToBounds
установлено значение,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:, если у вас возникли проблемы с взаимодействием с пользователем в подпредставлении.
источник
UIScrollView
подкласс для отложенной загрузки представлений (inlayoutSubviews
) и использовалclippingRect
дляpointInside:withEvent:
тестирования. Работает очень хорошо и не требует дополнительного представления контейнера.UIScrollView
подкласс как @ohhorob , но с точкой зрения контейнера для вырезки и возвращения[super pointInside:CGPointMake(self.contentOffset.x + self.frame.size.width/2, self.contentOffset.y + self.frame.size.height/2) withEvent:event];
вpointInside:withEvent:
. Это работает очень хорошо, и в ответе @JuriPakaste нет проблем с взаимодействием с пользователем.- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
в виду, что добавление UIScrollViewDelegate в iOS5 означает, что вам больше не нужно повторять эту болтовню.Приведенное
ClipView
выше решение сработало для меня, но мне пришлось-[UIView hitTest:withEvent:]
применить другую реализацию. В версии Эда Марти не было взаимодействия с пользователем, работающего с вертикальными прокрутками, которые у меня есть внутри горизонтального.У меня работала следующая версия:
-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { UIView* child = nil; if ((child = [super hitTest:point withEvent:event]) == self) return self.scrollView; return child; }
источник
Установите размер кадра scrollView, так как размер ваших страниц будет:
[self.view addSubview:scrollView]; [self.view addGestureRecognizer:mainScrollView.panGestureRecognizer];
Теперь вы можете перемещаться
self.view
по экрану, и содержимое scrollView будет прокручиваться.Также используйте,
scrollView.clipsToBounds = NO;
чтобы предотвратить обрезку содержимого.источник
Я сам выбрал пользовательский UIScrollView, поскольку это был самый быстрый и простой метод, который мне показался. Однако я не видел точного кода, поэтому решил поделиться. Мне нужен был UIScrollView с небольшим содержимым, и поэтому сам UIScrollView был маленьким для достижения эффекта разбиения на страницы. Как говорится в сообщении, пролистывать нельзя. Но теперь это возможно.
Создайте класс CustomScrollView и подкласс UIScrollView. Затем все, что вам нужно сделать, это добавить это в файл .m:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return (point.y >= 0 && point.y <= self.frame.size.height); }
Это позволяет прокручивать из стороны в сторону (по горизонтали). Отрегулируйте границы соответствующим образом, чтобы настроить сенсорную область смахивания / прокрутки. Наслаждайтесь!
источник
Я сделал еще одну реализацию, которая может автоматически возвращать 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; }
источник
У меня есть еще одна потенциально полезная модификация реализации 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; }
источник
Я реализовал предложенное выше предложение, но
UICollectionView
я считал, что все, что находится вне кадра, находится за пределами экрана. Из-за этого соседние ячейки отображались за пределами границ только тогда, когда пользователь прокручивал к ним, что было не идеально.В итоге я имитировал поведение scrollview, добавив метод ниже к делегату (или
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.источник
Включите запуск событий касания в дочерних представлениях прокрутки, поддерживая технику этого вопроса 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, но это было слишком большое изменение, чтобы вносить его.)
источник
моя версия Нажатие кнопки лежащей на скролле - работа =)
- (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] должен быть прокруткой
источник
Вот ответ Swift, основанный на ответе Эда Марти, но также включая модификацию Юри Пакасте, позволяющую нажимать кнопки и т.д. внутри прокрутки.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let view = super.hitTest(point, with: event) return view == self ? scrollView : view }
источник