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

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

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

그림

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

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

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

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

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

감사!



답변

스크롤 뷰와 테이블 뷰를 동시에 처리하는 솔루션은 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를 만들었습니다.

여기에 이미지 설명 입력


답변

더 효율적이고 버그가 없도록 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에서 볼 수 있습니다.


답변

많은 시행 착오 끝에 이것이 가장 잘 작동했습니다. 솔루션은 두 가지 요구 사항을 해결해야합니다. 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")

    }

}

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


답변

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

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

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


답변

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

인터페이스 빌더에서 :

  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로 설정해야합니다.


답변

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

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

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

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


답변

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

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

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.