CollectionView Adapter

CollectionViewAdapter

CollectionView Adapter
출처: https://labs.brandi.co.kr/2020/12/04/kimjw.html

사내에서 리스트를 구현할 때에, UITableView 대신 유동적인 셀을 구성할 수 있는 UICollectionView를 적극 사용하며 ViewController의 비즈니스로직을 ViewModel로 분리함에도 뷰 내부의 UICollectionViewDelegate들로 인해 많은 코드들이 다시 쌓이기 시작했고, CollectionViewAdapter Class로 분리하여 관리하도록 작업하고 있다.

CollectionViewAdapter

데이터 파이프라인, 사용자 액션 혹은 비즈니스로직을 protocol로 받아 사용할 수 있도록 설계한다.

protocol ProductListDataProvider: AnyObject {
    var dataList: [Photo] { get set }
}

protocol ProductListAdapterDelegate: AnyObject {
    func didSelectedProduct(product: Photo)
}

final class ProductListCollectionViewAdapter: NSObject {
    
    private weak var provider: ProductListDataProvider?
    private weak var delegate: ProductListAdapterDelegate?
    
    init(provider: ProductListDataProvider? = nil, delegate: ProductListAdapterDelegate? = nil) {
        self.provider = provider
        self.delegate = delegate
    }
    
    func setRequirement(_ collectionView: UICollectionView) {
        collectionView.dataSource = self
        collectionView.delegate = self
    
        collectionView.register(ProductListCell.self, forCellWithReuseIdentifier: ProductListCell.className)
    }
}


extension ProductListCollectionViewAdapter: UICollectionViewDataSource {
    
    func collectionView(
        _ collectionView: UICollectionView,
        numberOfItemsInSection section: Int
    ) -> Int {
     //
    }
    
    func collectionView(
        _ collectionView: UICollectionView,
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
       //
    }
}

extension ProductListCollectionViewAdapter: UICollectionViewDelegate { 
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
   //
    }
}

extension ProductListCollectionViewAdapter: UICollectionViewDelegateFlowLayout {
    func collectionView(
        _ collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        sizeForItemAt indexPath: IndexPath
    ) -> CGSize {
        //
    }
}

ViewController

// MARK: - ProductListVC
final class ProductListViewController: UIViewController {
  private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
    
    private lazy var adapter = ProductListCollectionViewAdapter(
        provider: viewModel,
        delegate: self
    )

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.navigationItem.title = "ProductListVC"
        self.addViews()
        self.setLayout()
        self.adapter.setRequirement(self.collectionView)
    }

      func addViews() {
        self.view.addSubview(self.collectionView)
    }
    
    func setLayout() {
        self.collectionView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor),
            self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            self.collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            self.collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
        ])
    }
}

CollectionViewAdapter는 View에 종속되지 않기 때문에, ViewModel이 Provider Protocol만 채택한다면, 여러 뷰에서 사용이 가능해 유지보수성, 코드의 중복을 줄일 수 있고, 비대해지는 ViewController의 역할을 분리할 수 있다.

아키텍처와 디자인 패턴은 모든 프로젝트에 동일하게 적용될 수 있는 만능 해결책이 아니며, 프로젝트의 성격과 규모에 따라 적절한 선택이 필요하다고 생각한다. 예를 들어, 공통 View와 비즈니스 로직이 많고 많은 개발자가 투입되는 대규모 프로젝트에서는 어댑터 패턴과 같은 구조적 패턴을 활용하여 코드의 재사용성과 확장성을 높이는 것이 매우 유용할 수 있지만, 소규모 프로젝트나 단순한 앱에서는 이러한 패턴이 오히려 복잡도를 증가시킬 수 있으므로, 상황에 맞게 사용하는 것이 중요할 것 같다.