ScrollView 내에서 TableView의 스크롤을 자연스럽게 만드는 방법


80

이상한 구성을 가진이 앱을해야합니다.

다음 이미지에 표시된대로 기본보기는 UIScrollView입니다. 그런 다음 내부에는 UIPageView가 있어야하며 PageView의 각 페이지에는 UITableView가 있어야합니다.

그림

나는 지금까지이 모든 것을했다. 하지만 내 문제는 스크롤링이 자연스럽게 작동 하기를 원한다는 것 입니다.

다음은 제가 자연스럽게 의미하는 바입니다 . 현재 UITableView 중 하나를 스크롤하면 스크롤 뷰가 아닌 테이블 뷰가 스크롤됩니다. 그러나 scrollview가 스크롤 할 수 없어서 상단 또는 하단에 도달하지 않는 한 ScrollView를 스크롤하고 싶습니다 (이 경우 tableview를 스크롤하고 싶습니다).

예를 들어, 내 scrollview가 현재 맨 위로 스크롤되었다고 가정 해 보겠습니다. 그런 다음 테이블 뷰 (현재 페이지가 표시됨) 위에 손가락을 놓고 아래로 스크롤 하기 시작 합니다 . 이 경우 스크롤 뷰가 스크롤되기를 원합니다 (테이블 뷰 없음). 스크롤 뷰를 계속 아래로 스크롤하고 아래쪽에 도달하면 디스플레이에서 손가락을 떼고 tebleview 위에 다시 놓고 다시 아래로 스크롤하면 스크롤 뷰가 맨 아래에 도달했지만 그렇지 않았기 때문에 테이블 뷰가 아래로 스크롤되기를 원합니다. 계속 스크롤 할 수 있습니다.

이 스크롤을 구현하는 방법에 대해 알고 있습니까?

나는 이것으로 정말로 길을 잃었다. 어떤 도움이라도 대단히 감사하겠습니다 :(

감사!


사용자가 스크롤보기에서 다시 위로 스크롤하려는 경우 어떻게됩니까? 테이블보기에서 맨 위로 스크롤해야합니까?
Daniel Zhang

아니 @DanielZhang. 이 경우 scrollview는 더 이상 스크롤 할 수 없을 때까지 위로 스크롤해야합니다. 그런 다음 tableview가 위로 스크롤해야 할 때입니다.
Marcela Mukel Balady 2015

나는 대답을 추가했으며 테이블보기에서 위로 스크롤하는 것과 관련하여 설명하는 것과 반대임을 깨달았습니다. 그런 식으로 자연스러운 것 같습니다. 행동이 어떻게 달라야하는지 자세히 설명해 주시겠습니까?
Daniel Zhang

답변:


61

스크롤 뷰와 테이블 뷰를 동시에 처리하는 솔루션은 UIScrollViewDelegate. 따라서 뷰 컨트롤러가 해당 프로토콜을 따르도록하십시오.

class ViewController: UIViewController, UIScrollViewDelegate {

스크롤 뷰와 테이블 뷰를 콘센트로 표현하겠습니다.

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!

또한 스크롤 뷰 콘텐츠의 높이와 화면 높이를 추적해야합니다. 나중에 이유를 알 수 있습니다.

let screenHeight = UIScreen.mainScreen().bounds.height
let scrollViewContentHeight = 1200 as CGFloat

다음에서 약간의 구성이 필요합니다 viewDidLoad:.

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView.contentSize = CGSizeMake(scrollViewContentWidth, scrollViewContentHeight)
    scrollView.delegate = self
    tableView.delegate = self 
    scrollView.bounces = false
    tableView.bounces = false
    tableView.scrollEnabled = false
}

단순하게 유지하기 위해 튀는 기능을 해제했습니다. 주요 설정은 스크롤 뷰 및 테이블 뷰에 대한 델리게이트이며 테이블 뷰 스크롤링이 처음에 꺼지는 것입니다.

scrollViewDidScroll:대리자 메서드가 스크롤 뷰의 맨 아래에 도달하고 테이블 뷰의 맨 위에 도달하는 것을 처리 할 수 ​​있도록하기 위해 필요 합니다. 그 방법은 다음과 같습니다.

func scrollViewDidScroll(scrollView: UIScrollView) {
    let yOffset = scrollView.contentOffset.y

    if scrollView == self.scrollView {
        if yOffset >= scrollViewContentHeight - screenHeight {
            scrollView.scrollEnabled = false
            tableView.scrollEnabled = true
        }
    }

    if scrollView == self.tableView {
        if yOffset <= 0 {
            self.scrollView.scrollEnabled = true
            self.tableView.scrollEnabled = false
        }
    }
}

델리게이트 메서드가하는 일은 스크롤 뷰가 바닥에 도달했을 때 감지하는 것입니다. 이 경우 테이블보기를 스크롤 할 수 있습니다. 또한 테이블보기가 스크롤보기가 다시 활성화 된 맨 위에 도달하는시기를 감지합니다.

결과를 보여주기 위해 GIF를 만들었습니다.

여기에 이미지 설명 입력


7
헤이 다니엘, 답변 주셔서 감사합니다. 효과가있다! 사용자는 한 스크롤이 활성화 / 비활성화 될 때마다 두 번 스크롤해야합니다. 내 말은, 스크롤링은 그 순간에 한 스크롤에서 다른 스크롤로 옮겨지지 않는다는 것입니다. 이것을 어떻게 달성하는지 알고 있습니까? 다시 한 번 감사드립니다;)
Marcela Mukel Balady 2015

