[iOS]TableView Expand (by touch on section) - TableView section별로 접기/펴기

신용철·2020년 10월 16일
0

iOS_TableView

목록 보기
4/6

오늘 다를 주제는 tableView의 section별로 cell들을 접었다 폈다하는 방법에 관한 것입니다. 특히, section headerView를 클릭하면 내부의 cell들이 펼쳐졌다가 접혔다 하는 방법에 대해 알아보겠습니다.

1. section을 펼지 접을지 판단할 수 있는 [Bool]을 만듭니다.

  • 만약에 section이 4개라면 var isOpen = [false, false, false, false]로 만들어줍니다.

2. tableView(numberOfRowsInSection:)에 true/false에 따라 보여줄 cell의 갯수를 설정합니다.

  • 아래 예시처럼, isOpen[section]값이 true이면 cell의 개수를 정상표시하고, false면 0개를 표시하도록 delegate method에 조건문을 만듭니다.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if isOpen[section] == true {
            return dataList.count
        } else {
            return 0
        }
    }
  • 자 그럼 이제 isOpen[IndexPath.section]tableView.reloadData()를 이용하여 section을 펴고 접을 수 있습니다. 예를 들어 [false, false, false, false]상태에서 첫 번째 section을 열고 싶으면 [true, false, false, false]와 같이 0번째 element값을 true 만든 다음에 tableView를 reload하면 되겠죠? 아주 간단합니다.

  • 기능 구현을 어떻게 하느냐에 따라 어느 시점에 어떤 방식으로 펴고 접을지는 달라집니다. 펼치거나 접고싶은 section의 index값만 알면 var isOpen = [false, false, false, false] 에 접근하여 element를 .toggle()하고 self.tableView.reloadData()만 하면 되는 것이죠!

3. HeaderView에 TapGestureRecognizer를 추가합니다.

  • 다양한 시점에서 section을 펴고 접을 수 있겠지만 여기서는 section(headerView)를 Touch했을 때 동작하게 만들기로 했죠. 아쉽게도 section은 cell과 다르게 didSelect와 같은 method가 없습니다. 따라서 TapGestureRecognizer를 사용해야합니다.

  • 참고로, section의 View는 UITableViewHeaderFooterView를 상속받아 만들 수 있습니다.

    class CustomHeaderView: UITableViewHeaderFooterView { }
  • 아래와 같이 HeaderView안에 TapGestureRecognizer를 추가합니다. 아래 처럼 contentView에 addGestureRecognizer를 해주면 headerView의 어느 부분을 클릭해도 TapGestureRecognizer가 발동하게 됩니다.

 class CustomHeaderView: UITableViewHeaderFooterView {
    let tapGestureRecognizer = UITapGestureRecognizer()
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        contentView.addGestureRecognizer(tapGestureRecognizer)
        tapGestureRecognizer.addTarget(self, action: #selector(didSelectSection))
    }
    
    @objc func didSelectSection() {
        
    }
}
  • 아 그리고 UITableViewHeaderFooterView에서는 override func draw(_ rect: CGRect)를 init() 처럼 사용한다는 사실 잊지마시구요.
  • 자, 이제 section을 클릭하면 위의 @objc func didSelectSection()가 호출될 것이고, 이 method에서 우리가 펼치고 접을 section의 index값을 가지고 var isOpen = [false, false, false, false]의 값을 변경한 후에 tableView.reloadData()가 호출될 수 있도록 하면 작업이 끝납니다.

  • 그러나 class CustomHeaderView안에는 section의 index 값도 없고 tableView에는 더더욱 접근할 수가 없는 상황입니다. 이 두 가지 문제를 하나씩 해결해 봅시다.

4. HeaderView에 section값 넘겨주기

  • 아래와 같이 HeaderView class 안에 section값을 저장할 property를 선언합니다.
 class CustomHeaderView: UITableViewHeaderFooterView {
    var sectionIndex = 0
}
  • ViewController에서 tableView가 생성될 때 sectionView 별로 각각의 section의 Index값을 위의 property에 담아줍니다.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        guard let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "headerView") as? CustomHeaderView else { return UITableViewHeaderFooterView() }
           
        headerView.sectionIndex = section // 여기서 담아 줍니다.
        
        return headerView
    }
  • 이제 class CustomHeaderViewvar sectionIndex안에 section의 index가 담겼네요. 이제 이 section값을 가지고 ViewController에 있는 var isOpen = [false, false, false, false]의 값을 바꾸고, tableView만 reload하면 됩니다.

5. Delegate를 이용하여 ViewController에 접근하기

  • 이제 우리가 해야할 일은 section에서 TapGestureRecognizer가 발동되었을 때 발동된 바로 그 section의 index값을 ViewController에 던져주고 var isOpen = [false, false, false, false]의 값을 바꾸고, tableView만 reload하는 것이죠.

  • ViewController에 접근하기 위해서는 delegate를 사용해야합니다. CustomHeaderView class에 delegate를 선언하고 TapGestureRecognizer의 method에 집어넣습니다. 그러면 section이 클릭될 때마다 delegate method가 실행될겁니다. 우리는 section의 index값을 viewController에 던져줘야 하기 때문에 당연히 delegate method에는 index를 받을 수 있는 parameter가 있어야 합니다..

protocol HeaderViewDelegate: AnyObject {
    func didTouchSection(_ sectionIndex: Int)
}

class CustomHeaderView: UITableViewHeaderFooterView {

    var delegate: HeaderViewDelegate?
    var sectionNumber: Int = 0
    
     @objc func didSelectSection() {
        delegate?.didTouchSection(self.sectionNumber)
    }
  • 이제 ViewController에서 해당 프로토콜을 채택한 후 함수내용을 구현해 줍니다.
class ViewController: UIViewController, HeaderViewDelegate {
    /*CustomHeaderView에 delegate를 선언하여, 클릭된 section의 Int를 가져와
      true, false값을 toggle()한 후에  tableView를 리로드시킵니다.*/
    func didTouchSection(_ sectionIndex: Int) {
        self.sectionDataList[sectionIndex].isOpen.toggle()
        self.tableView.reloadData()
    }

6. 관련 팁

  • 만약 cell 안에 collectionView가 들어가 있거나 다른 TableView가 또 들어가 있는 경우, 안에 있는 것까지 동시에 reload를 해주어야 데이터가 정상적으로 반영이 됩니다. viewController에 있는 tableView만 relead하게 되면 내부의 collectionView에 data는 전달하지만 다시 그리지는 않습니다.

  • 접었다 필때마다 reload를 하면 곤란한 경우가 있을 수도 있겠죠. 예를 들어 data가 초기화되면 안되는 경우가 있을 수 있습니다. 이럴 경우에는 cell을 지우고 다시 그리는 위의 방법 보다. cell의 rowheight를 변경해주는 방법도 있습니다. true이면 정상크기, false이면 rowheight = 0이 되는 방식입니다.

profile
iOS developer

0개의 댓글