📦 Expandable TableView Cell 만들기
마켓컬리 클론코딩을 하다가 카테고리를 접었다가 펴주는 테이블 뷰 기능을 구현하게 되어
공부한 내용을 정리해보려고 합니다!
처음에는 어려울 것 같았는데
생각보다 엄청 간단하게 구현이 되어서 라이브러리를 쓰지 않아도 되겠다 싶었어요!
(맨 아래로 내려가면 full code 보실 수 있습니다!)
테이블 뷰를 위한 세팅을 해주세요~~
첫 번째로, 테이블 뷰에 들어갈 데이터를 위한 구조체를 하나 만들어 줍니다.
struct cellData {
var opened = Bool()
var title = String()
var sectionData = [String]()
}
- opened는 테이블 뷰 셀이 접혔는지 펴졌는지를 확인해주기 위한 변수입니다.
- title은 카테고리에 해당하는 문자열 변수고,
- sectionData는 카테고리 내 아이템들에 해당하는문자열 리스트 변수입니다.
그리고 테이블 뷰에 들어갈 데이터를 위한 리스트를 하나 선언해주고
내부에 데이터를 입력해줍니다.
class ViewController : UIViewController {
// MARK: - Data
var tableViewData = [cellData]() // 리스트 선언 부분
// MARK: - Property
var tableView = UITableView()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// 여기 이 부분에 데이터 넣어줍니다
tableViewData = [cellData(opened: false, title: "Section1", sectionData: ["Cell1", "Cell2", "Cell3"]),
cellData(opened: false, title: "Section2", sectionData: ["Cell1", "Cell2", "Cell3"]),
cellData(opened: false, title: "Section3", sectionData: ["Cell1", "Cell2", "Cell3"]),
cellData(opened: false, title: "Section4", sectionData: ["Cell1", "Cell2", "Cell3"])]
tableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
tableView.delegate = self
tableView.dataSource = self
configureUI()
}
두 번째로, UITableViewDataSource 부분을 채워줍시다~~
1 . Section의 개수
func numberOfSections(in tableView: UITableView) -> Int {
return tableViewData.count
}
2. Row의 개수
- 섹션이 열렸을 때랑 닫혔을 때에 따라 분기 처리를 해줘야 합니다.
- 섹션이 열린 경우에는 내부에 들어갈 데이터 개수에 카테고리 제목 셀까지 하나 추가해줘야 합니다. 그래서 + 1 을 해줍니다.
- 섹션이 닫힌 경우에는 카테고리 제목 셀 하나만 보여주면 되기 때문에 return 1을 해줍니다.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableViewData[section].opened == true {
// tableView Section이 열려있으면 Section Cell 하나에 sectionData 개수만큼 추가해줘야 함
return tableViewData[section].sectionData.count + 1
} else {
// tableView Section이 닫혀있을 경우에는 Section Cell 하나만 보여주면 됨
return 1
}
}
3. Row에 들어갈 Cell 등록하기
- indexPath.row 가 0인 경우.. 즉, 카테고리 제목인 경우에는 title을 뿌려줍니다.
- else 그 외에는 sectionData를 뿌려줘야 합니다.
📍여기서 중요한 점은 sectionData[indexPath.row - 1] 인데요.
- 1 을 해주는 이유는 카테고리 제목 부분은 빼야 하기 때문입니다.
카테고리 제목 부분을 제외하고 그 아래부터 데이터를 뿌려줘야 하기 때문에 -1을 해줍니다!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// section 부분 코드
if indexPath.row == 0 {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
as? TableViewCell else { return UITableViewCell() }
cell.configureUI()
cell.tableLabel.text = tableViewData[indexPath.section].title
return cell
// sectionData 부분 코드
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
as? TableViewCell else { return UITableViewCell() }
cell.configureUI()
cell.tableLabel.text = tableViewData[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
세 번째로, 셀 선택 시 접었다 펴지는 부분을 구현해줍니다~~
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 셀 선택 시 회색에서 다시 변하게 해주는 것
tableView.deselectRow(at: indexPath, animated: true)
// section 부분 선택하면 열리게 설정
if indexPath.row == 0 {
// section이 열려있다면 다시 닫힐 수 있게 해주는 코드
tableViewData[indexPath.section].opened = !tableViewData[indexPath.section].opened
// 모든 데이터를 새로고침하는 것이 아닌 해당하는 섹션 부분만 새로고침
tableView.reloadSections([indexPath.section], with: .none)
// sectionData 부분을 선택하면 아무 작동하지 않게 설정
} else {
print("이건 sectionData 선택한 거야")
}
print([indexPath.section], [indexPath.row])
}
didSelectRowAt 함수를 사용하면 셀 선택에 따른 동작 부분을 구현할 수 있죠?
카테고리 제목 부분을 눌렀을 때 셀이 펼쳐지는 걸 원하기 때문에 분기처리를 꼭 해줘야 합니다!
(안하면 카테고리 내부 아이템을 눌렀을 때도 펼쳐진다는 점..!)
1️⃣ tableViewData[indexPath.section].opened = !tableViewData[indexPath.section].opened
이 부분 코드는 아래 코드와 같은 의미입니다.
if tableViewData[indexPath.section].opened == true {
// 그리고 펼쳐져 있을 때 셀을 누르면 다시 닫혀줘야 하기 때문에 false로 바꿔줘야 함..
tableViewData[indexPath.section].opened = false
} else {
// 처음 기본값이 false니까 얘를 펼쳐주려면 true로 바꿔줘야 함..
tableViewData[indexPath.section].opened = true
}
2️⃣ tableView.reloadSections([indexPath.section], with: .none)
이 부분은 변경하고 나서 해당하는 섹션 부분만 데이터를 새로고침 해주는 코드입니다.
끝입니다! 간단하져?
생각보다 엄청 간단해요~!~!
결과 화면입니다~
full code
- 저는 스냅킷을 사용했고 스토리보드를 쓰지 않아서 따로 테이블 뷰 셀 코드도 추가해두겠습니다 ~
- 깃허브 링크도 걸어두겠습니다 : 🔗🔗🔗
< ViewController >
import UIKit
import SnapKit
struct cellData {
var opened = Bool()
var title = String()
var sectionData = [String]()
}
class ViewController : UIViewController {
// MARK: - Data
var tableViewData = [cellData]()
// MARK: - Property
var tableView = UITableView()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableViewData = [cellData(opened: false, title: "Section1", sectionData: ["Cell1", "Cell2", "Cell3"]),
cellData(opened: false, title: "Section2", sectionData: ["Cell1", "Cell2", "Cell3"]),
cellData(opened: false, title: "Section3", sectionData: ["Cell1", "Cell2", "Cell3"]),
cellData(opened: false, title: "Section4", sectionData: ["Cell1", "Cell2", "Cell3"])]
tableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
tableView.delegate = self
tableView.dataSource = self
configureUI()
}
// MARK: - UI
func configureUI() {
view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.top.leading.bottom.trailing.equalToSuperview()
}
}
}
// MARK: - UITableViewDelegate
extension ViewController : UITableViewDelegate {
}
// MARK: - UITableViewDataSource
extension ViewController : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return tableViewData.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableViewData[section].opened == true {
// tableView Section이 열려있으면 Section Cell 하나에 sectionData 개수만큼 추가해줘야 함
return tableViewData[section].sectionData.count + 1
} else {
// tableView Section이 닫혀있을 경우에는 Section Cell 하나만 보여주면 됨
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// section 부분 코드
if indexPath.row == 0 {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
as? TableViewCell else { return UITableViewCell() }
cell.configureUI()
cell.tableLabel.text = tableViewData[indexPath.section].title
return cell
// sectionData 부분 코드
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
as? TableViewCell else { return UITableViewCell() }
cell.configureUI()
cell.tableLabel.text = tableViewData[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 셀 선택 시 회색에서 다시 변하게 해주는 것
tableView.deselectRow(at: indexPath, animated: true)
// section 부분 선택하면 열리게 설정
if indexPath.row == 0 {
// section이 열려있다면 다시 닫힐 수 있게 해주는 코드
tableViewData[indexPath.section].opened = !tableViewData[indexPath.section].opened
// 모든 데이터를 새로고침하는 것이 아닌 해당하는 섹션 부분만 새로고침
tableView.reloadSections([indexPath.section], with: .none)
// sectionData 부분을 선택하면 아무 작동하지 않게 설정
} else {
print("이건 sectionData 선택한 거야")
}
print([indexPath.section], [indexPath.row])
}
}
< TableViewCell >
import UIKit
import SnapKit
class TableViewCell: UITableViewCell {
static let identifier = "TableViewCell"
// MARK: - Property
let tableLabel : UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 15, weight: .regular)
return label
}()
// MARK: - configureUI
func configureUI() {
addSubview(tableLabel)
tableLabel.snp.makeConstraints { (make) in
make.centerY.equalToSuperview()
make.leading.equalTo(20)
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
'⭐️ 개발 > iOS & Swift' 카테고리의 다른 글
[iOS] Alamofire Multipart/formdata를 통한 이미지 서버 통신 (3) | 2021.07.19 |
---|---|
[iOS] DateFormatter (2) | 2021.06.24 |
[iOS] UIColor Extension 파일 만드는 법 (feat.Zeplin) (1) | 2021.06.07 |
[iOS] NSMutableAttributedString : 문자열 특정 부분 색 바꿔주고 싶을 때 (0) | 2021.06.07 |
[iOS] Then / DuctTape 라이브러리 사용기 (0) | 2021.06.07 |