1
천만에요, Marcela. 귀하의 질문에 답하기 위해 스크롤을 한 스크롤보기에서 다른 스크롤보기로 옮기는 편리한 방법을 모르겠습니다. stackoverflow.com/questions/21554327/…에 유용한 관련 정보가 있습니다. 때로는 향후 OS 릴리스에서 중단 될 위험과 함께 관련된 작업으로 인해 과도한 사용자 정의가 필요한 것을 추구하는 것이 가치가 없습니다.
다니엘 장

@DanielZhang은 스크롤링을 전송하기 위해 델리게이트 메소드 scrollViewDidScroll이 호출 될 때 동기화하려는 뷰간에 .contentOffset을 공유합니다.
Ian

@Daniel Zhang 좋은 작업 및 데모이지만 제 경우에는보기, 스크롤보기 및 테이블보기에 설정 한 제약 조건과 관련된 문제가있었습니다. 수정하면 1 초 안에 작동합니다.
Ravi

@DanielZhang이 코드의 tableview 높이는 얼마입니까?
e.ozmen

28

더 효율적이고 버그가 없도록 Daniel의 답변을 수정했습니다.

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var tableHeight: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    //Set table height to cover entire view
    //if navigation bar is not translucent, reduce navigation bar height from view height
    tableHeight.constant = self.view.frame.height-64
    self.tableView.isScrollEnabled = false
    //no need to write following if checked in storyboard
    self.scrollView.bounces = false
    self.tableView.bounces = true
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 20
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 30))
    label.text = "Section 1"
    label.textAlignment = .center
    label.backgroundColor = .yellow
    return label
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = "Row: \(indexPath.row+1)"
    return cell
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView == self.scrollView {
        tableView.isScrollEnabled = (self.scrollView.contentOffset.y >= 200)
    }

    if scrollView == self.tableView {
        self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
    }
}

전체 프로젝트는 https://gitlab.com/vineetks/TableScroll.git에서 볼 수 있습니다.


@Vinit Kumar Singh 훌륭하게 문제를 해결했습니다. 정말 감사합니다
Vinayak Bhor에게

@Vineet Kumar 내비게이션 바가 없다면 어떻게해야하나요? 우리가 탐색 모음을 제거하면 코드에서, 만 11 행 개까지 스크롤
Bhanuteja

1
64는 탐색 모음의 기본 높이입니다. viewDidLoad () 메서드에서 0으로 줄이십시오.
Vineet Kumar Singh

이것이 작동하는 솔루션입니다. @Marcela이 답변 동의하십시오
Naveed 아마드

