UIScrollView : 수평으로 페이징, 수직으로 스크롤?


89

UIScrollView페이징 및 스크롤이 설정된 a가 주어진 순간에 수직 또는 수평으로 만 이동 하도록 어떻게 강제 할 수 있습니까?

내 이해는 directionalLockEnabled속성이 이것을 달성해야하지만 대각선으로 스 와이프하면 모션을 단일 축으로 제한하는 대신보기가 대각선으로 스크롤됩니다.

편집 : 더 명확하게하기 위해 사용자가 가로 또는 세로로 스크롤 할 수 있지만 동시에 둘 다 스크롤 할 수는 없습니다.


보기를 Horiz / Vert로만 스크롤하도록 제한 하시겠습니까? 아니면 사용자가 Horiz 또는 Vert를 스크롤하되 동시에 둘 다 스크롤하지 않도록 하시겠습니까?
Will Hartung

좀 더 흥미롭고 사소하지 않게 보이도록 부탁하고 제목을 변경할 수 있습니까? "UIScrollView : 수평으로 페이징, 수직으로 스크롤?"
Andrey Tarantsov

안타깝게도 더 이상 액세스 할 수없는 컴퓨터에 등록되지 않은 사용자로 질문을 게시했습니다 (집에 도착했을 때 같은 이름으로 등록하기 전). 즉, 편집 할 수 없음을 의미합니다. 이것은 또한 원래 질문을 편집하는 대신 후속 작업을 설명해야합니다. 이번에는 스택 에티켓을 어겨서 죄송합니다!
Nick

답변:


117

당신은 매우 힘든 상황에 처해 있습니다.

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그리고 touchesCanceledCocoaTouch의 이벤트 (그것 때문에 hitTest이야기는 이렇게하는). 그런 다음 원하는 경우 내부보기로 전달합니다.

이제 UIScrollView에 대한 모든 것을 알았으므로 동작을 변경할 수 있습니다. 사용자가 뷰를 터치하고 손가락을 움직이기 시작하면 (약간이라도) 뷰가 수직 방향으로 스크롤되기 시작하도록 수직 스크롤링을 선호 할 수 있습니다. 그러나 사용자가 손가락을 가로 방향으로 충분히 멀리 움직이면 세로 스크롤을 취소하고 가로 스크롤을 시작하려고합니다.

외부 UIScrollView를 하위 클래스로 지정 (예 : RemorsefulScrollView 클래스 이름 지정)하여 기본 동작 대신 모든 이벤트를 즉시 내부보기로 전달하고 상당한 수평 이동이 감지 될 때만 스크롤되도록합니다.

RemorsefulScrollView가 그렇게 작동하도록하는 방법은 무엇입니까?

  • 수직 스크롤을 비활성화 delaysContentTouches하고 NO로 설정 하면 중첩 된 UIScrollView가 작동하게됩니다. 불행히도 그렇지 않습니다. UIScrollView는 빠른 동작 (비활성화 할 수 없음)에 대해 몇 가지 추가 필터링을 수행하는 것처럼 보이므로 UIScrollView가 수평으로 만 스크롤 될 수 있더라도 항상 충분히 빠른 수직 동작을 잡아 먹고 무시합니다.

    이 효과가 너무 심해서 중첩 된 스크롤 뷰 내부의 세로 스크롤을 사용할 수 없습니다. (정확히이 설정을 가지고있는 것 같으므로 시도해보십시오. 손가락을 150ms 동안 잡고 수직 방향으로 이동하십시오. 그러면 중첩 된 UIScrollView가 예상대로 작동합니다!)

  • 이는 이벤트 처리에 UIScrollView의 코드를 사용할 수 없음을 의미합니다. RemorsefulScrollView에서 네 가지 터치 처리 방법을 모두 재정의하고 먼저 자체 처리를 수행해야 super하며 가로 스크롤을 사용하기로 결정한 경우 에만 이벤트를 (UIScrollView) 로 전달 해야합니다.

  • 그러나 당신은 통과해야 touchesBegan당신이 (당신이 나중에 결정하는 경우는베이스가 미래 수평 스크롤 좌표를 기억하고 싶기 때문에,있는 UIScrollView에 있다 수평 스크롤). 인수를 touchesBegan저장할 수 없기 때문에 나중에 UIScrollView 로 보낼 수 없습니다. touches인수는 다음 touchesMoved이벤트 전에 변경 될 개체를 포함 하며 이전 상태를 재현 할 수 없습니다.

    따라서 touchesBeganUIScrollView에 즉시 전달 해야하지만 touchesMoved가로로 스크롤하기로 결정할 때까지 추가 이벤트를 숨 깁니다 . 아니오 touchesMoved는 스크롤이 없음을 의미하므로이 이니셜 touchesBegan은 해를 끼치 지 않습니다. 그러나 delaysContentTouches추가 깜짝 타이머가 방해하지 않도록 NO로 설정하십시오 .

    (오프 토픽 — 사용자와 달리 UIScrollView 터치를 적절하게 저장하고 touchesBegan나중에 원래 이벤트를 재현하고 전달할 수 있습니다 . 게시되지 않은 API를 사용하는 불공정 한 이점이 있으므로 터치 개체가 변경되기 전에 복제 할 수 있습니다.)

  • 당신은 항상 앞으로 감안할 때 touchesBegan, 당신은 또한 전달해야 touchesCancelled하고 touchesEnded. 당신은 설정해야 할 touchesEndedtouchesCancelled있는 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에이 작은 에세이를 추가합니다.


