모바일 사파리 탭과 같은 UIScrollView 수평 페이징


88

Mobile Safari를 사용하면 하단에 페이지 컨트롤이있는 일종의 UIScrollView 수평 페이징보기를 입력하여 페이지를 전환 할 수 있습니다.

가로로 스크롤 가능한 UIScrollView가 다음보기의 콘텐츠 중 일부를 표시하는이 특정 동작을 복제하려고합니다.

Apple에서 제공 한 예제 : PageControl은 가로 페이징에 UIScrollView를 사용하는 방법을 보여 주지만 모든보기는 전체 화면 너비를 차지합니다.

모바일 Safari처럼 다음보기의 일부 콘텐츠를 표시하는 UIScrollView를 얻으려면 어떻게해야합니까?


2
생각 만해도 스크롤 뷰의 경계를 화면보다 작게 만들고 뷰가 제대로 표시되도록 조정하십시오. (그리고 스크롤 뷰의 clipsToBounds를 NO로 설정)
mjhoy

uiscrollview의 너비 (수평 스크롤)보다 더 큰 페이지를 원했습니다. 그리고 mjhoy의 생각이 실제로 나를 도왔습니다!
Sasho

답변:


263

UIScrollView페이징이 활성화 된 A 는 프레임 너비 (또는 높이)의 배수에서 중지됩니다. 따라서 첫 번째 단계는 페이지의 너비를 파악하는 것입니다. 그 너비를 UIScrollView. 그런 다음 서브 뷰의 크기를 원하는만큼 크게 설정하고 UIScrollView의 폭의 배수를 기반으로 중앙을 설정하십시오 .

그런 다음, 사용자가 설정 물론 다른 페이지,보고 싶은 이후 clipsToBoundsNOmhjoy이 명시된 바와 같이합니다. 트릭 부분은 사용자가 UIScrollView의 프레임 범위 밖에서 드래그를 시작할 때 스크롤하도록하는 것 입니다. 내 솔루션 (최근에이 작업을 수행해야했을 때)은 다음과 같습니다.

크리에이트 UIView서브 클래스 (즉 ClipView포함) UIScrollView과의 파단. 본질적으로, 그것은 당신이 UIScrollView정상적인 상황에서 가질 것이라고 가정하는 틀을 가져야합니다 . 장소 UIScrollView의 중심에 ClipView. 너비가 부모보기의 너비보다 작 으면 ClipViewclipsToBounds가로 설정되어 있는지 확인하십시오 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 : 수정 도 살펴 봐야 합니다.


3
감사합니다 Ed. 나는 갔다 UIScrollView조회수 (에서의 게으른 로딩 서브 클래스 layoutSubviews) 및 사용 clippingRect할 일 pointInside:withEvent:테스트를. 정말 잘 작동하며 추가 컨테이너보기가 필요하지 않습니다.
ohhorob

1
좋은 대답입니다. 내가 사용 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의 답변에 언급 된 사용자 상호 작용에 문제가 없습니다.
cduck

1
와, 이것은 정말 좋은 해결책입니다. 하지만 내 설정에서 내가 추가하는 페이지에는 UIButton이 있습니다. hitTest 메서드를 재정의하면 해당 버튼을 탭할 수 없습니다. 스크롤 할 수 있지만 페이지 내에서 작업을 선택할 수 없습니다.
A Salcedo

8
최근 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset에이 문제를 접하는 사람들을 위해 iOS5의 UIScrollViewDelegate에 추가되면 더 이상이 palaver를 통과 할 필요가 없다는 것을 인식하십시오.
Mike Pollard 2013 년

1
@MikePollard 효과는 동일하지 않으며 targetContentOffset 은이 상황에 잘 맞지 않습니다.
카일 팡

70

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

이는 또한 사용자 상호 작용이 작동하는 버튼 및 기타 하위보기를 가져 오는 데 도움이됩니다. :) 감사
토마스 Clayson

4
이 코드를 주셔서 감사합니다. 어떻게이 수정을 사용하여 scrollview 경계에 포함되지 않은 하위보기 (버튼)에서 이벤트를 가져올 수 있습니까? 예를 들어 버튼이 중앙 스크롤 뷰의 경계 내에 있지만 외부가 아닌 경우 작동합니다.
DreamOfMirrors

