관리 메뉴

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

[iOS - swift] 2. UICollectionViewFlowLayout 사용 방법 - 격자, 그리드 뷰 (grid view) 본문

iOS 응용 (swift)

[iOS - swift] 2. UICollectionViewFlowLayout 사용 방법 - 격자, 그리드 뷰 (grid view)

jake-kim 2022. 5. 17. 23:41

1. UICollectionViewFlowLayout 사용 방법 - 수평 스크롤 뷰 (horizontal scroll view)

2. UICollectionViewFlowLayout 사용 방법 - 격자, 그리드 뷰 (grid view)

FlowLayout을 사용하면 columns 수도 손쉽게 변경 가능

2줄인 경우

구현 아이디어

  • 셀의 레이아웃을 결정하는 곳은 델리게이트임을 알고 (UICollectinoViewDelegateFlowLayout) 여기서 셀의 크기를 결정하도록 설정
  • 위 델리게이트 중 sizeForItemAt 메소드에서 collectionView의 width값을 가져와서 너비에 관한 적절한 크기를 계산해서 셀의 크기를 결정해주면 grid cell 완성
  • 셀의 크기 계산
    • collectionView.bounds.width값에서 좌우 contentInset값과 중간에 있는 cell space 값을 뺀값이 전체 width가 될 값
    • 이 전체 width에서 사용하고 싶은 columns 수만큼 나누어주면 그만큼 셀의 크기가 정해지게 되어 grid형태 레이아웃 구현 완료

UICollectionViewFlowLayout 서브클래싱 정의

  • cell spacing과 scrollDirection을 디폴트로 설정
  • 사용하는쪽의 sizeForItemAt 델리게이트 메소드에서 property에 접근하여 numberOfColumns등의 값을 얻기 위해 프로퍼티 선언
import UIKit

class GridCollectionViewFlowLayout: UICollectionViewFlowLayout {
  var numberOfColumns = 1
  var cellSpacing = 0.0 {
    didSet {
      self.minimumLineSpacing = self.cellSpacing
      self.minimumInteritemSpacing = self.cellSpacing
    }
  }
  
  override init() {
    super.init()
    self.scrollDirection = .vertical
  }
  required init?(coder: NSCoder) {
    fatalError()
  }
}

커스텀 셀

  • UICollectionViewCell을 서브클래싱하고 있고 단순히 이미지 뷰 하나를 들고있는 셀
import UIKit

final class MyCell: UICollectionViewCell {
  static let id = "MyCell"
  
  // MARK: UI
  private let imageView: UIImageView = {
    let view = UIImageView()
    view.contentMode = .scaleAspectFill
    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.imageView)
    NSLayoutConstraint.activate([
      self.imageView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor),
      self.imageView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor),
      self.imageView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor),
      self.imageView.topAnchor.constraint(equalTo: self.contentView.topAnchor),
    ])
  }
  
  override func prepareForReuse() {
    super.prepareForReuse()
    
    self.prepare(image: nil)
  }
  
  func prepare(image: UIImage?) {
    self.imageView.image = image
  }
}

collectionView 사용하는 쪽

  • 데이타 소스 준비
import UIKit

class ViewController: UIViewController {
  private var dataSource = getSampleImages()
}

func getSampleImages() -> [UIImage?] {
  (1...100).map { _ in return UIImage(named: "dog") }
}
  • flowLayout 선언
    • 2개의 columns를 갖도록 설정 (밑에 sizeForItemAt 델리게이트 메소드에서 해당 값 사용할 예정)
import UIKit

class ViewController: UIViewController {
  private let gridFlowLayout: GridCollectionViewFlowLayout = {
    let layout = GridCollectionViewFlowLayout()
    layout.cellSpacing = 8
    layout.numberOfColumns = 2
    return layout
  }()
  
  private var dataSource = getSampleImages()
}
  • collectionView 프로퍼티
  private lazy var collectionView: UICollectionView = {
    let view = UICollectionView(frame: .zero, collectionViewLayout: self.gridFlowLayout)
    view.isScrollEnabled = true
    view.showsHorizontalScrollIndicator = false
    view.showsVerticalScrollIndicator = true
//    view.scrollIndicatorInsets = UIEdgeInsets(top: -2, left: 0, bottom: 0, right: 4)
    view.contentInset = .zero
    view.backgroundColor = .clear
    view.clipsToBounds = true
    view.register(MyCell.self, forCellWithReuseIdentifier: "MyCell")
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
  }()
  • 레이아웃 정의, 핵심인 delegate 선언
  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.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 120),
      self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
    ])
    
    self.collectionView.dataSource = self
    self.collectionView.delegate = self
  }

(dataSource는 생략)

  • UICollectionViewDelegateFlowLayout의 sizeForItemAt 메소드에서 셀의 사이즈를 계산
extension ViewController: UICollectionViewDelegateFlowLayout {
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

  }
}
  • 먼저 flowLayout 인스턴스를 다운캐스팅 (위에서 선언한 numberOfColumns 값을 사용하기 위함)
    guard
      let flowLayout = collectionViewLayout as? GridCollectionViewFlowLayout,
      flowLayout.numberOfColumns > 0
    else { fatalError() }
  • cell의 width값을 계산
// cell들의 width값 (내부 spacing 존재)
let widthOfCells = collectionView.bounds.width - (collectionView.contentInset.left + collectionView.contentInset.right)

// spacing 값
let widthOfSpacing = CGFloat(flowLayout.numberOfColumns - 1) * flowLayout.cellSpacing

// cell하나의 width = cell들의 width값에서 spacing값을 뺀것
let width = (widthOfCells - widthOfSpacing) / CGFloat(flowLayout.numberOfColumns)

return CGSize(width: width, height: width)

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

Comments