7
이 중첩있는 ScrollView의 동작은, SDK 지금 상자 밖으로 작동 참고 필요하지 재미 사업
andygeers하지

1
andygeers 댓글을 +1했지만 정확하지 않다는 것을 깨달았습니다. 시작점에서 대각선으로 드래그하여 일반 UIScrollView를 "파괴"할 수 있으며 "스크롤러 손실을 당겼습니다". 해제하고 끝날 때까지 방향 잠금을 무시합니다.
Kalle

멋진 비하인드 스토리 설명에 감사드립니다! 나는 조금 덜 침습적 인 IMO처럼 보이는 Mattias Wadman의 솔루션을 사용했습니다.
Ortwin Gentz ​​2014

UIScrollView가 팬 제스처 인식기를 노출 하므로이 답변이 업데이트되는 것을 보는 것이 좋습니다. (하지만 아마도 그것은 원래 범위를 벗어났습니다.)
jtbandes dec

이 게시물이 업데이트 될 가능성이 있습니까? 훌륭한 게시물과 귀하의 의견에 감사드립니다.
Pavan 2015

56

이것은 매번 나를 위해 작동합니다 ...

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

20
이 질문에 걸림돌이되는 다른 사람을 위해 : 나는이 답변이 질문을 명확히 한 편집 전에 게시되었다고 생각합니다. 이렇게하면 가로 로만 스크롤 할 수 있으며 세로 또는 가로로 스크롤 할 수 있지만 대각선으로는 스크롤 할 수없는 문제가 해결되지 않습니다.
andygeers 2011-04-30

나를위한 매력처럼 작동합니다. 페이징이있는 매우 긴 가로 scrollView와 필요한 세로 스크롤을 처리하는 각 페이지에 UIWebView가 있습니다.
Tom Redman

우수한! 그러나 누군가 이것이 어떻게 작동하는지 설명 할 수 있습니까 (contentSize 높이를 1로 설정)!
Praveen

실제로 작동하지만 왜 그리고 어떻게 작동합니까? 아무도 그것을 이해할 수 있습니까?
Marko Francekovic 2013 년

27

나 같은 게으른 사람들을 위해 :

설정 scrollview.directionalLockEnabledYES

- (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 는 델리게이트가 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;
    }
 }

흠 ... 실은 좀 더 조사 해보면 잘 작동 하지만 감속 단계에서 방향을 제어 할 수 없다는 점이 문제입니다. 따라서 손가락을 대각선으로 움직이기 시작하면 수평 또는 수직으로 만 움직이는 것처럼 보입니다. 잠시 동안 대각선 방향으로 감속 할 지점 을 놓을 때까지
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을 설정하면 페이징 동작이 파괴되는 것 같습니다. Mattias Wadman의 솔루션을 사용했습니다.
Ortwin Gentz ​​2014