4
이것은 정말 도움이되었습니다. 선택한 답변의 수정으로 포함되어야합니다.
Pacu 2011-06-22

2
예! 이것은 또한 scrollview 내에서 사용자 상호 작용이 다시 작동하게 만듭니다! 이 작업을 수행하려면 ClipView에서 userInteractionEnabled를 YES로 설정해야했습니다.
Tom van Zummeren 2011

self.scrollView.subviews를 반복하고 먼저 hitTest를 수행해야했습니다.
Bao Lei

8

scrollView의 프레임 크기를 페이지 크기로 설정하십시오.

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

이제에서 패닝 할 수 self.view있으며 scrollView의 콘텐츠가 스크롤됩니다.
또한 scrollView.clipsToBounds = NO;내용이 잘리지 않도록 사용 합니다.


5

사용자 지정 UIScrollView를 사용하는 것은 나에게 가장 빠르고 간단한 방법 이었기 때문입니다. 그러나 정확한 코드를 보지 못해서 공유 할 것이라고 생각했습니다. 내 요구 사항은 콘텐츠가 작은 UIScrollView가 필요했기 때문에 UIScrollView 자체는 페이징 효과를 얻기 위해 작았습니다. 게시물에 나와 있듯이 스 와이프 할 수 없습니다. 하지만 이제 할 수 있습니다.

CustomScrollView 클래스와 UIScrollView 하위 클래스를 만듭니다. 그런 다음 .m 파일에 추가하기 만하면됩니다.

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

이를 통해 좌우 (수평)로 스크롤 할 수 있습니다. 그에 따라 경계를 조정하여 스 와이프 / 스크롤 터치 영역을 설정합니다. 즐겨!


4

자동으로 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;
}

1
이미 xib / 스토리 보드가있는 경우 사용할 수 있습니다. 그렇지 않으면 Paging / Scrollview에 대한 참조를 어떻게 얻을 수 있을지 모르겠습니다. 어떤 아이디어?
mskw 2011

3

ClipView hitTest 구현에 대해 잠재적으로 유용한 또 다른 수정이 있습니다. ClipView에 대한 UIScrollView 참조를 제공 할 필요가 없었습니다. 아래 구현을 통해 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;
}

3

위의 upvoted 제안을 구현했지만 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에 사용될 수있다.


2
Dave, 저는 UICollectionView를 사용하여 이러한 동작을 달성하기 위해 고군분투하고 있었고 여기에서 설명하는 것과 동일한 접근 방식을 사용하게되었습니다. 그러나 스크롤링 경험은 페이징이 활성화 된 스크롤 뷰만큼 좋지 않습니다. 더 큰 속도로 스 와이프하면 여러 페이지가 스크롤되고 제안 된 페이지에서 중지되었습니다. 내가 달성하고 싶은 것은 페이징이 활성화 된 일반 스크롤 뷰로 동작을 저장하는 것입니다. 어떤 속도를 사용하더라도 1 페이지 만 더 얻을 수 있습니다. 어떻게하는지 아십니까?
Peter Stajger 2013 년

3

이 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의 답변을 편집 할 생각 이었지만 변경하기에는 너무 컸습니다.)


하위보기에 포함 된 UIButton에서 작업하기 위해 두드리기 위해 hitView = [childViews objectAtIndex : i]; with // find the UIButton for (UIView * paneView in [[childViews objectAtIndex : i] subviews]) {if ([paneView isKindOfClass : [UIButton class]]) return paneView; }
CharlesA 2013-10-17

2

내 버전 스크롤에 누워있는 버튼을 누르면-작업 =)

- (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] 만 스크롤이어야합니다.


이것은 내 문제를 해결했습니다 :) 경계를 확인할 때 스크롤 오프셋을 point.x에 추가하지 않고 코드가 수평 스크롤에서 제대로 작동하지 않습니다. 그것을 추가 한 후에는 잘 작동했습니다!
Bartserk 2013

2

다음은 Ed Marty의 답변을 기반으로 한 Swift 답변이지만 스크롤 뷰 내에서 버튼 탭 등을 허용하는 Juri Pakaste의 수정도 포함됩니다.

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

업데이트 해 주셔서 감사합니다. :)
Liam Bolling
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.