본문 바로가기

Mobile/iOS

[iOS] CollectionView - CompositionalLayout(2)

반응형


01. 목표 : 콜렉션 뷰 가로 스크롤

이번 목표는 아래 이미지와 같이 가로로 넘기는 CollectionView를 만들어보려고 합니다. 아직 CollectionView를 완전히 익힌 상태가 아니라 조금 부족한 부분이 있을겁니다. 이 부분은 계속 공부하면서 개선해 나갈 생각입니다. 그럼 시작해보겠습니다.


02. CollectionViewCell.swift

먼저 셀을 구성해주도록 하겠습니다. SnapKit을 써서 조금 더 쉽게 오토레이아웃을 잡았습니다.

import SnapKit
import Then

class CollectionViewCell: UICollectionViewCell {
    
    lazy var img: UIImageView = UIImageView().then {
        $0.image = UIImage(systemName: "photo")
        $0.contentMode = .scaleAspectFit
        $0.backgroundColor = .yellow
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupUI()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    fileprivate func setupUI() {
        addSubview(img)
        backgroundColor = .white
        
        img.snp.makeConstraints {
            $0.center.equalToSuperview()
            $0.width.height.equalTo(300)
        }
    }
}

03. ViewController.swift

class ViewController: UIViewController {
    
    lazy var collectionView: UICollectionView = UICollectionView(frame: .zero,
                                                                 collectionViewLayout: createLayout()).then {
        $0.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        $0.backgroundColor = .systemBackground
        $0.delegate = self
        $0.dataSource = self
        $0.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        setupUI()
    }
}

뷰 컨트롤러에서도 마찬가지로 SnapKit과 Then을 사용했습니다. collectionView를 생성할 때 저는 오토레이아웃을 잡아줄 것이기 때문에 frame을 .zero 옵션을 주었습니다. collectionViewLayout은 이후에 만들 layout을 미리 넣어주었습니다. UICollectionView는 이니셜라이즈 될 때 반드시 layout  파라미터를 갖고 있어야 합니다.

delegate와 dataSource 모두 같은 파일에 정의해 줄 예정이기 때문에 self 로 지정해줍니다. 또 커스텀된 Cell을 사용하기 때문에 커스텀된 셀을 등록해주었습니다.

extension ViewController {
    fileprivate func setupUI() {
        self.view.addSubview(collectionView)
        
        collectionView.snp.makeConstraints {
            $0.center.equalToSuperview()
            $0.height.width.equalTo(300)
        }
    }
}

이 과정 설명은 넘어가겠습니다.

extension ViewController {
    func createLayout() -> UICollectionViewLayout {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                              heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                               heightDimension: .fractionalHeight(1.0))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPagingCentered
        
        return UICollectionViewCompositionalLayout(section: section)
    }
}

지금 제가 만들고 싶은 형태의 콜렉션 뷰는 item이 1:1 비율, 그룹도 1:1 비율이기 때문에 위와 같이 설정해주었습니다. 그리고 섹션에 .orthogonalScrollingBehavior 라는 부분이 있는데, 이 부분이 스크롤이 가로로 할 수 있도록 해줍니다. 그리고 옵션인 .groupPagingCentered는 이미지를 넘길 때 group의 중앙이 오도록 하는 방법입니다.

*아래 코드를 참고하세요.

@available(iOS 13.0, *)
public enum UICollectionLayoutSectionOrthogonalScrollingBehavior : Int, @unchecked Sendable {
    
    // default behavior. Section will layout along main layout axis (i.e. configuration.scrollDirection)
    case none = 0
    
    // NOTE: For each of the remaining cases, the section content will layout orthogonal to the main layout axis (e.g. main layout axis == .vertical, section will scroll in .horizontal axis)
    
    // Standard scroll view behavior: UIScrollViewDecelerationRateNormal
    case continuous = 1
    
    // Scrolling will come to rest on the leading edge of a group boundary
    case continuousGroupLeadingBoundary = 2

    // Standard scroll view paging behavior (UIScrollViewDecelerationRateFast) with page size == extent of the collection view's bounds
    case paging = 3
    
    // Fractional size paging behavior determined by the sections layout group's dimension
    case groupPaging = 4
    
    // Same of group paging with additional leading and trailing content insets to center each group's contents along the orthogonal axis
    case groupPagingCentered = 5
}

아래는 Delegate와 DataSource를 설정해주었습니다.

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print(#fileID, #function, #line, "- \(indexPath)")
    }
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",
                                                            for: indexPath) as? CollectionViewCell else {
            return UICollectionViewCell()
        }
        
        return cell
    }
}

예시에서 사용된 것들을 보니까 Delegate와 DataSource를 프로토콜로 사용하지 않고 클로저 형식으로 하던데, 일단 저는 클로저 형식으로 하는 것이 익숙하지 않아 Delegate와 DataSource를 사용했습니다.

또 UICollectionViewDiffableDataSoruce 가 있는데, 이것은 UICollectionViewDataSource와는 다른 방식으로 스스로 계산하고 업데이트하는 똑똑한 프로토콜 같더라구요. 이 부분에 대해서도 추가적인 공부가 필요할 거 같습니다.

그 부분은 다음 글에서 뵙겠습니다.

반응형