13

나는 여기서 무덤을 파고 있을지 모르지만 오늘 같은 문제를 해결하려고 노력 하면서이 게시물을 우연히 발견했습니다. 여기 내 해결책이 있습니다. 잘 작동하는 것 같습니다.

한 화면의 콘텐츠를 표시하고 싶지만 사용자가 왼쪽 및 오른쪽 위의 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);
    }
}

사용자가 스크롤 뷰를 놓아도이 코드가 멋지게 "감속"되지 않는다는 것을 알게 될 것입니다. 감속을 원하지 않으면 괜찮을 수 있습니다.
andygeers 2011

4

여러분들, 서브 뷰 높이를 스크롤 뷰 높이 및 콘텐츠 크기 높이와 동일하게 만드는 것만 큼 간단합니다. 그런 다음 수직 스크롤이 비활성화됩니다.


2
이 질문에 걸림돌이되는 다른 사람을 위해 : 나는이 답변이 질문을 명확히 한 편집 전에 게시되었다고 생각합니다. 이렇게하면 가로로만 스크롤 할 수 있으며 세로 또는 가로로 스크롤 할 수 있지만 대각선으로는 스크롤 할 수없는 문제가 해결되지 않습니다.
andygeers 2011-04-30

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 ​​2014

3

나는 또한이 문제를 해결해야했으며 Andrey Tarantsov의 답변에는 UIScrollViews작동 방식을 이해하는 데 유용한 정보가 많이 있지만 솔루션이 약간 복잡하다고 생각합니다. 서브 클래 싱이나 터치 포워딩을 포함하지 않는 내 솔루션은 다음과 같습니다.

  1. 각 방향에 대해 하나씩 두 개의 중첩 된 스크롤보기를 만듭니다.
  2. 두 개의 더미 만들기 UIPanGestureRecognizers각 방향에 대해 다시 하나씩 .
  3. 다음을 사용하여 UIScrollViews'panGestureRecognizer 속성과 UIPanGestureRecognizerUIScrollView의 원하는 스크롤 방향에 수직 인 방향에 해당 하는 더미 사이에 오류 종속성을 만듭니다.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

감사합니다 Tonetel. 나는 당신의 접근 방식을 약간 수정했지만 수평 스크롤을 방지하는 데 정확히 필요했습니다.

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

향후 참조를 위해 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
        }

    }

}

도움이 되었기를 바랍니다.


1

문서에서 :

"기본값은 NO입니다. 이는 가로 및 세로 방향으로 스크롤이 허용됨을 의미합니다. 값이 YES이고 사용자가 일반적인 한 방향 (가로 또는 세로)으로 끌기 시작하면 스크롤보기가 다른 방향으로 스크롤을 비활성화합니다. "

중요한 부분은 "만약 사용자 한 방향으로 드래그를 . 따라서 그들이 대각선으로 드래그하기 시작하면 이것은 시작되지 않습니다.이 문서는 해석에있어서 항상 신뢰할 수있는 것은 아닙니다. 그러나 그것은 당신이보고있는 것과 맞는 것 같습니다.

이것은 실제로 합리적인 것 같습니다. 질문해야합니다. 왜 언제든지 수평 또는 수직으로 제한하고 싶습니까? 아마도 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 두 번째 솔루션에서 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로 설정하십시오.

새로 온 사람들에게 도움이되기를 바랍니다!


0

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

-3

인터페이스 빌더를 사용하여 인터페이스를 디자인하는 경우 이것은 매우 쉽게 수행 할 수 있습니다.

인터페이스 빌더에서 UIScrollView를 클릭하고 단방향 스크롤로 제한하는 옵션 (검사기에서)을 선택하기 만하면됩니다.


1
IB에는이를 수행 할 수있는 옵션이 없습니다. 참조 할 수있는 두 가지 옵션은 실제 스크롤이 아닌 스크롤 막대의 표시와 관련이 있습니다. 또한 방향 잠금은 스크롤이 시작된 후에 만 ​​방향을 잠급니다.
Sophtware
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.