Search

서로 다른 뷰 컨트롤러의 nested scroll 문제 feat. sticky header

부드럽게 잘 동작하는 모습

Sticky header 구현

extension HomeViewController: UIScrollViewDelegate { //뷰가 정지해야할 임계점 var threshold: CGFloat = 386 func scrollViewDidScroll(_ scrollView: UIScrollView) { //스크롤뷰의 현재 위치 let offset = scrollView.contentOffset.y //MARK: Sticky header 스크롤 뷰 안의 컬렉션 뷰 구현했던 경험 살려서 수정할 것. if offset > self.threshold { scrollView.contentOffset = CGPoint(x: 0, y: self.threshold) //상위 뷰 스크롤 정지. scrollView.isScrollEnabled = false } } }
Swift
복사
이렇게만 해두면, 사용성이 매우 안좋다.
상위 스크롤에서 하위 스크롤을 내렸을 때, 반응이 느리다
하위 스크롤도중 상위 스크롤을 올리고자할 때, 상위 스크롤을 터치해서 스크롤해야만 반응한다.
위 두가지 문제를 해결하기 위해 필요한 알고리즘을 고안해 보았다.
상위 스크롤 > 하위 스크롤 넘어갈 때
1.
상위 스크롤이 임계점에 닿으면 상위 스크롤 비활성화
2.
하위 스크롤 재활성화
하위 스크롤 > 상위 스크롤 넘어갈 때
3.
하위 스크롤이 맨 위에 닿으면 하위 스크롤 비활성화
4.
상위 스크롤 재활성화
위 동작이 하나의 뷰컨트롤러 내에서 일어나는 경우에는 조건문 만으로 어렵지 않게 구현이 가능하다.
하지만 내 경우엔 하위 스크롤이 embed된 다른 뷰컨트롤러에 존재했기 때문에
뷰컨틀러간의 신호 전달이 필요했다.
따라서 delegate 패턴을 사용했다.
다만 2번 동작의 경우 재활성화를 위해 delegate 를 서로 엮는 것 보다는
타이머를 설정해 자동으로 재활성화 하는 것이
스파게티 코드를 피하는 방법이라 생각했다.

상위 스크롤 뷰 컨트롤러

protocol NestedScrollDelegate { func scrollDidEndTop() } extension HomeViewController: NestedScrollDelegate { func scrollDidEndTop() { scrollView.isScrollEnabled = true } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "toItem" { let viewController: HomeItemViewController = segue.destination as! HomeItemViewController viewController.scrollDelegate = self } } }
Swift
복사

하위 스크롤 뷰 컨트롤러

//MARK: - 중첩 스크롤 해결 extension RecommendViewController: UIScrollViewDelegate { var scrollDelegate: NestedScrollDelegate? func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView.contentOffset.y < -5 { //하위 스크롤이 맨위에 닿는 다면 delegate 메서드 실행 scrollDelegate?.scrollDidEndTop() //스크롤이 맨위까지 닿는다면 하위 스크롤 잠금 collectionView.isScrollEnabled = false //0.4 초 뒤에 바로 해제 Timer.scheduledTimer(withTimeInterval: 0.4, repeats: false) { timer in self.collectionView.isScrollEnabled = true } } } }
Swift
복사