iOS/iOS
[iOS] CustomCell을 등록하고 재사용할때 실수를 줄이기 위한 방법
- -
왜 CustomCell을 사용할때 오류가 발생할까?
일반적으로 TableViewCell이나 CollectionViewCell을 만들고 사용할때를 보자
셀 등록
tableView.register(UINib(nibName: "MyTableViewCell", bundle: nil), forCellReuseIdentifier: "MyTableViewCell")
collectionView.register(UINib(nibName: "MyCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "MyCollectionViewCell")
재사용
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell") as? MyTableViewCell else { return UITableViewCell() }
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath) as? MyCollectionViewCell else { return UICollectionViewCell() }
코드를 보면 셀 등록시 nibName, ReuseIdentifier와, 셀 재사용시 withIdentifier까지 총 3번의 String값을 넣어주어야 한다.
크게 문제가 없어보이지만 코딩을 하다보면 오류가 발생하는데
- nibName, ReuseIdentifier, withIdentifier를 하드코딩하게 되는 경우 오타로 인하여 해당셀의 존재여부를 확인할 수 없어 앱이 죽는 경우가 있다.
- id를 수정하게된다면 세곳 모두를 바꾸어 주어야 한다.
- 커스텀 셀을 사용하는 경우에는 캐스팅을 해주어야하는데 실패할 확률이 있다
위에서 나열한 오류 외에도 다양한 오류가 존재한다. 이러한 오류를 방지하기 위해 프로토콜을 만들어서 CustomCell에 채택하는 것이다.
오류를 줄이는 방법
프로토콜 정의
import UIKit
protocol UICellRegister {
// cellId
static var cellId: String { get }
// xib파일로 CustomCell을 만들었는지 유무
static var isFromNib: Bool { get }
// CustomCell 등록
static func register(target: UIScrollView)
// CustomCell 꺼내오기
static func dequeueReusableCell(target: UIScrollView, indexPath: IndexPath?) -> Self
}
extension UICellRegister where Self: UITableViewCell {
// TableView에 CustomCell 등록
static func register(target: UIScrollView) {
guard let tableView = target as? UITableView else { fatalError("Error! Not TableView") }
// xib파일로 생성한 Cell이라면
if self.isFromNib { tableView.register(UINib(nibName: self.cellId, bundle: nil), forCellReuseIdentifier: self.cellId) }
// 코드로만 생성한 Cell이라면
else { tableView.register(Self.self, forCellReuseIdentifier: self.cellId) }
}
// CustomCell 꺼내오기
static func dequeueReusableCell(target: UIScrollView, indexPath: IndexPath?) -> Self {
guard let tableView = target as? UITableView else { fatalError("Error! \(self.cellId)")}
let cell: UITableViewCell?
// indexPath가 있을 때
if let indexPath = indexPath { cell = tableView.dequeueReusableCell(withIdentifier: self.cellId, for: indexPath) }
// indexPath가 없을 때
else { cell = tableView.dequeueReusableCell(withIdentifier: self.cellId) }
guard let cell = cell as? Self else { fatalError("Error! \(self.cellId)" ) }
return cell
}
}
extension UICellRegister where Self: UICollectionViewCell {
// CollectionView에 CustomCell 등록
static func register(target: UIScrollView) {
guard let collectionView = target as? UICollectionView else { fatalError("Error! Not CollectionView") }
// xib파일로 생성한 Cell이라면
if self.isFromNib { collectionView.register(UINib(nibName: self.cellId, bundle: nil), forCellWithReuseIdentifier: cellId) }
// 코드로만 생성한 Cell이라면
else { collectionView.register(Self.self, forCellWithReuseIdentifier: self.cellId) }
}
// CustomCell 꺼내오기
static func dequeueReusableCell(target: UIScrollView, indexPath: IndexPath?) -> Self {
guard let collectionView = target as? UICollectionView, let indexPath = indexPath else { fatalError("Error! \(self.cellId)") }
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellId, for: indexPath) as? Self else { fatalError("Error! \(self.cellId)") }
return cell
}
}
Extension을 사용하여 TableView또는 CollectionView일 때를 나눠서 사용
핵심은 ClassName, NibName, FileName, CellId를 전부 맞춰서 사용하는것
파일명, xib파일의 cell Id 맞추기
커스텀 셀에 프로토콜 채택
TableView, CollectionView에서 Cell 추가
셀 재사용
같은 방법으로 HeaderFooterView도 설정할 수 있다
UITableViewHeaderFooterView
import UIKit
protocol UITableViewHeaderFooterViewRegister {
static var viewId: String { get }
static var isFromNib: Bool { get }
static func register(tableView: UITableView)
static func dequeueReusableHeaderFooterView(tableView: UITableView) -> Self
}
extension UITableViewHeaderFooterViewRegister where Self: UITableViewHeaderFooterView {
static func register(tableView: UITableView) {
if self.isFromNib {
tableView.register(UINib(nibName: self.viewId, bundle: nil), forHeaderFooterViewReuseIdentifier: self.viewId)
} else {
tableView.register(Self.self, forHeaderFooterViewReuseIdentifier: self.viewId)
}
}
static func dequeueReusableHeaderFooterView(tableView: UITableView) -> Self {
guard let hfview = tableView.dequeueReusableHeaderFooterView(withIdentifier: self.viewId) as? Self else { fatalError("Error! \(self.viewId)") }
return hfview
}
}
UICollectionReusableView
import UIKit
protocol UICollectionReusableViewRegister {
static var viewId: String { get }
static var isFromNib: Bool { get }
static func register(collectionView: UICollectionView, kind: String)
static func dequeueReusableSupplementaryView(collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> Self
}
extension UICollectionReusableViewRegister where Self: UICollectionReusableView {
static func register(collectionView: UICollectionView, kind: String) {
if self.isFromNib {
collectionView.register(UINib(nibName: self.viewId, bundle: nil), forSupplementaryViewOfKind: kind, withReuseIdentifier: self.viewId)
} else {
collectionView.register(Self.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: self.viewId)
}
}
static func dequeueReusableSupplementaryView(collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> Self {
guard let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: self.viewId, for: indexPath) as? Self else {
fatalError("Error \(self.viewId)")
}
return supplementaryView
}
}
TableView Collection header footer 합쳐서
import UIKit
enum ViewType {
case tableViewHeaderFooterView
case collectionViewHeaderView
case collectionViewFooterView
}
protocol UIHeaderFooterViewRegister {
static var viewId: String { get }
static var isFromNib: Bool { get }
static var viewType: ViewType { get }
static func register(target: UIScrollView)
}
extension UIHeaderFooterViewRegister where Self: UITableViewHeaderFooterView {
static func register(target: UIScrollView) {
guard let target = target as? UITableView else { return }
if self.isFromNib {
target.register(UINib(nibName: self.viewId, bundle: nil), forHeaderFooterViewReuseIdentifier: self.viewId)
} else {
target.register(Self.self, forHeaderFooterViewReuseIdentifier: self.viewId)
}
}
}
extension UIHeaderFooterViewRegister where Self: UICollectionReusableView {
static func register(target: UIScrollView) {
guard let target = target as? UICollectionView else { return }
if self.isFromNib {
switch self.viewType {
case .collectionViewHeaderView:
target.register(UINib(nibName: self.viewId, bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: self.viewId)
case .collectionViewFooterView:
target.register(UINib(nibName: self.viewId, bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: self.viewId)
default :
return
}
} else {
switch self.viewType {
case .collectionViewHeaderView:
target.register(Self.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: self.viewId)
case .collectionViewFooterView:
target.register(Self.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: self.viewId)
default :
return
}
}
}
}
최종 코드
UITableViewCellRegister
import UIKit
protocol UITableViewCellReigster {
static var cellId: String { get }
static var isFromNib: Bool { get }
static func register(tableView: UITableView)
static func dequeueReusableCell(tableView: UITableView, indexPath: IndexPath?) -> Self
}
extension UITableViewCellReigster where Self: UITableViewCell {
static func register(tableView: UITableView) {
if self.isFromNib {
tableView.register(UINib(nibName: self.cellId, bundle: nil), forCellReuseIdentifier: self.cellId)
} else {
tableView.register(Self.self, forCellReuseIdentifier: self.cellId)
}
}
static func dequeueReusableCell(tableView: UITableView, indexPath: IndexPath?) -> Self {
var cell: UITableViewCell?
if let indexPath = indexPath {
cell = tableView.dequeueReusableCell(withIdentifier: self.cellId, for: indexPath)
} else {
cell = tableView.dequeueReusableCell(withIdentifier: self.cellId)
}
guard let cell = cell as? Self else { fatalError("Error! \(self.cellId)" ) }
return cell
}
}
UICollectionViewCellRegister
import UIKit
protocol UICollectionViewCellRegister {
// cellId
static var cellId: String { get }
// xib파일로 CustomCell을 만들었는지 유무
static var isFromNib: Bool { get }
// CustomCell 등록
static func register(collectionView: UICollectionView)
// CustomCell 꺼내오기
static func dequeueReusableCell(collectionView: UICollectionView, indexPath: IndexPath) -> Self
}
extension UICollectionViewCellRegister where Self: UICollectionViewCell {
// CollectionView에 CustomCell 등록
static func register(collectionView: UICollectionView) {
// xib파일로 생성한 Cell이라면
if self.isFromNib { collectionView.register(UINib(nibName: self.cellId, bundle: nil), forCellWithReuseIdentifier: cellId) }
// 코드로만 생성한 Cell이라면
else { collectionView.register(Self.self, forCellWithReuseIdentifier: self.cellId) }
}
// CustomCell 꺼내오기
static func dequeueReusableCell(collectionView: UICollectionView, indexPath: IndexPath) -> Self {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellId, for: indexPath) as? Self else { fatalError("Error! \(self.cellId)") }
return cell
}
}
-끗-
'iOS > iOS' 카테고리의 다른 글
[iOS] Delegate 패턴 이해하기 및 Protocol 프로그래밍을 지향하는 이유 (0) | 2023.04.10 |
---|---|
[iOS] TextView placeholder 구현하기 (0) | 2023.03.21 |
[iOS] SwiftUI에서 Info.plist 설정하기 (0) | 2023.03.02 |
[iOS] info.plist에서LightMode, DarkMode 강제하기 (0) | 2023.03.02 |
[iOS] Xcode13 이상 SwiftUI 프로젝트 설정하기 (0) | 2023.03.02 |
Contents
소중한 공감 감사합니다