chaneeii/iOS-Study-Log

UITableView (테이블뷰)

Opened this issue · 5 comments

Section 다루기

section : https://hururuek-chapchap.tistory.com/153

TableView Section 간 Space 를 설정하는 법

  let spaceBetweenSections = 300.0

  func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
      return CGFloat(spaceBetweenSections / 2)
  }
  
  func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
      return CGFloat(spaceBetweenSections / 2)
  }
    

TableView 속성다루기

테이블뷰 셀 구분선 없애기

tableView.separatorStyle = .none

Scroll 막기

  • 두개 차이 제대로 알아보기
tableView.alwaysBounceVertical = false
tableView.isScrollEnabled = false

상단여백제거

self.tableHeaderView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: CGFloat.leastNonzeroMagnitude))

간단하게 Expandable UITableViewCell 만들기

SampleCode

TestTableViewController
//
//  TableTestViewController.swift
//  DonWorry
//
//  Created by Chanhee Jeong on 2022/08/13.
//  Copyright © 2022 Tr-iT. All rights reserved.
//

import UIKit

struct DecoItem {
  let title: String
  let content: String
  var isHidden = true
 
  init(title: String,
       content: String,
       isHidden: Bool = true){
      self.title = title
      self.content = content
      self.isHidden = isHidden
  }
  
}


class TableTestViewController: UIViewController {
  
  private let tableView = UITableView()
  var decoItems: [DecoItem] = [
      DecoItem(title: "배경 선택", content: "Let’s focus on the cellForRowAt function. After configuring the cell with the title and the description we need to configure the bottomView in order to be hidden when the cell is tapped. We simply set the isHidden property of the bottomView with our value in the array. If you need also to change the icon (up or down) you can set the image based on the isHidden property."),
      DecoItem(title: "날짜 선택", content: "날짜 달력있음"),
      DecoItem(title: "계좌번호 입력 (선택)", content: "계좌번호 셀"),
      DecoItem(title: "파일 추가 (선택)", content: "파일추가 셀"),
  ]

  override func viewDidLoad() {
      super.viewDidLoad()
      view.addSubviews(tableView)
      tableView.translatesAutoresizingMaskIntoConstraints = false
      
      NSLayoutConstraint.activate([
          tableView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
          tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
          tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
          tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 60),
          tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -60),
      ])
      
      self.tableView.register(UINib(nibName: "TestCell", bundle: nil), forCellReuseIdentifier: "TestCell")
      self.tableView.dataSource = self
      self.tableView.delegate = self
      self.tableView.estimatedRowHeight = 50
      self.tableView.rowHeight = UITableView.automaticDimension
      self.tableView.separatorStyle = .none
      
  }
  

}

extension TableTestViewController: UITableViewDelegate {
  
  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
      let expandableItem = self.decoItems[indexPath.row]
      if expandableItem.isHidden {
          return 48
      }else {
          return 300
      }
  }
  
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      
      decoItems[indexPath.row].isHidden = !(decoItems[indexPath.row].isHidden)// toggle
      tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
      
  }
}


extension TableTestViewController: UITableViewDataSource {
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return decoItems.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      
      let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as! TestCell
      cell.selectionStyle = .none
      let expandableItem = self.decoItems[indexPath.row]
      
      cell.topTitleLabel.text = expandableItem.title
      cell.bottomDescriptionLabel.text = expandableItem.content
      cell.bottomView.isHidden = expandableItem.isHidden
      
      cell.chevronImageView.image = UIImage(systemName: expandableItem.isHidden ? "chevron.down" : "chevron.up")
      return cell
      
  }
  
}
TestCell 스크린샷 2022-08-14 오전 2 08 55
//
//  TestCell.swift
//  DonWorry
//
//  Created by Chanhee Jeong on 2022/08/13.
//  Copyright © 2022 Tr-iT. All rights reserved.
//

import UIKit

class TestCell: UITableViewCell {
  
  @IBOutlet private weak var containerStackView: UIStackView!
  @IBOutlet weak var topTitleLabel: UILabel!
  @IBOutlet weak var bottomDescriptionLabel: UILabel!
  @IBOutlet weak var chevronImageView: UIImageView!
  @IBOutlet weak var bottomView: UIView! {
      didSet {
          bottomView.isHidden = true
      }
  }
  
  @IBOutlet weak var button: UIButton!
  
  override func awakeFromNib() {
      super.awakeFromNib()
      self.containerStackView.layer.cornerRadius = 10
      self.containerStackView.layer.masksToBounds = true
      // Initialization code
  }

  override func setSelected(_ selected: Bool, animated: Bool) {
      super.setSelected(selected, animated: animated)

      // Configure the view for the selected state
  }

  @IBAction func buttonDidTap(_ sender: Any) {
      print("버튼이 눌림")
  }
  
}

section 없이 expandable tableview cells 을 만들기 위해서는 UIStackView를 사용하면 된다!

먼저 UITableViewCell 을 설정해주자

스크린샷 2022-08-13 오후 9 59 32

xib 파일이 다음과 같이 생겨야한다, ContentView 안에 UIStackView 를 넣고 contstraints 를 추가해야한다.
StackView 안에서 헤더가 될 부분과 / content 가 들어갈 부분을 같은 수준의 계층에 있게 쌓아야 한다.
이때 content 가 들어갈 부분을 bottom 뷰라고 명명하겠다.

코드는 다음과 같을 것이다.

class TestCell: UITableViewCell {
    
