Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 클린 코드
- 스위프트
- map
- 리펙터링
- Clean Code
- Refactoring
- combine
- Protocol
- Observable
- MVVM
- 리팩토링
- Xcode
- uiscrollview
- 애니메이션
- clean architecture
- RxCocoa
- tableView
- Human interface guide
- ios
- SWIFT
- 리펙토링
- uitableview
- ribs
- swiftUI
- rxswift
- collectionview
- UITextView
- swift documentation
- HIG
- UICollectionView
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] TableView - section, header, footer 사용방법 본문
Header, Section, Footer 개념
- Section이 존재하고 Section 하나당 각각 Header와 Footer가 존재
- 예제에서는 Header가 Section1에서만 표출
- Footer는 Section3에서만 표출하지 않는 형태
데이터 Entity 정의
import Foundation
typealias MyItemList = [MyItem]
struct MyItem {
enum MyType {
case a
case b
case c
case d
case e
}
let title: String
let type: MyType
}
BaseView 정의
- BaseTableViewCell
class BaseTableViewCell<T>: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configure()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure() {
selectionStyle = .none
}
var model: T? {
didSet {
if let model = model {
bind(model)
}
}
}
func bind(_ model: T?) {}
}
- BaseTableViewHeaderFooterView
class BaseTableViewHeaderFooterView<T>: UITableViewHeaderFooterView {
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
configure()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var model: T? {
didSet {
if let model = model {
bind(model)
}
}
}
func configure() {}
func bind(_ model: T) {}
}
CustomView 정의
- MyTableViewCell
import UIKit
import SnapKit
class MyTableViewCell: BaseTableViewCell<MyItem> {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 16)
label.textColor = .black
return label
}()
override func configure() {
super.configure()
backgroundColor = .white
addSubviews()
makeConstraints()
}
private func addSubviews() {
contentView.addSubview(titleLabel)
}
private func makeConstraints() {
titleLabel.snp.makeConstraints { maker in
maker.centerY.equalToSuperview()
maker.leading.equalToSuperview().inset(24)
}
}
override func bind(_ model: MyItem?) {
super.bind(model)
titleLabel.text = model?.title
}
}
- MyTableViewHeader
import UIKit
import RxSwift
class MyTableViewHeader: BaseTableViewHeaderFooterView<Void> {
var disposeBag = DisposeBag()
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = .white
return view
}()
private lazy var myImageView: UIImageView = {
let view = UIImageView()
view.image = #imageLiteral(resourceName: "background")
return view
}()
private lazy var disclosureIndicatorButton: UIButton = {
let button = UIButton()
button.setImage(#imageLiteral(resourceName: "btnDisclosureIndicator"), for: .normal)
return button
}()
private lazy var separatorView: UIView = {
let view = UIView()
view.backgroundColor = .separator
return view
}()
override func configure() {
super.configure()
backgroundColor = .white
addSubviews()
makeConstraints()
}
private func addSubviews() {
addSubview(containerView)
containerView.addSubview(myImageView)
containerView.addSubview(disclosureIndicatorButton)
containerView.addSubview(separatorView)
}
private func makeConstraints() {
containerView.snp.makeConstraints { maker in
maker.edges.equalToSuperview()
}
myImageView.snp.makeConstraints { maker in
maker.top.greaterThanOrEqualToSuperview().inset(24)
maker.leading.equalToSuperview().inset(24)
maker.bottom.equalTo(separatorView).offset(-24)
}
disclosureIndicatorButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
disclosureIndicatorButton.snp.makeConstraints { maker in
maker.trailing.equalToSuperview().inset(24)
maker.centerY.equalTo(myImageView)
maker.leading.greaterThanOrEqualTo(myImageView.snp.trailing).offset(30)
}
separatorView.snp.makeConstraints { maker in
maker.height.equalTo(1)
maker.leading.trailing.equalToSuperview().inset(16)
maker.bottom.equalToSuperview().inset(12)
}
}
}
- SeparatorTableViewFooter
class SeparatorTableViewFooter: BaseTableViewHeaderFooterView<Void> {
lazy var separatorView: UIView = {
let view = UIView()
view.backgroundColor = .separator
return view
}()
override func configure() {
super.configure()
addSubviews()
makeConstraints()
}
private func addSubviews() {
contentView.addSubview(separatorView)
}
private func makeConstraints() {
separatorView.snp.makeConstraints { maker in
maker.height.equalTo(1)
maker.leading.trailing.greaterThanOrEqualToSuperview().inset(16)
maker.centerY.equalToSuperview()
}
}
}
tableView 적용
- 핵심
- tableView.register()를 Header, Cell, Footer 각각 해주는 부분
- Header와 Footer 각각 생성하고 height 설정하는 delegate 메서드
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
// business logic에 위치해야할 값
var dataSource: [MyItemList] = []
lazy var tableView: UITableView = {
// header 영역도 같이 scroll되기 위해 .grouped로 설정
let view = UITableView(frame: .zero, style: .grouped)
view.dataSource = self
view.delegate = self
view.estimatedRowHeight = 44
view.rowHeight = 48
view.separatorStyle = .none
view.backgroundColor = .white
view.register(MyTableViewCell.self, forCellReuseIdentifier: "MyTableViewCell")
view.register(MyTableViewHeader.self, forHeaderFooterViewReuseIdentifier: "MyTableViewHeader")
view.register(SeparatorTableViewFooter.self, forHeaderFooterViewReuseIdentifier: "SeparatorTableViewFooter")
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupDataSource()
addSubviews()
configure()
}
private func addSubviews() {
view.addSubview(tableView)
}
private func configure() {
tableView.snp.makeConstraints { maker in
maker.leading.top.bottom.equalToSuperview()
maker.trailing.equalToSuperview().inset(120)
}
}
}
// business logic에 위치해야할 값들
extension ViewController {
var numberOfSections: Int {
return dataSource.count
}
func setupDataSource() {
// Section 1
let item1 = MyItem(title: "1", type: .a)
let item2 = MyItem(title: "2", type: .b)
// Section 2
let item3 = MyItem(title: "3", type: .c)
let item4 = MyItem(title: "4", type: .d)
let item5 = MyItem(title: "5", type: .e)
// Section 3
let item6 = MyItem(title: "6", type: .c)
let item7 = MyItem(title: "7", type: .d)
let item8 = MyItem(title: "8", type: .e)
let section1 = [item1, item2]
let section2 = [item3, item4, item5]
let section3 = [item6, item7, item8]
dataSource = [section1, section2, section3]
}
func numberOfRows(in section: Int) -> Int {
return dataSource[section].count
}
func getItem(in section: Int, _ row: Int) -> MyItem {
return dataSource[section][row]
}
func didSelectRowAt(in section: Int, _ row: Int) {
print("did select type = \(dataSource[section][row].type)")
}
}
- 핵심: dataSource, delegate
- Header와 Footer도 일반 Cell과 동일하게 객체를 만들고 반환 및 height 조정
extension ViewController: UITableViewDataSource, UITableViewDelegate {
// TableView DataSource
func numberOfSections(in tableView: UITableView) -> Int {
return numberOfSections
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfRows(in: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell") as? MyTableViewCell else {
fatalError("MyTableViewCell not dequeued property")
}
let item = getItem(in: indexPath.section, indexPath.row)
cell.model = item
return cell
}
// Header & Footer
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard section == 0, let headerCell = tableView.dequeueReusableHeaderFooterView(withIdentifier: "MyTableViewHeader") as? MyTableViewHeader else { return nil }
let tapGesture = UITapGestureRecognizer()
headerCell.addGestureRecognizer(tapGesture)
tapGesture.rx.event
.asDriver()
.drive(onNext: { _ in
print("did tap first header")
}).disposed(by: headerCell.disposeBag)
return headerCell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
// Header 영역 크기 = 140(separator 상단) + 12(separator 하단)
return section == 0 ? 152 : 0
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
// 마지막 section은 footer 미표출
guard section != numberOfSections - 1 else {
return nil
}
return tableView.dequeueReusableHeaderFooterView(withIdentifier: "SeparatorTableViewFooter")
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
// footer 영역 크기 = 12 (마지막 section의 footer 크기는 0)
return section == numberOfSections - 1 ? 0 : 12
}
// Select Cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
didSelectRowAt(in: indexPath.section, indexPath.row)
}
}
* source code: https://github.com/JK0369/TableViewEx
'iOS 응용 (swift)' 카테고리의 다른 글
Comments