Tableview와 스크롤을 다루는 방법
Modern Collection View 와 MVVM 패턴 가이드
[iOS] Swift Modern Collection View & MVVM 패턴 가이드 | 덤벨로퍼 - 인프런
덤벨로퍼 | MVVM 패턴과 Modern Collection View를 사용해 네트워킹을 구현하고, 다양하고 동적인 Collection View를 자유자재로 다룰 수 있게 됩니다., Swift iOS UI, 제대로 다루는 핵심 기술! 📲 iOS Swift 레이
www.inflearn.com
Tableview의 길이가 길어져서 디바이스의 높이보다 길어지는순간
스크롤을 사용할수 있고 그덕에 긴 컨텐츠를 한 페이지에 보여줄수 있다.
그래서 table view의 사용목적 자체가 스크롤과 연관이 깊다.
이로 인해 스크롤과 테이블뷰 간의 관계를 잘 다루는게 유용할때가 많다.
어느날 이런 요구 사항을 구현해야했다.
- 테이블뷰가 있고 별개로 가장 하단에 고정 버튼이 있다.
- 스크롤의 높이에따라 버튼의 Title이 바뀐다. (스크롤이 가장 하단에 위치할때 , 그외)
- 하단 버튼을 클릭했을때 스크롤의 높이에따라 다른 작동을 한다. (스크롤이 가장 하단에 위치할때 , 그외)
- 가장 밑으로 스크롤 이동하기
- 다른 동작
이를 구현 하기 위해 구현해야할 함수는 다음과 같았다.
- 스크롤이 움직일때 지금 스크롤의 위치를 감지할 기능
- 가장 하단의 offset을 구하는 함수
- 스크롤의 위치를 가장 하단으로 이동할 기능
- 버튼 클릭했을때 스크롤에 위치에 다라 다르게 행동
스크롤의 위치를 감지
tableView.rx.contentOffset
.skip(1)
.bind { [weak self] point in
print(point.y)
}.disposed(by: disposeBag)
rxcocoa 를 사용하면 아주 감사하게도 tableview contentOffset 를 제공해준다.
Tableview의 가장 하단의 y offset을 구하는 함수
private func getBottomYOffset() -> CGFloat {
let contentSize = tableView.contentSize.height
let frameSize = tableView.frame.size.height
return contentSize - frameSize
}
content 의 길이와 frame 의 길이를 빼서 구할 수 있다.
스크롤이 가장 위에 위치했다면 Y offset 의 값은 0이다 (디바이스 기준 가장 위 지점이라 생각하면 됨)
스크롤 이 가장 하단에 위치한것은 테이블 뷰의 컨텐츠 높이에서 디바이스의 크기를 뺀 값이다 테이블 뷰의 frame 크기는 디바이스 크기와 같다 (크기 제약조건을 equal to superview로 지정했다면)
ContentSize FrameSize 는 아래와 같다.
스크롤의 위치를 가장 하단으로 이동
private func scrollToBottom() {
tableView.setContentOffset(.init(x: 0, y: getBottomYOffset()), animated: true)
}
위에서 구현한 함수를 통해 쉽게 이동시킬수 있다.
더불어 지금 위치가 바닥인지 아닌지 를 위에서 구현한 함수들을 통해 구현할수 있다.
private func checkScrollIsBottom(yOffset: CGFloat) -> Bool {
return yOffset >= getBottomYOffset() - 20
}
tableView.rx.contentOffset
.skip(1)
.bind { [weak self] point in
guard let s = self else { return }
if s.checkScrollIsBottom(yOffset: point.y) {
//Do SomeThing
} else {
//Do SomeThing
}
}.disposed(by: disposeBag)
버튼 클릭시 스크롤에 위치에 따라 다르게 행동
이것또한 rx를 사용해 쉽게 구현할수 있다.
bottomButton.rx.tap.withLatestFrom(confirmTableView.rx.contentOffset)
.bind { [weak self] point in
guard let s = self else {return }
if s.checkScrollIsBottom(yOffset: point.y) {
//Do SomeThing
} else {
s.scrollToBottom()
}
}.disposed(by: disposeBag)