작업 솔루션, 나를 위해 일했습니다.
Pritesh

4

많은 시행 착오 끝에 이것이 가장 잘 작동했습니다. 솔루션은 두 가지 요구 사항을 해결해야합니다. 1) 스크롤 속성을 사용해야하는 사람을 결정합니다. tableView 또는 scrollView? 2) tableView가 테이블 / 콘텐츠의 상단에 도달 할 때까지 scrollView에 권한을 부여하지 않는지 확인하십시오.

scrollview가 tableview와 비교하여 scrollview를 사용 해야하는지 확인하기 위해 tableview 바로 위의 UIView가 프레임 내에 있는지 확인했습니다. UIView가 프레임 내에 있으면 scrollView에 스크롤 권한이 있어야한다고 말하는 것이 안전합니다. UIView가 프레임 내에 없으면 tableView가 전체 창을 차지하고 있으므로 스크롤 권한이 있어야합니다.

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if scrollView.bounds.intersects(UIView.frame) == true {
     //the UIView is within frame, use the UIScrollView's scrolling.   

        if tableView.contentOffset.y == 0 {
            //tableViews content is at the top of the tableView.

        tableView.isUserInteractionEnabled = false
       tableView.resignFirstResponder()
        print("using scrollView scroll")

        } else {

            //UIView is in frame, but the tableView still has more content to scroll before resigning its scrolling over to ScrollView.

            tableView.isUserInteractionEnabled = true
            scrollView.resignFirstResponder()
            print("using tableView scroll")
        }

    } else {

        //UIView is not in frame. Use tableViews scroll.

        tableView.isUserInteractionEnabled = true
       scrollView.resignFirstResponder()
        print("using tableView scroll")

    }

}

이것이 누군가를 돕기를 바랍니다!


UIView.frame은 무엇입니까?
Andy Obusek 2018

@AndyObusek 모든 UIView는 tableview 바로 위에 있습니다.
Felecia Genet

이것이 어떻게 작동하는지보기 위해 고군분투하는 많은 사람들이 있기 때문에 이것의 프로젝트를 업로드 해 주시겠습니까? 아래 Vineet의 데모 프로젝트에 솔루션을 구현했지만 (쉽게 테스트 할 수 있었기 때문에) 내부 테이블 뷰가 차단되고 스크롤이 전송되지 않습니다.
Pavan

2
솔루션은 스크롤을 동시에 전송하지 않습니다. 스크롤보기 스크롤에서 테이블보기 스크롤로 이동하려면 두 번 탭하고 스크롤해야합니다.
Pavan

3

여기에 나온 답변 중 어느 것도 나를 위해 완벽하게 작동하지 않았습니다. 각각은 고유 한 미묘한 문제 (스크롤 뷰가 바닥에 닿았을 때 반복적으로 스 와이프해야하거나 스크롤 표시기가 정확하지 않은 것 등)가 있었기 때문에 다른 답변을 던질 것이라고 생각했습니다.

Ole Begemann은이 작업을 정확하게 수행하는 https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

오래된 게시물 임에도 불구하고 개념은 현재 API에 여전히 적용됩니다. 또한 그의 접근 방식 https://github.com/eyeem/OLEContainerScrollView 의 유지 (Xcode 9 호환) Objective-C 구현이 있습니다.


3

나도이 문제로 고심하고 있었다. 아주 간단한 해결책이 있습니다.

인터페이스 빌더에서 :

  1. 간단한 ViewController 만들기
  2. 간단한 뷰를 추가하면 헤더가되고 수퍼 뷰로 제한합니다.
    • 아래 예의 빨간색보기입니다.
    • 위쪽, 왼쪽 및 오른쪽에서 12px를 추가하고 고정 높이를 128px로 설정했습니다.
  3. PageViewController를 포함하여 헤더가 아닌 superview로 제한되도록합니다.

ib

