UIScrollView
페이징 및 스크롤이 설정된 a가 주어진 순간에 수직 또는 수평으로 만 이동 하도록 어떻게 강제 할 수 있습니까?
내 이해는 directionalLockEnabled
속성이 이것을 달성해야하지만 대각선으로 스 와이프하면 모션을 단일 축으로 제한하는 대신보기가 대각선으로 스크롤됩니다.
편집 : 더 명확하게하기 위해 사용자가 가로 또는 세로로 스크롤 할 수 있지만 동시에 둘 다 스크롤 할 수는 없습니다.
UIScrollView
페이징 및 스크롤이 설정된 a가 주어진 순간에 수직 또는 수평으로 만 이동 하도록 어떻게 강제 할 수 있습니까?
내 이해는 directionalLockEnabled
속성이 이것을 달성해야하지만 대각선으로 스 와이프하면 모션을 단일 축으로 제한하는 대신보기가 대각선으로 스크롤됩니다.
편집 : 더 명확하게하기 위해 사용자가 가로 또는 세로로 스크롤 할 수 있지만 동시에 둘 다 스크롤 할 수는 없습니다.
답변:
당신은 매우 힘든 상황에 처해 있습니다.
pagingEnabled=YES
페이지 사이를 전환 하려면 UIScrollView를 사용해야 하지만 pagingEnabled=NO
수직으로 스크롤 해야 합니다.
두 가지 가능한 전략이 있습니다. 어느 것이 작동하는지 / 구현하기가 더 쉽기 때문에 둘 다 시도하십시오.
첫째 : 중첩 된 UIScrollViews. 솔직히,이 일을 한 사람을 아직 보지 못했습니다. 그러나 나는 개인적으로 충분히 노력하지 않았고, 내 연습에 따르면 충분히 노력 하면 UIScrollView가 원하는 것을 할 수 있습니다 .
따라서 전략은 외부 스크롤 뷰는 수평 스크롤 만 처리하고 내부 스크롤 뷰는 수직 스크롤 만 처리하도록하는 것입니다. 이를 수행하려면 UIScrollView가 내부적으로 어떻게 작동하는지 알아야합니다. hitTest
메서드를 재정의 하고 항상 자신을 반환하므로 모든 터치 이벤트가 UIScrollView로 이동합니다. 그런 다음 내부 touchesBegan
, touchesMoved
등이 점검은이 이벤트에 관심이, 그리고 하나 핸들 또는 내부 구성 요소에 전달합니다 않다면.
터치를 처리할지 또는 전달할지 결정하기 위해 UIScrollView는 처음 터치 할 때 타이머를 시작합니다.
150ms 이내에 손가락을 크게 움직이지 않으면 이벤트를 내부보기로 전달합니다.
150ms 이내에 손가락을 크게 움직이면 스크롤이 시작됩니다 (이벤트를 내부보기로 전달하지 않음).
테이블 (스크롤보기의 하위 클래스)을 터치하고 즉시 스크롤을 시작하면 터치 한 행이 강조 표시되지 않습니다.
당신이 경우 하지 150ms의 내 크게 손가락을 이동하고있는 UIScrollView는 내부 뷰에 이벤트를 전달하기 시작하지만, 다음, 당신은 시작 스크롤있는 UIScrollView가 호출에 대해 충분히 손가락을 움직 touchesCancelled
스크롤 내부보기 및 시작에.
표를 터치하고 손가락을 조금 누른 다음 스크롤을 시작하면 터치 한 행이 먼저 강조 표시되지만 나중에 강조 표시되지 않습니다.
이러한 이벤트 순서는 UIScrollView 구성으로 변경할 수 있습니다.
delaysContentTouches
이벤트가 바로 내부 제어로 이동 (하지만 당신은 충분히 당신의 손가락을 이동하는 경우 다음 취소됩니다) - NO이며, 다음에는 타이머가 사용되지 않습니다cancelsTouches
아니오 인 경우 이벤트가 컨트롤로 전송되면 스크롤이 발생하지 않습니다.참고가받을 수있는 UIScrollView 것을 모두를 touchesBegin
, touchesMoved
, touchesEnded
그리고 touchesCanceled
CocoaTouch의 이벤트 (그것 때문에 hitTest
이야기는 이렇게하는). 그런 다음 원하는 경우 내부보기로 전달합니다.
이제 UIScrollView에 대한 모든 것을 알았으므로 동작을 변경할 수 있습니다. 사용자가 뷰를 터치하고 손가락을 움직이기 시작하면 (약간이라도) 뷰가 수직 방향으로 스크롤되기 시작하도록 수직 스크롤링을 선호 할 수 있습니다. 그러나 사용자가 손가락을 가로 방향으로 충분히 멀리 움직이면 세로 스크롤을 취소하고 가로 스크롤을 시작하려고합니다.
외부 UIScrollView를 하위 클래스로 지정 (예 : RemorsefulScrollView 클래스 이름 지정)하여 기본 동작 대신 모든 이벤트를 즉시 내부보기로 전달하고 상당한 수평 이동이 감지 될 때만 스크롤되도록합니다.
RemorsefulScrollView가 그렇게 작동하도록하는 방법은 무엇입니까?
수직 스크롤을 비활성화 delaysContentTouches
하고 NO로 설정 하면 중첩 된 UIScrollView가 작동하게됩니다. 불행히도 그렇지 않습니다. UIScrollView는 빠른 동작 (비활성화 할 수 없음)에 대해 몇 가지 추가 필터링을 수행하는 것처럼 보이므로 UIScrollView가 수평으로 만 스크롤 될 수 있더라도 항상 충분히 빠른 수직 동작을 잡아 먹고 무시합니다.
이 효과가 너무 심해서 중첩 된 스크롤 뷰 내부의 세로 스크롤을 사용할 수 없습니다. (정확히이 설정을 가지고있는 것 같으므로 시도해보십시오. 손가락을 150ms 동안 잡고 수직 방향으로 이동하십시오. 그러면 중첩 된 UIScrollView가 예상대로 작동합니다!)
이는 이벤트 처리에 UIScrollView의 코드를 사용할 수 없음을 의미합니다. RemorsefulScrollView에서 네 가지 터치 처리 방법을 모두 재정의하고 먼저 자체 처리를 수행해야 super
하며 가로 스크롤을 사용하기로 결정한 경우 에만 이벤트를 (UIScrollView) 로 전달 해야합니다.
그러나 당신은 통과해야 touchesBegan
당신이 (당신이 나중에 결정하는 경우는베이스가 미래 수평 스크롤 좌표를 기억하고 싶기 때문에,있는 UIScrollView에 있다 수평 스크롤). 인수를 touchesBegan
저장할 수 없기 때문에 나중에 UIScrollView 로 보낼 수 없습니다. touches
인수는 다음 touchesMoved
이벤트 전에 변경 될 개체를 포함 하며 이전 상태를 재현 할 수 없습니다.
따라서 touchesBegan
UIScrollView에 즉시 전달 해야하지만 touchesMoved
가로로 스크롤하기로 결정할 때까지 추가 이벤트를 숨 깁니다 . 아니오 touchesMoved
는 스크롤이 없음을 의미하므로이 이니셜 touchesBegan
은 해를 끼치 지 않습니다. 그러나 delaysContentTouches
추가 깜짝 타이머가 방해하지 않도록 NO로 설정하십시오 .
(오프 토픽 — 사용자와 달리 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을 기억 하고 대각선 이동이 감지되면 scrollViewWillBeginDragging
내부에서 contentOffset을 확인하고 재설정 scrollViewDidScroll
해야합니다. 시도 할 또 다른 전략은 사용자의 손가락이 이동하는 방향을 결정한 후 한 방향으로 만 스크롤 할 수 있도록 contentSize를 재설정하는 것입니다. (UIScrollView는 델리게이트 메서드에서 contentSize 및 contentOffset을 조작하는 것에 대해 꽤 관대 해 보입니다.)
그 실수 영상에서 작업 중 하나 또는 결과를하지 않는 경우에, 당신은 재정을 가지고 touchesBegan
, touchesMoved
등 및있는 UIScrollView에 전달하지 대각선 이동 이벤트. (그러나이 경우 사용자 경험은 차선책이 될 것입니다. 대각선 이동을 한 방향으로 강제하는 대신 무시해야하기 때문입니다. 정말 모험심을 느끼고 있다면 RevengeTouch와 같은 자신 만의 UITouch 모양을 작성할 수 있습니다. -C는 평범한 오래된 C이고, 세상에서 C보다 더 덕 유형적인 것은 없습니다. 누구도 객체의 실제 클래스를 확인하지 않는 한 누구도하지 않는 한 어떤 클래스도 다른 클래스처럼 보이게 만들 수 있습니다. 원하는 좌표로 원하는 터치를 합성 할 수 있습니다.)
백업 전략 : Three20 라이브러리 에 UIScrollView를 제대로 재 구현 한 TTScrollView가 있습니다 . 불행히도 사용자에게는 매우 부자연스럽고 비음 성적인 느낌이 듭니다. 그러나 UIScrollView를 사용하려는 모든 시도가 실패하면 사용자 지정 코딩 된 스크롤보기로 대체 할 수 있습니다. 나는 가능하다면 반대 할 것을 권합니다. UIScrollView를 사용하면 향후 iPhone OS 버전에서 어떻게 발전하든 상관없이 기본 모양과 느낌을 얻을 수 있습니다.
좋아요,이 작은 에세이가 너무 길어졌습니다. 며칠 전 ScrollingMadness에서 작업 한 후에도 여전히 UIScrollView 게임에 빠져 있습니다.
추신 만약 당신이 이것들 중 하나가 작동하고 공유하고 싶은 느낌이 든다면, andreyvit@gmail.com으로 관련 코드를 저에게 이메일로 보내주십시오. 저는 그것을 제 ScrollingMadness의 트릭 가방에 기꺼이 포함하겠습니다 .
PPS ScrollingMadness README에이 작은 에세이를 추가합니다.
이것은 매번 나를 위해 작동합니다 ...
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);
나 같은 게으른 사람들을 위해 :
설정 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;
}
이 문제를 해결하기 위해 다음 방법을 사용했습니다.
먼저 컨트롤러 헤더 파일에서 다음 변수를 정의하십시오.
CGPoint startPos;
int scrollDirection;
startPos 는 델리게이트가 scrollViewWillBeginDragging 메시지를 수신 할 때 contentOffset 값 을 유지합니다 . 그래서이 방법에서 우리는 이것을합니다.
- (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;
}
}
- (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; }
나는 여기서 무덤을 파고 있을지 모르지만 오늘 같은 문제를 해결하려고 노력 하면서이 게시물을 우연히 발견했습니다. 여기 내 해결책이 있습니다. 잘 작동하는 것 같습니다.
한 화면의 콘텐츠를 표시하고 싶지만 사용자가 왼쪽 및 오른쪽 위의 4 개의 다른 화면 중 하나로 스크롤 할 수 있도록 허용합니다. 320x480 크기와 960x1440 contentSize의 단일 UIScrollView가 있습니다. 콘텐츠 오프셋은 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);
}
}
여러분들, 서브 뷰 높이를 스크롤 뷰 높이 및 콘텐츠 크기 높이와 동일하게 만드는 것만 큼 간단합니다. 그런 다음 수직 스크롤이 비활성화됩니다.
다음은 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
나는 또한이 문제를 해결해야했으며 Andrey Tarantsov의 답변에는 UIScrollViews
작동 방식을 이해하는 데 유용한 정보가 많이 있지만 솔루션이 약간 복잡하다고 생각합니다. 서브 클래 싱이나 터치 포워딩을 포함하지 않는 내 솔루션은 다음과 같습니다.
UIPanGestureRecognizers
각 방향에 대해 다시 하나씩 .UIScrollViews
'panGestureRecognizer 속성과 UIPanGestureRecognizer
UIScrollView의 원하는 스크롤 방향에 수직 인 방향에 해당 하는 더미 사이에 오류 종속성을 만듭니다.UIGestureRecognizer's
-requireGestureRecognizerToFail:
선택기 .-gestureRecognizerShouldBegin:
더미 UIPanGestureRecognizers 의 콜백에서 제스처의 -translationInView : 선택기를 사용하여 팬의 초기 방향을 계산합니다 ( UIPanGestureRecognizers
터치가 팬으로 등록 할만큼 충분히 번역 될 때까지이 델리게이트 콜백을 호출하지 않습니다). 계산 된 방향에 따라 제스처 인식기가 시작되도록 허용하거나 허용하지 않습니다. 그러면 수직 UIScrollView 이동 제스처 인식기가 시작 될지 여부를 제어해야합니다.-gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
더미 UIPanGestureRecognizers
가 스크롤과 함께 실행되는 것을 허용하지 마십시오 (추가하려는 추가 동작이없는 경우).내가 한 일은 뷰 화면 크기의 프레임으로 페이징 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;
}
이것은 나에게 상당히 불안정한 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;
}
향후 참조를 위해 Andrey Tanasov의 답변을 기반으로 잘 작동하는 다음 솔루션을 찾았습니다.
먼저 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
}
}
}
도움이 되었기를 바랍니다.
문서에서 :
"기본값은 NO입니다. 이는 가로 및 세로 방향으로 스크롤이 허용됨을 의미합니다. 값이 YES이고 사용자가 일반적인 한 방향 (가로 또는 세로)으로 끌기 시작하면 스크롤보기가 다른 방향으로 스크롤을 비활성화합니다. "
중요한 부분은 "만약 사용자 가 한 방향으로 드래그를 . 따라서 그들이 대각선으로 드래그하기 시작하면 이것은 시작되지 않습니다.이 문서는 해석에있어서 항상 신뢰할 수있는 것은 아닙니다. 그러나 그것은 당신이보고있는 것과 맞는 것 같습니다.
이것은 실제로 합리적인 것 같습니다. 질문해야합니다. 왜 언제든지 수평 또는 수직으로 제한하고 싶습니까? 아마도 UIScrollView가 당신을위한 도구가 아닐까요?
내보기는 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];
}
}
나를위한 매력처럼 작동합니다!
큰 scrollview가 있고 대각선 스크롤을 제한하려는 경우 http://chandanshetty01.blogspot.in/2012/07/restricting-diagonal-scrolling-in.html
@AndreyTarantsov 두 번째 솔루션에서 Swift를 찾는 사람들에게는 다음과 같은 코드가 있습니다.
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로 설정하십시오.
새로 온 사람들에게 도움이되기를 바랍니다!
scrollViewDidBeginDragging (_ :)을 구현하고 기본 UIPanGestureRecognizer의 속도를 확인하여이 문제를 해결했습니다.
NB :이 솔루션을 사용하면 대각선이었던 모든 팬이 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
}
}