iOS 개발 기록

[UIKit]Modern Collection View 본문

UIKit

[UIKit]Modern Collection View

택꽁이 2023. 2. 21. 15:31
728x90
📄

Collection View

UICollectionViewDelegate, UICollectionViewDataSource

class ViewController: UIViewController {
  @IBOutlet weak var collectionView: UICollectionView!  

  override func viewDidLoad() {
    super.viewDidLoad()

    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionViewCell")
  }
}

// MARK: - 기존 CollectionView를 사용하는 방법
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell else {
      return UICollectionViewCell()
    }

    cell.label.text = indexPath.row.description
    return cell
  }
}

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

    let itemSpacing: CGFloat = 10
    let width: CGFloat = (collectionView.bounds.width - itemSpacing) / 2
    let height: CGFloat = width * 10/7

    return CGSize(width: width, height: height)
  }
}

Modern Collection View (iOS 13.0+)

문제가 되는 상황과 케이스
  • 기존의 collectionView 형태에서는 UI와 DataSource의 버전(truth)가 같지 않을때 멈추는 문제가 있었다. reloadData()로 해결할 수 있지만, 그럴 경우 애니메이션이 씹히는 현생이 발생. 이를 해결하기 위해 snapShot이라는 새로운 개념을 접근하는 것이 개요.
  • diffable은 화면에 보여질 아이템이 업데이트 될 때 마다 collectionView가 자동으로 업데이트 된다는 의미. 이전 데이터와의 차이를 계산하고 애니메이션을 통해 보여준다.

UICollectionViewDiffableDataSource

func setUpDataSource() {
   dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView) { (collectionView, indexPath, cellNumber) -> UICollectionViewCell? in
     guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell else {
       return UICollectionViewCell()
     }

     cell.label.text = cellNumber.description

     return cell
   }
 }

func performCell() {
   let cellNum: [Int] = (0..<10).map{ Int($0) }

   var snapShot = NSDiffableDataSourceSnapshot<Int, Int>()
   snapShot.appendSections([0])
   snapShot.appendItems(cellNum)
   self.dataSource.apply(snapShot)
 }

UICollectionViewLayout

UICollectionViewFlowLayout

  • 기존의 방법대로

UICollectionViewCompositionalLayout (iOS 13.0 +)

전체 코드

class ViewController: UIViewController {

  @IBOutlet weak var collectionView: UICollectionView!
  var dataSource: UICollectionViewDiffableDataSource<Int, Int>!
  
  override func viewDidLoad() {
    super.viewDidLoad()

//    collectionView.delegate = self
//    collectionView.dataSource = self
    collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionViewCell")
    
    // UICollectionViewDiffableDataSource를 사용한 방법
    collectionView.collectionViewLayout = createLayout()
    setUpDataSource()
    performCell()
    
    
  }
}


 // MARK: - 기존 CollectionView를 사용하는 방법
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell else {
      return UICollectionViewCell()
    }

    cell.label.text = indexPath.row.description
    return cell
  }
}

extension ViewController: UICollectionViewDelegateFlowLayout {
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let itemSpacing: CGFloat = 10
    let width: CGFloat = (collectionView.bounds.width - itemSpacing) / 2
    let height: CGFloat = width * 10/7

    return CGSize(width: width, height: height)
  }
}


// MARK: - UICollectionViewDiffableDataSource을 사용한 방법
extension ViewController {
  func setUpDataSource() {
     dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView) { (collectionView, indexPath, cellNumber) -> UICollectionViewCell? in
       guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell else {
         return UICollectionViewCell()
       }

       cell.label.text = cellNumber.description

       return cell
     }
   }

  func performCell() {
     let cellNum: [Int] = (0..<10).map{ Int($0) }

     var snapShot = NSDiffableDataSourceSnapshot<Int, Int>()
     snapShot.appendSections([0])
     snapShot.appendItems(cellNum)
     self.dataSource.apply(snapShot)
   }
  
  func createLayout() -> UICollectionViewLayout {
    let layout: UICollectionViewCompositionalLayout = {
      let itemSpacing: CGFloat = 10
      let width: CGFloat = 1.0 / 2.0
      let height: CGFloat = 1.0 / 2.5

      // item
      let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(width),
        heightDimension: .fractionalHeight(1)
      )
      let item = NSCollectionLayoutItem(layoutSize: itemSize)
      item.contentInsets = NSDirectionalEdgeInsets(top: itemSpacing, leading: itemSpacing, bottom: itemSpacing, trailing: itemSpacing)

      // group
      let groupSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1),
        heightDimension: .fractionalHeight(height)
      )
      let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])


      //section
      let section = NSCollectionLayoutSection(group: group)
      section.contentInsets = NSDirectionalEdgeInsets(top: itemSpacing, leading: itemSpacing, bottom: itemSpacing, trailing: itemSpacing)

      return UICollectionViewCompositionalLayout(section: section)
    }()

    return layout
  }
}

Reference

  • Documents
Apple Developer Documentation
https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views

  • collectionView layout
[iOS] UICollectionView와 관련 된 경험들
UITableView 또는 UICollectionView를 사용하면 데이터를 정돈된 형태로 사용자에게 보여줄 수 있습니다. UITableView보다 UICollectionView가 더 다양한 layout으로 보여줄 수 있기 때문에 저는 거의 Collection View를 사용하는 것 같아요. 그래서 UICollectionView를 사용하면서 겪었던 경험들을 이 글에서 공유해보고자 합니다. 트러블슈팅이기도 하고, 개념을 톺아보는 글이기도 하고, 다양한 뷰를 만들어 본 경험을 공유하는 자리이기도 하겠네요.
https://sueaty.tistory.com/187

  • Modern Collection View
Diffable Datasource
이거 꼭 공부해보고싶었는데! WWDC19. Apple이 Diffable Datasource를 소개합니다. 물론 iOS 13부터 사용이 가능한 ^^.. Datasource하니까 가장 먼저 떠오르는게 있지 않나요? UITableViewDataSource UICollectionViewDataSource 저는 이 2개가 떠오르는데, Diffable Datasource에는 어떤것이 있을까요? UICollectionView Diffable DataSource 🙋 : UITableViewDataSource를 conform하는 대신 UITableView Diffable DataSource를 confom하면 뭔가가 달라지겠구나! 🧑‍💻 : UITableViewDataSource는 Protocol이라 보통 UIViewController가 이를 conform하곤 했었습니다.
https://zeddios.tistory.com/1197
[iOS] Modern Collection View - List 구현
iOS 14 이후부터 지원하는 기능으로 이전의 방법과 아예 다른 방식으로 collection view를 만들어주고 있다. 개괄적인 순서를 먼저 보고 하나 하나 예제 코드를 통해 살펴보자! 이번에는 List 형태의 collection view를 만들어 볼 것이다. 위와 같은 뷰를 보면 "테이블뷰로 만들었네!"라고도 할 수 있지만 collection view로도 동일한 뷰를 만들 수 있다.
https://leechamin.tistory.com/555

  • UICollectionViewLayout
[iOS - swift] 1. UICollectionViewCompositionalLayout - 개념 (section, group, item)
1. UICollectionViewCompositionalLayout - 개념 (section, group, item) 2. UICollectionViewCompositionalLayout - 개념 SupplementaryView, Header, Footer) 3. UICollectionViewCompositionalLayout - 개념..
https://ios-development.tistory.com/945


Uploaded by N2T