관리 메뉴

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

[iOS - swift] HorizontalScroll 수평 스크롤 (자동 스크롤 구현, UICollectionViewCompositionalLayout 사용) 본문

iOS 응용 (swift)

[iOS - swift] HorizontalScroll 수평 스크롤 (자동 스크롤 구현, UICollectionViewCompositionalLayout 사용)

jake-kim 2022. 5. 2. 22:48

자동으로 스크롤 되고, 드래그하여 스크롤 할 수 있는 UI

cf) UICollectionViewFlowLayout를 이용한 방법은 이 포스팅 글 참고

수평 스크롤 구현 방향

  • UICollectionView를 사용하여 스크롤을 구현
    • UIScrollView를 사용하면 터치 이벤트 시, 어떤 아이템을 터치했는지 체크하기가 까다롭기 때문에 Cell을 사용할 수 있는 UICollectionView 사용
  • IUCollectionView의 UICollectionViewFlowLayout를 사용해도 되지만, 더욱 복잡한 레이아웃을 쉽게 추가하기 쉬운 확장성 있는 CompositionlalLayout 사용
  • 스크롤 구현은 Timer를 두고 매초마다 collectionView.scrollToItem(at:at:animated:) 메소드를 사용하여 이동되게끔 구현

구현

  • 예제로 사용할 클래스 준비
import UIKit

class ViewController: UIViewController {

}
  • 필요한 프로퍼티 선언
    • currentItem은 현재 몇번째 item인지 상태를 저장해놓고, 매초마다 +1 시키기 위해 존재
    • currentIndexPath는 computedProperty이고 단순히 IndexPath값을 얻어오기 편하게 하기위해서 사용
  private var collectionView: UICollectionView!
  
  private var dataSource = (0...30).map(Int.init(_:))
  private var scrollTimer: Timer?
  private var currentItem = -1
  private var currentIndexPath: IndexPath {
    IndexPath(item: self.currentItem, section: 0)
  }
  
  deinit {
    self.scrollTimer?.invalidate()
    self.scrollTimer = nil
  }
  • NSCollectionLayoutSection을 가져오는 getLayoutSection() 메소드 정의
    • CompositionalLayout은 layout만 따로 구현해놓고 생성자의 collectionViewLayout 파라미터에 넘겨주면 되므로 사용
// 커스텀 layout들을 섹션마다 다르게 손쉽게 사용 방법
self.collectionView = UICollectionView(
  frame: .zero,
  collectionViewLayout: UICollectionViewCompositionalLayout { [weak self] section, env -> NSCollectionLayoutSection? in
    guard let ss = self else { return nil }
    switch self.dataSource[section] {
     ...
    }
  }
)
  • getLayoutSection() 메소드 정의
    • page처럼 하나씩 스크롤되어야 하므로, sectino의 orthogonalScrollingBehavior를 .paging으로 할당
// 레이아웃 정의
private func getLayoutSection() -> NSCollectionLayoutSection {
  // item
  let itemSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(1.0),
    heightDimension: .fractionalHeight(1.0)
  )
  let item = NSCollectionLayoutItem(layoutSize: itemSize)
  item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
  
  // group
  let groupSize = NSCollectionLayoutSize(
    widthDimension: .fractionalWidth(1.0),
    heightDimension: .fractionalHeight(1.0)
  )
  let group = NSCollectionLayoutGroup.horizontal(
    layoutSize: groupSize,
    subitems: [item]
  )
  
  // section
  let section = NSCollectionLayoutSection(group: group)
  section.orthogonalScrollingBehavior = .paging
  return section
}
  • 자동이 아닌 손으로 스크롤하는 경우, 자동 스크롤 될때도 지금 스크롤된 IndexPath를 기준으로 스크롤되어야 하므로, section의 visibleItemsInvalidationHandler에서 현재 보여지는 IndexPath를 가져와서 반영
  // section
  let section = NSCollectionLayoutSection(group: group)
  section.orthogonalScrollingBehavior = .paging
  section.visibleItemsInvalidationHandler = { [weak self] (visibleItems, offset,  env) in
    guard
      let ss = self,
      let visibleIndexPath = visibleItems.last?.indexPath,
      ss.currentIndexPath != visibleIndexPath
    else { return }
    ss.currentItem = visibleIndexPath.item
  }
  return section
  • Timer에서 자동 스크롤 구현
// in viewDidLoad

self.scrollTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: { [weak self] _ in
  guard let ss = self else { return }
  let countOfIndexPath = ss.collectionView.numberOfItems(inSection: 0)
  ss.currentItem = (ss.currentItem + 1) % countOfIndexPath
  
  UIView.animate(
    withDuration: 2.0,
    animations: {
      ss.collectionView.scrollToItem(
        at: ss.currentIndexPath,
        at: .left,
        animated: true
      )
    },
    completion: nil
  )
})

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

Comments