본문 바로가기
iOS

Reusable Horizontal UICollectionView in UITableView

by HaningYa 2020. 8. 3.
728x90

 

해커톤을 위해 토이 프로젝트를 하고 있다.

 

디자인이 슬슬 나오기 시작한다.

오늘 조진 GUI 화면 구현이 조금 복잡하여 블로그에 정리해보려 한다.

기록하려는 것이기 때문에 설명 불친절 주의☠️

 

Reusable Horizontal UICollectionView in UITableView

라는 뜻은 아래의 스크린 샷과 같은 UI를 내가 표현해본 것이다.

내 맘대로 이름붙여보았다.

 

큰틀로 UITableView를 유지하면서 중간중간 Cell 에 Horizontal 하게 Scroll 될 수 있는 UICollectionView 를 넣어주는 것이다.

처음엔 그냥 UIScrollView 에 UIVIew로 나눠서 하나씩 UICollectionView 를 넣으려 했다. 문제는

생각보다 저 UICollectionView 가 똑같이 사용되는 UI 가 많았다.

이 방식 대론 계속 하나씩 구현해야 되니 눈물을 머금고

UITableView 에 Cell 로 집어 넣기로 한다.

주요 아이디어는 TableView 와 CollectionView 의 Cell을 만들때 Nib 으로 만들고

Cell 안에 CollectionView 관련 메소드를 구현하는 것이다.


#1 ViewController 에 UITableView 를 만들어 준다.

그냥 테이블뷰다

#2 각 UITableViewCell 에 들어갈 Cell 들의 Nib 을 등록한다.

private func setupTableView(){
        // Register the xib for tableview cell
        tableView.delegate = self
        tableView.dataSource = self
        // #1
        let curatingCellNib = UINib(nibName: "CuratingCell", bundle: nil)
        self.tableView.register(curatingCellNib, forCellReuseIdentifier: "CuratingCell")
        // #2 #3
        let newsLetterCellNib = UINib(nibName: "NewsLetterCell", bundle: nil)
        self.tableView.register(newsLetterCellNib, forCellReuseIdentifier: "NewsLetterCell")
        // #4
        let categoryCellNib = UINib(nibName: "CategoryCell", bundle: nil)
        self.tableView.register(categoryCellNib, forCellReuseIdentifier: "CategoryCell")
    }

여기서 Nib들은 다 New ->Create Cocoa Touch Class -> ~Cell (with xib) 해서 만들어준 것이다.

그리고 Datasource 와 Delegate 를 Extension 에 적어준다.

extension HomeVC: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 4
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        switch indexPath.row {
        case 0:
            return 270
        case 1:
            return 270
        case 2:
            return 270
        case 3:
            return 300
        default:
            return 80
        }
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        switch indexPath.row {
        case 0:
            if let cell = tableView.dequeueReusableCell(withIdentifier: "CuratingCell") as? CuratingCell {
                cell.lbTitle.text = "에디터 선정 이달의 뉴스레터"
                cell.cellDelegate = self
                return cell
            }
        case 1:
            if let cell = tableView.dequeueReusableCell(withIdentifier: "NewsLetterCell") as? NewsLetterCell {
                cell.lbName.text = "김태형"
                return cell
            }
        case 2:
            if let cell = tableView.dequeueReusableCell(withIdentifier: "NewsLetterCell") as? NewsLetterCell {
                cell.lbName.text = ""
                cell.lbTitle.text = "사람들이 많이보는 뉴스레터"
                return cell
            }
        case 3:
            if let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as? CategoryCell {
                return cell
            }
            
            
        default:
            return UITableViewCell()
        }
        
        return UITableViewCell()
        
    }
}

#3 구현 방식은 curatingCell 과 newsLetterCell, categoryCell 모두 동일하다.

그래서 2번 사용되는 newsLetterCell 만 잡고 정리하자면

TableViewCell 의 Xib를 만들고

Xib를 만들고

UICollectionViewCell 의 Xib 를 만들고

또만들고

TableViewCell 에 Collection뷰 관련 메소드를 정의해준다.

//
//  NewsLetterCell.swift
//  TK
//
//  Created by TaeHyeong Kim on 2020/08/03.
//  Copyright © 2020 TaeHyeong Kim. All rights reserved.
//

import UIKit
import MSPeekCollectionViewDelegateImplementation

class NewsLetterCell: UITableViewCell {
    
    @IBOutlet weak var lbName: UILabel!
    @IBOutlet weak var lbTitle: UILabel!
    @IBOutlet weak var collectionView: UICollectionView!
    
    let behavior = MSCollectionViewPeekingBehavior()
    
    
    
    override func awakeFromNib() {
        super.awakeFromNib()
        self.collectionView.dataSource = self
        self.collectionView.delegate = self
        
        // Register the xib for collection view cell
        let cellNib = UINib(nibName: "NewsLetterCollectionCell", bundle: nil)
        self.collectionView.register(cellNib, forCellWithReuseIdentifier: "NewsLetterCollectionCell")
        
        behavior.cellSpacing = 8
        behavior.cellPeekWidth = 17
        behavior.numberOfItemsToShow = 2
        collectionView.configureForPeekingBehavior(behavior: behavior)
        
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
        // Configure the view for the selected state
    }
    
}
extension NewsLetterCell : UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 4
    }
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NewsLetterCollectionCell", for: indexPath) as? NewsLetterCollectionCell {
            cell.tagListView.removeAllTags()
            cell.tagListView.addTags(["Tag1","Tag2","Tag3"])
            return cell
        }
        return UICollectionViewCell()
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
    }
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        //        let cell = collectionView.cellForItem(at: indexPath) as? CuratingCollectionCell
        //        self.cellDelegate?.collectionView(collectionviewcell: cell, index: indexPath.item, didTappedInTableViewCell: self)
    }
    
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        behavior.scrollViewWillEndDragging(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
    }
    
}

 

완성이다.

 

왜 재사용 이냐면 ViewController 에서 UITableView CellForRowAt 을 보면

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        switch indexPath.row {
        case 0:
            if let cell = tableView.dequeueReusableCell(withIdentifier: "CuratingCell") as? CuratingCell {
                cell.lbTitle.text = "에디터 선정 이달의 뉴스레터"
                cell.cellDelegate = self
                return cell
            }
        case 1:
            if let cell = tableView.dequeueReusableCell(withIdentifier: "NewsLetterCell") as? NewsLetterCell {
                cell.lbName.text = "김태형"
                return cell
            }
        case 2:
            if let cell = tableView.dequeueReusableCell(withIdentifier: "NewsLetterCell") as? NewsLetterCell {
                cell.lbName.text = ""
                cell.lbTitle.text = "사람들이 많이보는 뉴스레터"
                return cell
            }
        case 3:
            if let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as? CategoryCell {
                return cell
            }
            
            
        default:
            return UITableViewCell()
        }
        
        return UITableViewCell()
        
    }
}

저기 case 1과 2가 같은 걸 볼수 있다.

이제 난 저기에 다른 datasource 만 넣어주면 처음 계획했던 것 처럼 하나씩 추가 안해도 된다.

 

양 끝에 잘짝 다음 Cell 이 보이는 CollectionView 는 라이브러리를 사용하였다.

 

MaherKSantina/MSPeekCollectionViewDelegateImplementation

A custom paging behavior that peeks the previous and next items in a collection view - MaherKSantina/MSPeekCollectionViewDelegateImplementation

github.com

 

 

 

728x90

댓글