TIL✏️

[iOS] UICollectionView: Header(헤더) 사용하기

yujjne 2024. 4. 23. 23:48

 

오전, 오후 시간 각각의 영화 시간대가 출력되는 각각의 컬렉션 뷰를 구성하고 있었다.

오전, 오후라는 제목을 넣고 싶어서 처음에는 다음과 같이 라벨 2개와 컬렉션 뷰 2개를 구성했었다.

@IBOutlet weak var morningLabel: UILabel!
@IBOutlet weak var morningCollectionView: UICollectionView!
@IBOutlet weak var afternoonLabel: UILabel!
@IBOutlet weak var afternoonCollectionView: UICollectionView!

 

2개의 컬렉션 뷰 중 하나의 셀만 선택하여 스타일을 변경 후 표시해주고 싶었는데 여러 방법을 시도해봐도 처리가 어려웠다. 

 

▼ 시도 방법 확인하기

더보기

시도 방법 1. 이전 선택한 셀의 스타일을 초기화

// MARK: - UICollectionViewDelegat
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // 이전에 선택된 셀의 배경색 초기화
    if let previousSelectedIndexPath = selectedIndexPath {
        if let previousCell = collectionView.cellForItem(at: previousSelectedIndexPath) {
            previousCell.contentView.backgroundColor = .clear
        }
    }
    
    // 선택된 셀의 배경색 변경
    if let selectedCell = collectionView.cellForItem(at: indexPath) {
        selectedCell.contentView.backgroundColor = .green
    }
    
    // 선택된 셀의 인덱스 저장
    selectedIndexPath = indexPath
}

 

시도 방법 2.  모든 셀의 선택 상태를 해제

// MARK: - UICollectionViewDelegat
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // 모든 셀의 선택 상태를 해제
    for index in 0..<collectionView.numberOfItems(inSection: 0) {
        let indexPath = IndexPath(item: index, section: 0)
        collectionView.deselectItem(at: indexPath, animated: false)
    }

    // 선택된 셀의 스타일 변경
    if let cell = collectionView.cellForItem(at: indexPath) {
        cell.isSelected = true
        cell.contentView.backgroundColor = .green // 스타일 적용
    }

    // 선택된 셀의 인덱스 저장
    selectedIndexPath = indexPath
}

 

시도 방법 3. 선택된 셀의 IndexPath를 저장하여 선택되지 않은 컬렉션뷰의 선택 해제 후 리로드

// MARK: - UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // 선택된 셀의 IndexPath 저장
    if collectionView == morningCollectionView {
        selectedIndexPathForMorning = indexPath
        selectedIndexPathForAfternoon = nil // 오전 시간대의 선택이 있을 때는 오후 시간대의 선택 해제
    } else if collectionView == afternoonCollectionView {
        selectedIndexPathForAfternoon = indexPath
        selectedIndexPathForMorning = nil // 오후 시간대의 선택이 있을 때는 오전 시간대의 선택 해제
    }

    // 컬렉션 뷰 리로드
    collectionView.reloadData()
}

 

 

이전에 선택된 셀을 찾아 스타일을 해제해봐도,

데이터가 변경될 때 셀을 다시 로드해줘도,, 같은 문제가 계속 발생했다 !!

 

결국 하나의 셀만 선택되지 않는 문제가 발생한 것인데 그 이유는 두 개의 컬렉션 뷰가 각각의 셀 선택을 관리하려고 시도하기 때문이다!

 

그래서 결국 컬렉션 뷰 2개를 이용해서 구현하는 방식을 바꾸기로 결정했다.

2개로 나누는 것보다는 하나의 컬렉션 뷰로 구성해야 더 효율적인 것 같아

오전 오후의 시간대를 나누기 위해 섹션을 나누는 방법 중 알아낸 것은 헤더를 추가하는 것이었다.

 

헤더란?

컬렉션뷰의 헤더는 컬렉션뷰의 섹션(그룹) 위에 표시되는 영역이다. 

