관리 메뉴

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

[iOS - swift] 1. 스크롤 영역을 암시해주는 Carousel 구현 방법 (UICollectionView, 수평 스크롤 뷰, paging 구현) 본문

UI 컴포넌트 (swift)

[iOS - swift] 1. 스크롤 영역을 암시해주는 Carousel 구현 방법 (UICollectionView, 수평 스크롤 뷰, paging 구현)

jake-kim 2022. 6. 26. 14:46

1. 스크롤 영역을 암시해주는 Carousel 구현 - (UICollectionView, 수평 스크롤 뷰, paging 구현)

2. 스크롤 영역을 암시해주는 Carousel 구현 - 포커스 영역 이펙트

좌, 우측에 item이 보이도록 스크롤 되는 Carousel

구현 아이디어

  • 수평 스크롤 뷰 구현 방법은 FlowLayout+UICollectionView으로 구현하는 방법인 이전 포스팅 글 참고
  • 양옆에 item이 보여서, 스크롤 할 수 있다는 암시를 주도록 paging이 되어야 하므로 이 것을 구현하는 것이 핵심
    • paging을 직접 구현 `isPagingEnabled = false`
    • 스크롤이 페이징처럼 보여지기 `decelearationRate = .fast`
    • 페이징을 scrollViewWillEndDragging에서 구현 (UICollectionViewDelegateFlowLayout의 델리게이트 메소드)
  • 페이징 구현 방법
    • scrollViewWillEndDragging 메소드에서 현재까지 스크롤된 크기를 구할 수 있고, 이 스크롤 크기를 다시 수정할 수 있으므로 이곳에서 구현
    • 스크롤된 크기를 구하고, item의 index값을 구함

  • index값을 구하면, 아래 식으로 다시 페이징되게하여 원하는 곳으로 스크롤 되도록 입력이 가능
    • 스크롤 위치 = (index * cellWidth) - insetX

구현

  • UICollectionView의 cell 정의
final class MyCollectionViewCell: UICollectionViewCell {
  static let id = "MyCollectionViewCell"
  
  // MARK: UI
  private let myView: UIView = {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
  }()
  
  // MARK: Initializer
  @available(*, unavailable)
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    
    self.contentView.addSubview(self.myView)
    NSLayoutConstraint.activate([
      self.myView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor),
      self.myView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor),
      self.myView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor),
      self.myView.topAnchor.constraint(equalTo: self.contentView.topAnchor),
    ])
  }
  
  override func prepareForReuse() {
    super.prepareForReuse()
    
    self.prepare(color: nil)
  }
  
  func prepare(color: UIColor?) {
    self.myView.backgroundColor = color
  }
}

private var randomColor: UIColor {
  UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
}
  • 예제로 사용할 데이터인, randomColor 정의
private var randomColor: UIColor {
  UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
}
  • ViewController 선언
import UIKit

class ViewController: UIViewController {
}
  • itemSize, itemSpacing, insetX 정의
  // in ViewController
  private enum Const {
    static let itemSize = CGSize(width: 300, height: 400)
    static let itemSpacing = 24.0
    
    static var insetX: CGFloat {
      (UIScreen.main.bounds.width - Self.itemSize.width) / 2.0
    }
    static var collectionViewContentInset: UIEdgeInsets {
      UIEdgeInsets(top: 0, left: Self.insetX, bottom: 0, right: Self.insetX)
    }
  }
  • flowLayout과 collectionView 선언
    • flowLayout에서 itemSize와 spacing을 정의
    • collectionView에서 isPagingEnabled = false로,decelerationRate를 fast로 설정
  // in ViewController
  private let collectionViewFlowLayout: UICollectionViewFlowLayout = {
    let layout = UICollectionViewFlowLayout()
    layout.scrollDirection = .horizontal
    layout.itemSize = Const.itemSize // <-
    layout.minimumLineSpacing = Const.itemSpacing // <-
    layout.minimumInteritemSpacing = 0
    return layout
  }()
  private lazy var collectionView: UICollectionView = {
    let view = UICollectionView(frame: .zero, collectionViewLayout: self.collectionViewFlowLayout)
    view.isScrollEnabled = true
    view.showsHorizontalScrollIndicator = false
    view.showsVerticalScrollIndicator = true
    view.backgroundColor = .clear
    view.clipsToBounds = true
    view.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: MyCollectionViewCell.id)
    view.isPagingEnabled = false // <- 한 페이지의 넓이를 조절 할 수 없기 때문에 scrollViewWillEndDragging을 사용하여 구현
    view.contentInsetAdjustmentBehavior = .never // <- 내부적으로 safe area에 의해 가려지는 것을 방지하기 위해서 자동으로 inset조정해 주는 것을 비활성화
    view.contentInset = Const.collectionViewContentInset // <-
    view.decelerationRate = .fast // <- 스크롤이 빠르게 되도록 (페이징 애니메이션같이 보이게하기 위함)
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
  }()
  • 데이터소스와 레이아웃 정의
  // in ViewController  
  private var items = (0...100).map { _ in randomColor }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    self.view.addSubview(self.collectionView)
    NSLayoutConstraint.activate([
      self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
      self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
      self.collectionView.heightAnchor.constraint(equalToConstant: Const.itemSize.height),
      self.collectionView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
    ])
    
    self.collectionView.dataSource = self
    self.collectionView.delegate = self
  }
  • 페이징을 구현하는 부분 `scrollViewWillEndDragging`
    • 스크롤된 크기를 구하고, item의 index값을 구함
    • index값을 구하면, 아래 식으로 다시 페이징되게하여 원하는 곳으로 스크롤 되도록 입력이 가능
    • 스크롤 위치 = (index * cellWidth) - insetX
extension ViewController: UICollectionViewDelegateFlowLayout {
  func scrollViewWillEndDragging(
    _ scrollView: UIScrollView,
    withVelocity velocity: CGPoint,
    targetContentOffset: UnsafeMutablePointer<CGPoint>
  ) {
    let scrolledOffsetX = targetContentOffset.pointee.x + scrollView.contentInset.left
    let cellWidth = Const.itemSize.width + Const.itemSpacing
    let index = round(scrolledOffsetX / cellWidth)
    targetContentOffset.pointee = CGPoint(x: index * cellWidth - scrollView.contentInset.left, y: scrollView.contentInset.top)
  }
}

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

Comments