jane1choi / TIL

Today I Learned #심야아요

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[iOS] UIView를 이용해 TableView의 Section Header처럼 보이도록 화면 구성하기

jane1choi opened this issue · comments

문제 상황

스크린샷 2022-03-05 오후 6 57 37 스크린샷 2022-03-05 오후 6 57 37

화질이 좀 깨지긴 하지만..
TableView의 Section Header로 구성한 카테고리 영역의 CollectionViewCell을 터치 시 해당 카테고리의 게시글들을 불러오는
get 통신을 하게 된다. 서버 통신 시 UI 업데이트를 위해 reload를 해주어야 하는데, 이 때 해당 section의 헤더까지 같이 reload가 되어 선택된 카테고리의 UI가 초기화되어 계속 '전체' 카테고리가 선택된 것 처럼 보인다는 문제가 있었다..😢

해결 방법

굉장히 다양한 방법들을 시도해봤지만(reloadSection이 아닌 reloadRows 메소드 사용, isSelected 오버라이드 방식 말고 셀 선택 판단을 didSelectItemAt에서 해주기 등..), CollectionView 영역이 SectionHeader에 위치해 있다면 함께 리로드 된다는 문제는 해결되지 않았다.

그래서 뷰의 전체 구조를 바꿔야 겠다고 생각을 하고
SectionHeader에 넣지 않고도 뷰의 상단에 닿으면 카테고리 영역이 붙을 수 있도록 하는 방법을 찾아봤다.
즉, SectionHeader에 셀을 위치 시키지 않고 sticky header 처럼 보이도록 해주어야 겠다고 생각한 것이다!

구글링을 통해 찾아낸 방법은!(감사합니다..) UIView를 이용해 상단에 숨겨질/고정될 영역을 만드는 것이었다.
이게 무슨말이냐 하면, 전체 화면 스크롤 시 화면 상단에 붙어서 고정될 영역인 컬렉션 뷰 영역과 그 위에 위치하는 숨겨질 부분(기존 section0으로 구성했었던 셀)을 모두 UIView로 구성한 후, ScrollView의 offset값에 따라 화면에 어떻게 보일 지를 판단해 주는 것이다.

스크린샷 2022-05-25 오후 11 18 10 스크린샷 2022-05-25 오후 11 18 26

  • HeaderView : 상단 전체 뷰
  • Upper Header View: 스크롤 시 숨겨질 영역
  • Category CV: 스크롤 시 화면 상단에 고정 될 카테고리 셀 부분

스토리보드에서 이렇게 뷰를 구성해주었는데, 이때 중요한 것은 HeaderView를 테이블 뷰 안에 위치시키는 것이 아니라 같은 계층에 위치 시키는 것이다.(그냥 테이블 뷰 위에 올려두는 것)
뷰를 다 구성했다면 이제 스크롤 시 화면에 어떻게 보여질 지 offSet값으로 판단만 해주면 된다!

    let maxHeight: CGFloat = 215.0 // headerView의 최대 높이 값 (UpperHeaderView 높이 + CategoryCV 높이)
    let minHeight: CGFloat = 55.0 // headerView의 최소 높이 값 (화면 상단에 고정될 CategoryCV 높이)
    
    // MARK: IBOutlet
    @IBOutlet weak var feedTV: UITableView! {
        didSet {
            feedTV.contentInset = UIEdgeInsets(top: maxHeight, left: 0, bottom: 0, right: 0)
        }
    }
    @IBOutlet weak var categoryCV: UICollectionView!
    @IBOutlet weak var headerView: UIView!
    @IBOutlet weak var upperHeaderView: UIView!
    @IBOutlet weak var heightConstraint: NSLayoutConstraint! {
        didSet {
            heightConstraint.constant = maxHeight
        }
    }

요소들을 outlet으로 연결해주고! 여기서 중요한 것은 feedTV.contentInsetheightConstraint.constant 의 값을 지정해줄 때 didSet 이라는 프로퍼티 옵저버를 써준 것이다.
얼마나 스크롤 하느냐에 따라 headerView의 높이를 바꿔줄 것이고, 이에 따라 TableView 상단 제약조건 또한 바뀌어야 하기 때문에 해당 값이 바뀔 때 마다 View도 갱신해주기 위해 프로퍼티 값이 변경되기 직전을 감지해주는 didSet 프로퍼티 옵저버를 사용했다.

다음으로, TableView가 scroll 됨에 따라 UI가 바뀌어야 하기 때문에 scrollViewDidScroll() 메소드를 구현해주었다.
이 함수는 UIScrollViewDelegate의 메소드로, 스크롤이 됐을 때 마다 호출된다(print를 찍어보면 스크롤 하고 있을 때 연속적으로 계속 프린트문이 찍힌다)

// MARK: - UITableViewDelegate
extension FeedMainVC: UITableViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.contentOffset.y < 0.0  { // 카테고리 영역이 상단에 닿기 전
            heightConstraint.constant = max(abs(scrollView.contentOffset.y), minHeight)
        } else if scrollView.contentOffset.y == 0.0 { // 처음 viewDidLoad()가 실행될 때 (UpperHeaderView까지 다 보이도록)
            heightConstraint.constant = maxHeight
        } else { // 카테고리 영역만 보이도록 
            heightConstraint.constant = minHeight
        }
    }
}

이렇게 구성하니 TableView에는 리로드 되어야하는 게시글 셀들만 존재하기 때문에(게시글 영역과 카테고리 영역 분리됨) 카테고리 UI까지 함께 리로드되는 문제가 해결되었다!

완성

Simulator Screen Recording - iPhone 11 - 2022-05-25 at 23 54 58

느낀점

사실 이때까지 화면 구성 단계가 UI 구성 - 기능 구성 - 서버 통신 의 단계로 진행된다고 생각했는데
UI 구성 단계에서 이후의 작업을 어떻게 할 지 까지 큰 그림을 그리고 개발을 시작하는 것이 중요하다는 것을 깨달았다!
서버 통신을 어느 버튼을 누를 때, 어떻게 해줄지 생각을 해보고 그 때 문제는 없을 지 UI 부분에서 이슈는 없을 지 잘 생각한 다음에 작업에 들어가는 것이 중요할 것 같다.
+테이블 뷰의 미래..제법 어둡다..?

참고자료: https://iamcho2.github.io/2020/11/02/uitableview-sticky-header, https://stackoverflow.com/questions/46692316/how-to-hide-a-uiview-while-scrolling