관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 3. UICollectionViewCompositionalLayout - 둘러보기2 (DecorationView, Badge, NSCollectionLayoutAnchor) 본문

iOS 응용 (swift)

[iOS - swift] 3. UICollectionViewCompositionalLayout - 둘러보기2 (DecorationView, Badge, NSCollectionLayoutAnchor)

jake-kim 2022. 4. 21. 00:50

1. UICollectionViewCompositionalLayout - 개념 (section, group, item)

2. UICollectionViewCompositionalLayout - 둘러보기1 (SupplementaryView, Header, Footer)

3. UICollectionViewCompositionalLayout - 둘러보기2 (DecorationView, Badge, NSCollectionLayoutAnchor)

4. UICollectionViewCompositionalLayout - 개념 (orthogonalScrollingBehavior,  수평 스크롤, visibleItemsInvalidationHandler, NSCollectionLayoutAnchor)

5. UICollectionViewCompositionalLayout - 응용 (유튜브 뮤직 앱 UI 구현)

복습) SupplementaryView와 DecorationView

https://ebookreading.net/view/book/EB9781484212424_13.html

  • 애플에서의 개념으로 이해하면, UICollectionView는 크게 3가지로 존재
    • item cell: 메인 데이터가 있는 UI
    • supplementaryView: 메인 데이터를 보충해주는 UI
    • decorationView: 데이터를 표출하지 않는 단순 UI (컬렉션 뷰의 모델과 독립적인 뷰) - 배경, 섹션 하이라이트에 사용

DecorationView로 배경 설정하기

  • DecorationView는 섹션별로 적용이 가능
Section에 Decoration이 없는 경우 Section에 DecorationView를 넣어준 경우
  • 데이터가 없으므로 데이터를 반영하지 않아도 되는 prepare이 없는 뷰 준비
    • SupplementaryView와 동일하게 UICollectionReusableView를 상속하여 구현
import UIKit

class MyDecorationView: UICollectionReusableView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    backgroundColor = UIColor.white.withAlphaComponent(1)
    layer.borderColor = UIColor.black.cgColor
    layer.borderWidth = 0
    layer.cornerRadius = 12
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowRadius = 5
    layer.shadowOpacity = 1
  }
  required init?(coder: NSCoder) {
    fatalError("not implemented")
  }
}
  • supplementaryView와 다르게 dataSource 델리게이트를 사용하지 않고 간편하게 사용
    • supplementaryView는 collectionView.register하지만, 데코레이션뷰는 layout에서 register하여 사용
  static func getLayout() -> UICollectionViewCompositionalLayout {
    let layout = UICollectionViewCompositionalLayout { (section, env) -> NSCollectionLayoutSection? in
    
    ...

    layout.register(MyDecorationView.self, forDecorationViewOfKind: "MyDecorationView")
    return layout
  }
  • section에 `decorationItems` 프로퍼티에 데코레이션 뷰를 주입하여 적용
    • 시스템에서 제공하는 아래 static method API를 사용하여 위에서 등록한 ofKind 값으로 인스턴스 획득
open class func background(elementKind: String) -> Self
        // in getLayout()
        
        // Section
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: itemInset, leading: itemInset, bottom: itemInset, trailing: itemInset)
        
        // Decoration
        let decorationView = NSCollectionLayoutDecorationItem.background(elementKind: "MyDecorationView")
        decorationView.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
        section.decorationItems = [decorationView]
        
        return section

Badge 사용 방법

  • NSCollectionLayoutAnchor 개념
    • cell의 우측 상단에 위치하도록 레이아웃을 잡을 때 NSCollectionLayoutAnchor를 사용하여 쉽게 구현 (아래에서 계속)
// 아래에서 볼 코드

// Badge
let badgeItemSize = NSCollectionLayoutSize(widthDimension: .absolute(25), heightDimension: .absolute(25))
let badgeItemAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing], fractionalOffset: CGPoint(x: 0.3, y: -0.3))
let badgeItem = NSCollectionLayoutSupplementaryItem(
  layoutSize: badgeItemSize,
  elementKind: "MyBadgeView",
  containerAnchor: badgeItemAnchor
)

  • BadgeView 정의
    • SupplementaryView의 한 종류이므로 UIColelctionReusableView 서브클래싱
import UIKit

final class MyBadgeView: UICollectionReusableView {
  private let button: UIButton = {
    let button = UIButton()
    button.setImage(UIImage(named: "close"), for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false
    return button
  }()
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.clipsToBounds = true
    
    self.addSubview(button)
    NSLayoutConstraint.activate([
      self.button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
      self.button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      self.button.widthAnchor.constraint(equalToConstant: 25),
      self.button.heightAnchor.constraint(equalToConstant: 25),
    ])
  }
  
  required init?(coder: NSCoder) {
    fatalError("Not implemented")
  }
}
  • SupplemtaryView와 마찬가지로 register와 dataSource 델리게이트 설정
// collectionView 초기화 부분
view.register(MyBadgeView.self, forSupplementaryViewOfKind: "MyBadgeView", withReuseIdentifier: "MyBadgeView")

// dataSource 델리게이트
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
  switch kind {
    case "MyBadgeView":
      let badgeView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "MyBadgeView", for: indexPath) as! MyBadgeView
      return badgeView
      
   ...
}
  • Item 셀의 우측상단에 위치해야하므로, 해당 레이아웃 작성
    • NSCollectionLayoutAnchor를 통해 top, trailing을 걸어주고, 약간의 offset을 주어서 cell보다 더 우측상단에 위치하도록 설정
    • 이 item을 정의하고 메인 cell item에 supplementaryItems에 넣어주면 완성
  static func getLayout() -> UICollectionViewCompositionalLayout {
    let layout = UICollectionViewCompositionalLayout { (section, env) -> NSCollectionLayoutSection? in
    ...
    
      // Badge
      let badgeItemSize = NSCollectionLayoutSize(widthDimension: .absolute(25), heightDimension: .absolute(25))
      let badgeItemAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing], fractionalOffset: CGPoint(x: 0.3, y: -0.3))
      let badgeItem = NSCollectionLayoutSupplementaryItem(
        layoutSize: badgeItemSize,
        elementKind: "MyBadgeView",
        containerAnchor: badgeItemAnchor
      )
      
      // Item
      let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(itemFractionalWidthFraction),
        heightDimension: .fractionalHeight(1)
      )
      let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badgeItem])
    
    
    ...
    
    layout.register(MyDecorationView.self, forDecorationViewOfKind: "MyDecorationView")
    return layout
  }

* 전체 코드: https://github.com/JK0369/ExCompositionalLayout3

 

* 참고

https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views

https://ebookreading.net/view/book/EB9781484212424_13.html

https://jwonylee.github.io/ios/collectionview-with-badge

https://medium.com/@dn070287gav/all-what-you-need-to-know-about-uicollectionviewcompositionallayout-f3b2f590bdbe

Comments