이제 재미있는 부분이 있습니다. 추가하는 각 페이지에 대해 tableView가 위쪽에서 오프셋이 있는지 확인하십시오. 그게 다야. 예를 들어이 코드를 사용하면 할 수 있습니다 (UITableViewController를 페이지로 사용한다고 가정).

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let tables = viewControllers.compactMap { $0 as? UITableViewController }
    tables.forEach {
        $0.tableView.contentInset = UIEdgeInsets(top: headerView.bounds.height, left: 0, bottom: 0, right: 0)
        $0.tableView.contentOffset = CGPoint(x: 0, y: -headerView.bounds.height)
    }
}

테이블 뷰 내부 스크롤 내부의 지저분한 스크롤, 델리게이트와의 맹 글링, 중복 스크롤, 완벽하게 자연스러운 동작이 없습니다. 헤더가 보이지 않는다면 아마도 tableView 배경색 때문일 것입니다. 헤더가 tableView 아래에서 표시되도록하려면이를 clear로 설정해야합니다.


이 솔루션에서는 헤더 뷰와 사용자 상호 작용이 불가능합니다.
Knut Valen 20.05.12

나는이 일을 할 수 없었다. 테이블의 섹션 헤더는 contentInset / offset 위에 표시되지 않으며 헤더의 버튼은 클릭 할 수 없습니다. 하지만 스크롤링은 부드럽고 기분이 좋습니다.
Knut Valen

3

중첩 된 스크롤 문제에 문제가있는 경우 여기에 가장 간단한 해결책이 있습니다.

  1. 디자인 화면으로 이동
  2. 스크롤보기를 선택한 다음 스크롤시 바운스를 비활성화합니다.
  3. 뷰가 스크롤 뷰 내에서 테이블 뷰를 사용하는 경우 테이블 뷰 스크롤에서 바운스를 비활성화하십시오.
  4. 실행하고 해결되었는지 확인하십시오.

스크롤 뷰의 스크롤에서 바운스를 비활성화하는 방법 확인

tableview보기의 스크롤에서 바운스를 비활성화하는 방법을 확인하십시오.


1

두 가지 옵션이 있다고 생각합니다.

스크롤 뷰와 메인 뷰의 크기를 알고 있기 때문에 스크롤 뷰가 바닥에 닿았는지 여부를 알 수 없습니다.

if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
    // reach bottom
}

그래서 그것이 맞았을 때; 당신은 기본적으로 설정

[contentScrollView setScrollEnabled:NO];

및 tableView에 대한 다른 방법.

제 생각에 더 정확한 다른 것은 제스처를 뷰에 추가하는 것입니다.

UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(respondToTapGesture:)];

// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;

// Add the tap gesture recognizer to the view
[self.view addGestureRecognizer:tapRecognizer];

// Do any additional setup after loading the view, typically from a nib

당신이 제스처를 추가 할 때, 당신은 단순히 변경하여 활성 뷰 제어 할 수 있습니다 setScrollEnabled에를 respondToTapGesture.


1

멋진 라이브러리 MXParallaxHeader를 찾았습니다.

Storyboard에서 UIScrollView클래스를 설정 MXScrollView하면 마법이 발생합니다.

이 클래스를 사용 UIScrollView하여 UIPageViewController컨테이너 뷰를 임베드 할 때 처리했습니다 . 더 자세한 정보를 위해 시차 헤더보기를 삽입 할 수도 있습니다.

또한,이 라이브러리를 제공 Cocoapods하고Carthage

아래에 UIView계층 구조 를 나타내는 이미지를 첨부했습니다 . MXScrollView 계층


0

정답으로 표시된 솔루션을 시도했지만 제대로 작동하지 않았습니다. 사용자는 스크롤을 위해 테이블 ​​뷰를 두 번 클릭해야하는데 그 후에는 전체 화면을 다시 스크롤 할 수 없었습니다. 그래서 방금 viewDidLoad ()에 다음 코드를 적용했습니다.

tableView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(tableViewSwiped)))
scrollView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(scrollViewSwiped)))

그리고 아래 코드는 액션의 구현입니다.

func tableViewSwiped(){
    scrollView.isScrollEnabled = false
    tableView.isScrollEnabled = true
}

