새소식

인기 검색어

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
    }
}

 

-끗-

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.