여러 종류의 Cell을 한 UITableView에서 사용하기 | UITableView with different kind of cells

이남준·2021년 3월 24일
1
post-thumbnail

여러 종류의 Custom Cell을 한 UITableView에서 사용하기

복잡해지는 ViewController

한 테이블 뷰에서 이미지를 포함한 셀, 텍스트만 포함하는 셀, 여러 버튼이 있는 셀 등 여러 셀을 다루다 보면

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let item = items[indexPath.row]
        let cell: UITableViewCell
        
        if item.isPhotoItem {
            cell = tableView.dequeueReusableCell(withIdentifier: photoCellIdentifier, for: indexPath)
            // Configure photo cell...
            
        } else if item.isVideoItem {
            cell = tableView.dequeueReusableCell(withIdentifier: videoCellIdentifier, for: indexPath)
            // Configure video cell...
            
        } else {
            cell = tableView.dequeueReusableCell(withIdentifier: textCellIdentifier, for: indexPath)
            // Configure text cell...
            
        }
        
        return cell
    }

이런식으로 tableView(_cellForRowAt) 안에 if-else 문이 매우 많아지고, ViewController 가 점점 비대해지고 가독성이 떨어집니다.

CellController Protocol

protocol TableCellController {
    static func registerCell(on tableView: UITableView)
    func cellFromTableView(_ tableView: UITableView, forIndexPath indexPath: IndexPath) -> UITableViewCell
    func didSelectCell()
}

// cell 이 어떤 셀인지
protocol ListItem {
    var isPhotoItem: Bool {get}
    var isVideoItem: Bool {get}
    var isTextItem: Bool {get}
}

ViewController를 가볍게 하고, 가독성을 좋게 하기 위해서는 cell이 스스로 일하게 해야합니다.
cell의 xib를 register, deque, UI 구성 등을 위의 protocol을 통해 cell이 직접 수행하도록 합니다.

위 프로토콜을 따르는 cell controller의 에는

class PhotoTableCellController: TableCellController {
    
    fileprivate let item: ListItem
    
    init(item: ListItem) {
        self.item = item
    }
    
    fileprivate static var cellIdentifier: String {
        return String(describing: type(of: PhotoTableViewCell.self))
    }
    
    static func registerCell(on tableView: UITableView) {
        tableView.register(UINib(nibName: cellIdentifier, bundle: Bundle(for: PhotoTableViewCell.self)), forCellReuseIdentifier: cellIdentifier)
    }
    
    func cellFromTableView(_ tableView: UITableView, forIndexPath indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: type(of: self).cellIdentifier, for: indexPath) as! PhotoTableViewCell
        
        // Configure photo cell...
        
        return cell
    }
    
    func didSelectCell() {
        // Do something for photo...
    } 
}

이런 cell controller를 사용할 셀 마다 정의 함으로써, view controller에서 하던 일을 가져올 수 있습니다.

Cell Controller Factory

tableView에 여러 종류의 셀을 사용하면, 그 셀의 종류의 갯수만큼, cell controller의 갯수도 늘어나는데

cellController1 = someCellController()
cellController2 = otherCellController()
cellController3 = differentCellController()
...

이런식으로 View Controller 내에서 하나씩 생성을 해주면 또 View Controller가 커지는 문제가 있고, 가독성이 떨어지기 때문에 Factory pattern을 통해 내가 사용하고 싶은 셀들의 Cell Controller를 한번에 생성하여 가져올 수 있습니다.

class MyCellControllerFactory {
    
    // xib 등록
    func registerCells(on tableView: UITableView) {
        PhotoTableCellController.registerCell(on: tableView)
        VideoTableCellController.registerCell(on: tableView)
        TextTableCellController.registerCell(on: tableView)
    }
    
    func cellControllers(with items: [ListItem]) -> [TableCellController] {
        // items에 맞는 Cell Controller를 모두 생성해서 리턴
        return items.map { item in
            
            if item.isPhotoItem {
                return PhotoTableCellController(item: item)
            } else if item.isVideoItem {
                return VideoTableCellController(item: item)
            } else {
                return TextTableCellController(item: item)
            }
        }
    }
}

View Controller


class MyViewController: UITableViewController {
    
    fileprivate var cellControllers = [TableCellController]()
    fileprivate let cellControllerFactory = MyCellControllerFactory()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        cellControllerFactory.registerCells(on: tableView)
    }
    
    //...
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return cellControllers[indexPath.row].cellFromTableView(tableView, forIndexPath: indexPath)
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        cellControllers[indexPath.row].didSelectCell()
    }
}

Factory pattern으로 cell controller를 모두 받아오고,
cell controller에 여러 역할을 맡김으로써 위처럼 View Controller는 매우 간결해진 것을 볼 수 있습니다.


참조: https://tech.busuu.com/dealing-with-different-kinds-of-cells-in-swift-part-1-of-3-18c6cd10a0b3

profile
iOS 개발자의 기록

0개의 댓글