func scrollViewSwiped(){
    scrollView.isScrollEnabled = true
    tableView.isScrollEnabled = false
}

0

무차별적일 수도 있지만 완벽하게 작동합니다. 그런데 저는 자동 레이아웃을 사용합니다.

tableView (또는 collectionView 또는 기타)에 대해 스토리 보드에서 임의의 높이를 설정하고 클래스에 콘센트를 만듭니다. 적절한 경우 (viewDidLoad () 또는 ...) tableView가 스크롤 할 필요가 없도록 tableView의 높이를 충분히 크게 설정합니다. (미리 행 수를 알아야 함) 그러면 외부 scrollView 만 멋지게 스크롤됩니다.


diffrent 셀 높이가 tableviews이 나던 일
모하마드 레자 Koohkan

0

한 가지 쉬운 트릭은 부모 스크롤 뷰를 일반 컨테이너 뷰로 바꾸는 것입니다. 컨테이너보기에 팬 제스처를 추가하면 첫 번째보기의 상단 제약으로 플레이하여 음수 값을 할당 할 수 있습니다. 페이지보기의 출처를 확인할 수 있습니다. 정상에 도달하면 pageView의 하위보기의 콘텐츠 오프셋에 해당 값을 할당 할 수 있습니다. 사용자가 컨테이너보기에서 최상위보기 상태에서 테이블보기를 얻을 때까지 페이지 tableView의 스크롤을 비활성화하고 콘텐츠 오프셋을 설정하여 수동으로 스크롤 할 수 있습니다. 따라서 처음에는 페이지보기 높이가 하단에서 축소 (또는 화면 밖) 또는 그 이하로 표시됩니다. 나중에 아래로 스크롤하면 더 많은 공간을 차지하도록 확장됩니다.

프레임 외부에서 탐색 표시 줄 또는 컨테이너보기 외부의 다른보기에 표시되면 제스처가 자동으로 응답을 중지합니다.

제스처는 많은 앱에서 사용되는 사용자 상호 작용 전환의 핵심입니다. 특정 시간 동안 스크롤을 모방 할 수 있습니다.


0

제 경우에는 다음과 같은 높이에 대한 제약 조건을 사용하고 있습니다.

self.heightTableViewConstraint.constant = self.tableView.contentSize.height
self.scrollView.contentInset.bottom = self.tableView.contentSize.height

0

SWIFT 5

다양한 화면 크기로 인해 scrollView 콘텐츠 오프셋 (Y)을 보장 할 수없는 경우 Vineet의 답변을 사용하는 데 문제가 있습니다. 이를 해결하기 위해 tableView의 스크롤이 활성화 될 때의 첫 번째 트리거 이벤트를 변경했습니다.

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.bounds.contains(button.frame) {
            tableView.isScrollEnabled = true
        }

        if scrollView == tableView {
            self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
        }
    }

scrollView.bounds.contains는 주어진 요소의 프레임이 scrollView의 보이는 콘텐츠 내에 완전히 있는지 확인합니다. 이것을 tableView 아래에있는 버튼으로 설정했습니다. tableView가 완전히 표시되는 유일한 조건 인 경우 대신 tableVIew의 프레임으로 설정할 수 있습니다.

tableView의 스크롤을 비활성화 할 때의 원래 구현을 그대로 두었고 매우 잘 작동합니다.


-1
CGFloat tableHeight = 0.0f;

YourArray =[response valueForKey:@"result"];

tableHeight = 0.0f;
for (int i = 0; i < [YourArray count]; i ++) {
    tableHeight += [self tableView:self.aTableviewDoc heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}

self.aTableviewDoc.frame = CGRectMake(self.aTableviewDoc.frame.origin.x, self.aTableviewDoc.frame.origin.y, self.aTableviewDoc.frame.size.width, tableHeight);

안녕. 질문은 "swift"태그가 있습니다. 태그에 따라 답변을 작성하십시오. 감사! :)
Eric Aya

1
또한 코드에 몇 가지 설명을 추가해야합니다. 이 스 니펫은 OP 문제를 어떻게 해결합니까?
Luca Angeletti
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.