일반적으로 섹션의 제목이나 추가 정보를 표시하는 데 사용된다.

헤더는 섹션의 시작 부분에 위치하며, 섹션과 관련된 데이터를 시각적으로 나타내는 데 도움이 된다.

 

셀과 유사하게 추가할 수 있는데 조금 차이가 있다.

  • Cell -> UICollectionViewCell
  • Header -> UICollectionReusableView

UICollectionReusableView를 사용해서 만들 수 있고 헤더를 커스텀하는 방식은 기존 cell처럼 UICollectionViewDelegateFlowLayout에서 조정할 수 있다.
이후 헤더를 collectionView에 register하고 불러와 사용하면 된다!

 

해결 방법

그래서 헤더를 공부한 후 문제를 해결한 방법으로는

두 개의 컬렉션 뷰 대신 하나의 컬렉션 뷰를 사용하여 모든 시간을 표시하고, 각각의 시간을 "오전"과 "오후"로 나누는 헤더를 추가하는 것이었다. 이렇게 하면 하나의 컬렉션 뷰에서만 셀 선택을 관리하면 되기 때문에 여러 개의 셀이 선택되는 문제가 해결되었다.

 

컬렉션뷰Cell과 동일하게 Header 등록하기
func register() {
    timeCollectionView.register(UINib(nibName: "TimeCell", bundle: nil), forCellWithReuseIdentifier: "TimeCell")
    timeCollectionView.register(UINib(nibName: "HeaderView", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
}

 

UICollectionViewDelegateFlowLayout으로 헤더뷰 설정하기
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: collectionView.bounds.width, height: 50) // 헤더 높이 설정
}

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    if kind == UICollectionView.elementKindSectionHeader {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView

        // 헤더 뷰 설정
        headerView.titleLabel.text = indexPath.section == 0 ? "오전" : "오후"

        return headerView
    } else {
        return UICollectionReusableView()
    }
}

👉 다음과 같이 collectionView(_:viewForSupplementaryElementOfKind:at:)를 사용하여 헤더를 구성했다.
이렇게 하면 하나의 컬렉션 뷰에 두 개의 섹션을 가진 구조를 만들어 각각의 섹션에 "오전"과 "오후"를 표시할 수 있게 된다!

 

 

빌드를 해서 확인해보면 원하는 대로 컬렉션 뷰에 각 섹션 헤더가 추가된 걸 확인할 수 있다.

 

 

원래 구현 목적이었던 선택한 셀의 스타일을 변경하는 코드를 구성해보자.

이제 간단히 작성해볼 수 있다.

didSelectItemAt()
// MARK: - UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // 선택된 셀의 스타일 변경
    if let cell = collectionView.cellForItem(at: indexPath) as? TimeCell {
        cell.borderView.backgroundColor = .systemIndigo
        print("Selected time: \(cell.titleLabel.text ?? "")")
        selectedTime = cell.titleLabel.text ?? ""
    }

    // 이전에 선택된 셀이 있다면 선택 해제
    if collectionView == collectionView {
        if let selectedIndexPath = selectedIndexPath {
            if let cell = collectionView.cellForItem(at: selectedIndexPath) as? TimeCell {
                cell.borderView.backgroundColor = .clear // 이전에 선택된 셀의 스타일 초기화
            }
        }
    }
    // 선택된 셀의 인덱스 저장
    selectedIndexPath = indexPath
}

선택된 셀의 배경색을 변경하여 강조해준다. 

또한 이전에 선택된 셀이 있다면 선택을 해제하고, 마지막으로 선택된 셀의 인덱스를 저장한다.

 

 

이제 처음에 셀의 스타일 변경여러개가 선택되어 골치아팠던 문제가 잘 해결되었다 ㅎㅎ!

헤더를 사용해서 섹션을 구분할 수 있어 원래 구성하려고 했던 2개의 컬렉션 뷰 구성보다

구조가 더 깔끔하고 중복되는 코드들도 줄어들었다.