    @IBOutlet private weak var containerStackView: UIStackView!
    @IBOutlet weak var topTitleLabel: UILabel!
    @IBOutlet weak var bottomDescriptionLabel: UILabel!
    @IBOutlet weak var chevronImageView: UIImageView!
    @IBOutlet weak var bottomView: UIView! {
        didSet {
            bottomView.isHidden = true
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
}

근데, bottomView 에는 isHidden 이라는 didSet을 넣어 이것이 바뀌면 view 가 사라지게 해야한다.

다음으로 UIViewCotnroller 를 보자

image

위 코드에서 detailsTableView 가 우리의 UITableView가 될 것이다.
cell 을 register 하고 UITableViewDelegate 와 UITableViewDatasource을 채택하여 deleagate 와 datasource 를 설정한다.

그리고 estimatedRowHeight을 100 으로 편의상추가하고, rowHeight 을 automaticDimension 로 해서 cell 의 컨텐츠에 높이가 맞도록 해준다.

UITableViewDelegate와 UITableViewDatasource을 설정해주기

image

먼저 UITableViewDelegate 쪽을 보면, didSelectRowAt 메소드만 설정해주면된다.
이 예제에서는 expandable cell 의 상태를 유지하기위한 model들의 array 가 있다.
그래서 cell 을 누르면 isHidden 을 바꿀 수 있도록 하였고, 여기서 reloadRows 함수를 호출하는 것이 트릭이다.

tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.automatic)

다음은 Cell을 expand 하는 trick을 보자
UITableViewDataSource 을 구현해보자.

image

numberOfRowsInSection 은 단순히 array 의 개수를 셀 것이다.
cellForRowAt 함수에 집중해보자!

cell 을 title 과 description으로 configuring 하고나서, 우리는 bottomView 가 tap 되면 hidden 되도록 설정해야한다.
우리는 이때 간단하게 우리 model array 의 value 와 bottomView의 isHidden 속성을 set 할 것이다.

만약 icon 의 방향 (위,아래) 설정등이 필요하다면, isHidden property 에 기반하여 바뀌도록 설정하면 된다.

DataSource 와 Delegate 차이

DataSources 의 메소드

protocol UITableViewDataSource
 @required 
 // 특정 위치에 표시할 셀을 요청하는 메서드
 func tableView(UITableView, cellForRowAt: IndexPath) 
 
 // 각 섹션에 표시할 행의 개수를 묻는 메서드
 func tableView(UITableView, numberOfRowsInSection: Int)
 
 @optional
 // 테이블뷰의 총 섹션 개수를 묻는 메서드
 func numberOfSections(in: UITableView)
 
 // 특정 섹션의 헤더 혹은 푸터 타이틀을 묻는 메서드
 func tableView(UITableView, titleForHeaderInSection: Int)
 func tableView(UITableView, titleForFooterInSection: Int)
 
 // 특정 위치의 행을 삭제 또는 추가 요청하는 메서드
 func tableView(UITableView, commit: UITableViewCellEditingStyle, forRowAt: IndexPath)
 
 // 특정 위치의 행이 편집 가능한지 묻는 메서드
 func tableView(UITableView, canEditRowAt: IndexPath)

 // 특정 위치의 행을 재정렬 할 수 있는지 묻는 메서드
 func tableView(UITableView, canMoveRowAt: IndexPath)
 
 // 특정 위치의 행을 다른 위치로 옮기는 메서드
 func tableView(UITableView, moveRowAt: IndexPath, to: IndexPath)

Delegate의 메소드

protocol UITableViewDelegate
// 특정 위치 행의 높이를 묻는 메서드
 func tableView(UITableView, heightForRowAt: IndexPath)
 // 특정 위치 행의 들여쓰기 수준을 묻는 메서드
 func tableView(UITableView, indentationLevelForRowAt: IndexPath)

 // 지정된 행이 선택되었음을 알리는 메서드
 func tableView(UITableView, didSelectRowAt: IndexPath)

 // 지정된 행의 선택이 해제되었음을 알리는 메서드
 func tableView(UITableView, didDeselectRowAt: IndexPath)

 // 특정 섹션의 헤더뷰 또는 푸터뷰를 요청하는 메서드
 func tableView(UITableView, viewForHeaderInSection: Int)
 func tableView(UITableView, viewForFooterInSection: Int)

 // 특정 섹션의 헤더뷰 또는 푸터뷰의 높이를 물어보는 메서드
 func tableView(UITableView, heightForHeaderInSection: Int)
 func tableView(UITableView, heightForFooterInSection: Int)

 // 테이블뷰가 편집모드에 들어갔음을 알리는 메서드
 func tableView(UITableView, willBeginEditingRowAt: IndexPath)

 // 테이블뷰가 편집모드에서 빠져나왔음을 알리는 메서드
 func tableView(UITableView, didEndEditingRowAt: IndexPath?)

Reload

reloadData() : Reloads the rows and sections of the table view.

  • 섹션과 관계 없이 모든 데이터를 갱신 ➡️ 비효율적
func reloadData()

reloadSections(_:with:) : Reloads the specified sections using a given animation effect.

  • 특정 섹션의 데이터만 갱신, 애니메이션 적용
func reloadSections(_ sections: IndexSet, 
               with animation: UITableView.RowAnimation)
// 섹션에 관계없이 모든 데이터를 reload
self.tableView.reloadData()

// 2번째 section의 데이터만 reload, 애니메이션 적용
self.tableView.reloadSections(IndexSet(2...2 ), with: UITableView.RowAnimation.automatic )

내부 크기에 따라 tableview 사이즈 다르게

https://ios-development.tistory.com/400