관리 메뉴

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

[iOS - swift] 4. UICollectionViewCompositionalLayout - 개념 (orthogonalScrollingBehavior, 수평 스크롤, visibleItemsInvalidationHandler) 본문

iOS 응용 (swift)

[iOS - swift] 4. UICollectionViewCompositionalLayout - 개념 (orthogonalScrollingBehavior, 수평 스크롤, visibleItemsInvalidationHandler)

jake-kim 2022. 4. 22. 23:59

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

2. UICollectionViewCompositionalLayout - 개념 SupplementaryView, Header, Footer)

3. UICollectionViewCompositionalLayout - 개념 (DecorationView, Badge, NSCollectionLayoutAnchor)

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

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

orthogonalScrollingBehavior (수평 스크롤)

  • 특정 Section에 대해 스크롤 방향을 반대로 설정할 수 있는 방법
    • 기존에는 수평스크롤을 넣고 싶은 경우, cell안에 collectionView를 하나 더 넣어서 수평 스크롤을 구현했지만 orthogonalScrollingBehavior를 사용하면 간결
  • 크게 자연스럽게 스크롤 되는 continuous와 paging 스크롤 되는 paging이 존재
    • 5가지 속성이 존재
// Standard scroll view behavior: UIScrollViewDecelerationRateNormal
case continuous

// Scrolling will come to rest on the leading edge of a group boundary
case continuousGroupLeadingBoundary

// Standard scroll view paging behavior (UIScrollViewDecelerationRateFast) with page size == extent of the collection view's bounds
case paging

// Fractional size paging behavior determined by the sections layout group's dimension
case groupPaging

// Same of group paging with additional leading and trailing content insets to center each group's contents along the orthogonal axis
case groupPagingCentered
.continous paging
  • orthogonalScrollingBehavior는 section의 프로퍼티이므로 아래처럼 사용
    • section.orthogonalScrollingBehavior = .continuous
private func getGridSection() -> NSCollectionLayoutSection {
  let itemSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(0.3),
    heightDimension: .fractionalHeight(1.0)
  )
  let item = NSCollectionLayoutItem(layoutSize: itemSize)
  item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
  let groupSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(1.0),
    heightDimension: .fractionalHeight(0.3)
  )
  // collectionView의 width에 3개의 아이템이 위치하도록 하는 것
  let group = NSCollectionLayoutGroup.horizontal(
    layoutSize: groupSize,
    subitem: item,
    count: 3
  )
  let section = NSCollectionLayoutSection(group: group)
  section.orthogonalScrollingBehavior = .continuous
  return section
}
  • 사용하는쪽
  private lazy var collectionView: UICollectionView = {
    let view = UICollectionView(frame: .zero, collectionViewLayout: self.getLayout())
    view.isScrollEnabled = true
    view.showsHorizontalScrollIndicator = false
    view.showsVerticalScrollIndicator = true
    view.contentInset = .zero
    view.backgroundColor = .clear
    view.clipsToBounds = true
    view.register(MyCell.self, forCellWithReuseIdentifier: MyCell.id)
    view.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(view)
    return view
  }()
  
  private func getLayout() -> UICollectionViewLayout {
    return UICollectionViewCompositionalLayout { sectionIndex, env -> NSCollectionLayoutSection? in
      switch self.dataSource[sectionIndex] {
      case .main:
        return self.getListSection()
      case .sub:
        return self.getGridSection()
      }
    }
  }

visibleItemsInvalidationHandler

보여지는 Cell들의 애니메이션 효과

  • 현재 보여지고 있는 화면에 어떤 항목이 표시되는지 알 수 있는 api
    • section의 visibleItemsInvalidationHandler 프로퍼티에 클로저 내부에서 처리
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
section.visibleItemsInvalidationHandler = { [weak self] (visibleItems, offset, env) in
  guard let ss = self else { return }
  let normalizedOffsetX = offset.x
  let centerPoint = CGPoint(x: normalizedOffsetX + ss.collectionView.bounds.width / 2, y: 20)
  visibleItems.forEach({ item in
    guard let cell = ss.collectionView.cellForItem(at: item.indexPath) else { return }
    UIView.animate(withDuration: 0.3) {
      cell.transform = item.frame.contains(centerPoint) ? .identity : CGAffineTransform(scaleX: 0.9, y: 0.9)
    }
  })
}

...
// 최초에 위 애니메이션이 적용되어야 하므로, 런타임에서 뷰의 크기가 정해졌을 때 performBatchUpdates 호출
override func viewWillLayoutSubviews() {
  super.viewWillLayoutSubviews()
  self.collectionView.performBatchUpdates(nil, completion: nil)
}

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

* 참고

https://betterprogramming.pub/super-easy-compositional-uicollectionviews-in-swift-f0fa6a2c108d

Comments