[UIKit] UICollectionView: Compositional Layout

Junyoung Park·2022년 10월 29일
0

UIKit

목록 보기
61/142
post-thumbnail
post-custom-banner

CollectionView Compositional Layout (Advanced Layouts) - Swift 5, Xcode 12, 2020 - iOS Development

UICollectionView: Compositional Layout

구현 목표

  • 다양한 크기의 셀 크기를 표현할 수 있는 커스텀 레이아웃의 컬렉션 뷰 구현

구현 태스크

  • 기존의 플로우 레이아웃을 UICollectionViewCollectionViewLayout으로 변경
  • 아이템, 그룹, 섹션 단위로 각 위치 별 아이템의 개수, 그룹의 방향 등을 결정 가능

핵심 코드

    static func createLayout() -> UICollectionViewCompositionalLayout {
        // Item
        let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(2/3), heightDimension: .fractionalHeight(1)))
        item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
        let verticalStackItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.5)))
        verticalStackItem.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
        let verticalStackGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalHeight(1)), repeatingSubitem: verticalStackItem, count: 2)
        let tripletItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(1)))
        tripletItem.contentInsets = .init(top: 2, leading: 2, bottom: 2, trailing: 2)
        let horizontalTripletGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.3)), repeatingSubitem: tripletItem, count: 3)
        // Group
        let horizontalGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.7)), subitems: [item, verticalStackGroup])
        let verticalGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)), subitems: [horizontalGroup, horizontalTripletGroup])
        // Sections
        let section = NSCollectionLayoutSection(group: verticalGroup)
        // Return
        return UICollectionViewCompositionalLayout(section: section)
    }
  • 아이템, 섹션, 그룹 단위의 레이아웃을 결정하는 코드
  • fractional을 통해 너비, 높이에 해당하는 비율을 값으로 줄 수 있음 (absoulte를 통해 절댓값을 줄 수도 있음)
  • 그룹을 여러 개로 묶은 섹션을 리턴, 여러 개의 섹션에 다양한 종류의 UICollectionViewCompositionalLayout을 줄 수 있는 게 핵심

소스 코드

import UIKit

class CollectionViewController: UIViewController {
    private let collectionView: UICollectionView = {
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout())
        collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
        return collectionView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        setUI()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        collectionView.frame = view.bounds
    }
    
    private func setUI() {
        view.backgroundColor = .systemBackground
        view.addSubview(collectionView)
        collectionView.delegate = self
        collectionView.dataSource = self
    }
    
    static func createLayout() -> UICollectionViewCompositionalLayout {
        // Item
        let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(2/3), heightDimension: .fractionalHeight(1)))
        item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
        let verticalStackItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.5)))
        verticalStackItem.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
        let verticalStackGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalHeight(1)), repeatingSubitem: verticalStackItem, count: 2)
        let tripletItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(1)))
        tripletItem.contentInsets = .init(top: 2, leading: 2, bottom: 2, trailing: 2)
        let horizontalTripletGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.3)), repeatingSubitem: tripletItem, count: 3)
        // Group
        let horizontalGroup = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.7)), subitems: [item, verticalStackGroup])
        let verticalGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)), subitems: [horizontalGroup, horizontalTripletGroup])
        // Sections
        let section = NSCollectionLayoutSection(group: verticalGroup)
        // Return
        return UICollectionViewCompositionalLayout(section: section)
    }
}

extension CollectionViewController: UICollectionViewDelegate {
    
}

extension CollectionViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 100
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.identifier, for: indexPath) as? CustomCollectionViewCell else {
            return UICollectionViewCell()
        }
        if
            let number = (1...11).randomElement(),
            let image = UIImage(named: "fantasy_\(number)") {
            cell.configure(with: image)
        }
        return cell
    }
}

extension CollectionViewController: UICollectionViewDelegateFlowLayout {
    
}
  • 컬렉션 뷰를 이니셜라이즈 할 때 사용하기 위해 static func로 해당 레이아웃을 리턴받고 있음
import UIKit

class CustomCollectionViewCell: UICollectionViewCell {
    static let identifier = "CustomCollectionViewCell"
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.clipsToBounds = true
        imageView.contentMode = .scaleAspectFill
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = contentView.bounds
    }
    
    private func setUI() {
        contentView.addSubview(imageView)
    }
    
    func configure(with image: UIImage) {
        imageView.image = image
    }
}
  • contentModescaleAspectToFill을 등록

구현 화면

profile
JUST DO IT
post-custom-banner

0개의 댓글