오랜만에 UIKit을 다시 써보면서 새로운 개념들도 알아가고 있고 소마에서 배웠던 기술들도 적용해보고 있습니다. 이러한 것들에 대해 개념을 자세히 설명하기보다는 실제로 어떻게 썼는지, 어떤 점이 좋았는지 정리해보고자 합니다!
TableView와 CollectionView는 UIKit에서 리스트 데이터를 표현하기 위해 자주 쓰입니다. 다만 차이점이 있는데 먼저 요약하자면 아래와 같습니다.
TableView
CollectionView
TableView와 CollectionView 중 어떤 것을 사용햐여 할지 정답은 없지만 아무래도 각각의 특성 상 사용하면 좋은 환경은 있습니다.
TableView는 커스터마이징이 비교적 어려운 대신 구현이 복잡하지는 않기 때문에 빠르고 간단하게 데이터를 표현하고 싶을 때 사용 가능합니다.
CollectionView는 커스터마이징 요소가 많기 때문에 구현이 복잡할 순 있지만 그만큼 원하는 형태로 표현하고 싶을 때 사용 가능합니다.
각각의 요소를 코드로 어떻게 구현했는지 설명드리겠습니다. 프로젝트 전체 보기
TableView를 사용하고 싶을 때 해야하는 것들을 크게 나눠보자면 아래와 같습니다.
// TableView 보여줄 UIViewController 정의
import UIKit
import SnapKit
final public class BasketView: UIViewController {
private let contents = [...]
public init() {
super.init(nibName: nil, bundle: nil)
setView()
setLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// TableView 정의
lazy private var tableView: UITableView = {
let tableView = UITableView()
tableView.dataSource = self
tableView.delegate = self
tableView.register(WishListBlock.self, forCellReuseIdentifier: WishListBlock.id)
return tableView
}()
private func setView() {
view.addSubview(tableView)
}
private func setLayout() {
tableView.snp.makeConstraints { make in
...
}
}
}
// 데이터소스 정의
extension BasketView: UITableViewDataSource {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contents.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let content = contents[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: WishListBlock.id) as! WishListBlock
cell.setCell(title: content.title, price: content.price)
return cell
}
}
extension BasketView: UITableViewDelegate {
...
}
// TableView에 쓰이는 Cell 정의
import UIKit
final public class WishListBlock: UITableViewCell {
static let id = "WishListBlock"
override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setView()
setLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy private var title: UILabel = {
let title = UILabel()
...
return title
}()
lazy private var price: UILabel = {
let price = UILabel()
...
return price
}()
private func setView() {
addSubview(title)
addSubview(price)
}
private func setLayout() {
title.snp.makeConstraints { make in
...
}
price.snp.makeConstraints { make in
...
}
}
func setCell(title: String, price: Int) {
self.title.text = title
self.price.text = "\(price)원"
}
}
TableView 정의를 할 때는 dataSource와 delegate를 연결하는 것을 까먹지 않도록 주의해야합니다. 또한, 커스텀 셀을 쓴다면 셀을 재사용할 수 있도록 미리 등록도 해줘야합니다. 추가적으로 여러 옵션들을 줄 수 있는데 예를 들면 아래와 같이 표준 스타일도 지정 가능합니다.
tableview.style = .plain
TableViewCell 같은 경우 저는 UITableViewCell을 상속하고 커스텀해서 사용하고 있는데, 원한다면 아래와 같이 표준 스타일도 사용 가능합니다.
let cell = UITableViewCell(style: .default, reuseIndentifier: "DefaultCell")
UITableViewDataSource 정의할 때는 섹션마다 몇개의 데이터를 보여줄 것인지와 어떤 셀을 보여줄 것인지는 필수적으로 정해야합니다. 그 외에 여러 가지 설정할 수 있는 것들이 있는데, 예를 들어 섹션을 몇개로 할지도 설정 가능합니다.
public func numberOfSections(in tableView: UITableView) -> Int
UITableViewDelegate는 사실 필수적으로 정의할 필요는 없긴 합니다. 다만 여러 편리한 기능들을 제공하는데 대표적으로 셀을 눌렀을 때 어떤 동작이 일어나게 할 것인지 결정할 수 있습니다.
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
CollectionView를 사용할 때 해야하는 작업도 TableView와 비슷하지만 큰 차이점이라고 한다면 레이아웃을 정의해줘야합니다.
// CollectionView를 보여줄 UIViewController 정의
import UIKit
import SnapKit
final public class ShoppingListView: UIViewController {
private let contents = [...]
public init() {
super.init(nibName: nil, bundle: nil)
setView()
setLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// CollectionViewLayout 정의
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
...
}
// CollectionView 정의
private lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(AllProductsBlock.self, forCellWithReuseIdentifier: AllProductsBlock.id)
return collectionView
}()
private func setView() {
view.addSubview(collectionView)
}
private func setLayout() {
collectionView.snp.makeConstraints { make in
...
}
}
}
// 데이터소스 정의
extension ShoppingListView: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return contents.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let content = contents[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AllProductsBlock.id, for: indexPath) as! AllProductsBlock
cell.setCell(title: content.title, price: content.price)
return cell
}
}
extension ShoppingListView: UICollectionViewDelegate {
...
}
// CollectionView에 쓰이는 Cell 정의
import UIKit
final public class AllProductsBlock: UICollectionViewCell {
static let id = "AllProductsBlock"
override public init(frame: CGRect) {
super.init(frame: frame)
setView()
setLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy private var title: UILabel = {
let title = UILabel()
...
return title
}()
lazy private var price: UILabel = {
let price = UILabel()
...
return price
}()
private func setView() {
addSubview(title)
addSubview(price)
}
private func setLayout() {
title.snp.makeConstraints { make in
...
}
price.snp.makeConstraints { make in
...
}
}
func setCell(title: String, price: Int) {
self.title.text = title
self.price.text = "\(price)원"
}
}
레이아웃 같은 경우 CollectionView에서는 대표적으로 두가지 레이아웃을 사용 가능합니다.
이런 레이아웃에 여러 설정들을 함으로써 셀의 간격이나 크기 등을 조정할 수 있는데, 자세한 내용은 추후 포스팅에서 다루겠습니다.
이외에는 TableView와 거의 비슷하다고 생각하면 됩니다. 다만 표준 스타일이 없기에 UICollectionViewCell을 상속한 커스텀 셀을 반드시 만들어줘야한다는 점 등을 주의해야합니다.
이상으로 TableView와 CollectionView에 대해 알아봤습니다. 저도 현재 배우고 있는 입장이기 때문에 틀린 개념, 부족한 개념들이 있을 수 있습니다. 혹시라도 그런 부분이 있다면 언제든지 지적해주셔도 됩니다. 긴 글 읽어주셔서 감사합니다. 😊