Danny의 iOS 컨닝페이퍼
article thumbnail

[TIL #12] 2023 / 04 / 11

분명히 이전에 CollectionView를 사용해 봤는데,

오랜만에 다시 만드려니까 헷갈리더라고요.

 

이번 기회에 다시 간단히 정리를 하려고 합니다.

 

 

FlowLayout

흐름 있는 레이아웃? 

간단하게 Grid형태의 레이아웃을 쉽게 구성할 수 있도록 도와는 클래스입니다.

 

일단 공식문서는 여기 있습니다

https://developer.apple.com/FlowLayout

https://developer.apple.com/UICollectionviewFlowlayout

 

FlowLayout의 Cell은 이런 순으로 배치됩니다.

 

FlowLayout의 속성들을 알아봅시다.

func createFlowLayout() -> UICollectionViewFlowLayout {
    
    let layout = UICollectionViewFlowLayout()
    // 스크롤을 방향 (기본값 vertical)
    layout.scrollDirection = .vertical
    // 그리드 줄 간격 (기본값 10)
    layout.minimumLineSpacing = 20
    // 그리드 행 간격 (기본값 10)
    layout.minimumInteritemSpacing = 20
    // 각 cell의 크기 설정. 기본 크기는 (50, 50)
    layout.itemSize = CGSize(width: 100, height: 100)
    // 동적으로 cell의 크기를 계산할 때 성능을 높여 준다고 합니다.
    layout.estimatedItemSize = CGSize(width: 100, height: 100)
    // section에 대한 여백을 설정합니다. (콘텐츠뷰 여백)
    layout.sectionInset = UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 30)
    // header, footer 크기 지정
    // 스크롤 방향에 따라서 크기가 영향을 받는다. (vertical은 height값만 갖고 계산, horizontal 일 경우 width만 영향을 받는다)
    layout.headerReferenceSize = CGSize(width: 50, height: 100)
    layout.footerReferenceSize = CGSize(width: 50, height: 100)
    // header, footer를 고정 시킴 (기본값 false)
    layout.sectionHeadersPinToVisibleBounds = true
    layout.sectionFootersPinToVisibleBounds = true
    
    return layout
}

 

대리자를 통해서 크기를 정해줄 수도 있습니다.

공식문서에는 대리자를 통해 동적으로 크기를 정해줄 주 있다고 하네요. 

// UICollectionViewDelegateFlowLayout (위의 초기 설정들 대신 값이 적용됨)
extension ViewController: UICollectionViewDelegateFlowLayout {
    // 파라미터에 들어있는 이름이 위의 속성과 비슷합니다.
    
    // 셀의 크기를 지정
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // 여백과 뷰 크기를 통해 값을 계싼해 설정하면 원하는 배치로대로 레이아웃을 쉽게 잡을 수 있다.
        return CGSize(width: (view.frame.width-70)/3, height: (collectionView.frame.height-110)/5)
    }
    
    // 그리드 줄 간격
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 20
    }
    
    // 그리드 행 간격
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 20
    }
    
    // section 여백
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    }
    
    // header, footer 크기 변경도 있어요
}

 

 

CollectionView

이제 간단히 FlowLayout을 알았으니 CollectionView를 사용해 봅시다.

간단히 생성 순서 알아봅시다.

 

1. UICollectionViewLayout과 함께 UICollectionView 객체를 생성해 줍니다

2. CollectionViewCell을 만들어 주고 재사용 가능하게 식별자를 등록합니다.

3. dataSource 채택하여 셀을 불러오고 데이터를 처리하는 작업을 해줍니다.

 

 

CollectionView 생성 (1, 2 과정)

func setupCollectioView() {
    // 만들어 준 FlowLayout을 생성해줍시다.
    let flowLayout = createFlowLayout()
    // FlowLayout을 갖고 collectionView의 객체를 생성
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
    
    //  뷰에 추가 및 레이아웃 설정
    view.addSubview(collectionView)
    collectionView.snp.makeConstraints {
        $0.edges.equalToSuperview()
    }
    
    // dataSource 채택
    collectionView.dataSource = self
        
    // 만들어준 UICollectionViewCell 클래스로 식별자를 등록해줍니다.
    collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")
}

 

dataSource 프로토콜 구현 (3 과정)

dataSource를 채택하면

numberOfItemsInSection와 cellForItemAt 메서드는 필수 구현해줘야 합니다!

extension ViewController: UICollectionViewDataSource {
    
    // section의 갯수
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    
    // cell의 갯수
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 9
    }
    
    // cell에 표현될 뷰를 설정
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        // 위에서 등록해 둔 withReuseIdentifier를 갖고 cell을 만듭니다.
        // 커스텀 셀을 만들어 사용 시 다운 캐스팅으로 설정한 타입으로 변환 시켜줘서 사용합니다.
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CustomCell else {
            fatalError("Failed to load cell!")
        }        
        return cell
    }
}

 

 

추가로 Header, Footer도 사용해 봅시다.

 

이것도  CustomCell과 마찬가지로 사용 시 CollectionView에 register를 해줘야 합니다.

register 메서드가 조금 다릅니다. (forSupplementaryViewOfKind)

 

그리고 Header와 Footer를 만들 땐, UICollectionReusableView를 상속하여 만들어 줘야 합니다.

참고로 UICollectionViewCell도 기본적으로 UICollectionReusableView를 상속하고 있어요.

 

원하는 모양의 Header와 Footer를 만들고

CustomCell을 등록한 방식과 같이 사용하면 됩니다.

// forSupplementaryViewOfKind
collectionView.register(HeaderReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header")
collectionView.register(FooterReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "footer")

 

dataSource 프로토콜에서

UICollectionReusableView을 리턴하는 메서드 사용해 줍니다.

// dequeueReusableSupplementaryView
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
extension ViewController: UICollectionViewDataSource {
 
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

        switch kind {
            
        case UICollectionView.elementKindSectionHeader:
            // 헤더 세팅
            guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as? HeaderReusableView else {
                fatalError("Failed to load Header!")
            }
            return header
            
        case UICollectionView.elementKindSectionFooter:
            // 푸터 세팅
            guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "footer", for: indexPath) as? FooterReusableView else {
                fatalError("Failed to load Footer!")
            }
            return footer
            
        default: break
        }
        
        return UICollectionReusableView()
    }
}

 

주의

header, footer의 기본 사이즈는 (0, 0)입니다.

만약 추가하게 된다면  header, footer의 크기를 정해줘야 화면에 나타납니다!

 

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!