1. 기본적인 컬렉션뷰의 구조와 사용
class ViewController: UIViewController {
//콜렉션뷰 선언
var collectionView: UICollectionView!
//데이터 배열
let data = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
override func viewDidLoad() {
super.viewDidLoad()
//1. 콜렉션뷰 레이아웃 설정 (기본적인 그리드 레이아웃)
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 100, height: 100) //셀 크기
layout.minimumLineSpacing = 10 //셀 사이 수직 간격
layout.minimumInteritemSpacing = 10 //셀 사이 수평 간격
//2. 콜렉션뷰 초기화
collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .white //배경색 설정
//3. 콜렉션뷰에 사용할 셀 등록
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
//4. 델리게이트 및 데이터소스 설정
collectionView.delegate = self
collectionView.dataSource = self
//5. 뷰에 콜렉션뷰 추가
self.view.addSubview(collectionView)
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate {
//섹션당 항목 수
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
//셀 생성 및 재사용
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//1. 셀 재사용
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
//2. 셀 초기화: 이전에 추가된 하위 뷰를 모두 제거 (재사용 시 기존 뷰가 남아있을 수 있음)
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
//3. 셀에 데이터 추가 (라벨 추가)
let label = UILabel(frame: CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height))
label.text = data[indexPath.row]
label.textAlignment = .center
label.textColor = .white
//4. 셀에 배경색 추가
cell.backgroundColor = .blue
//5. 셀에 라벨 추가
cell.contentView.addSubview(label)
return cell
}
//셀 선택 처리
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedItem = data[indexPath.row]
print("\(selectedItem) 선택됨")
}
}
2. 필수 프로토콜
필수 프로토콜은 데이터를 관리하고 사용자 상호작용을 처리하기 위해 사용된다
ViewController를 확장(익스텐션) 한다
2-1. UICollectionViewDataSource
: 데이터 관리를 담당하는 메서드
🌈 데이터 소스 필수 메서드
- numberOfItemsInSection : 섹션당 항목 수를 반환
- 아래 예시에서는 데이터의 수량만큼의 섹션 수를 반환하고 있다
- 뷰당 보여줄 섹션 수가 한정되어 있다면 int 값으로도 설정 가능(3, 5, 10 등)
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
- cellForItemAt : 셀을 생성하고 구성
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
//셀 내부 설정
return cell
}
2-2. UICollectionViewDelegate
: 사용자 상호작용을 처리
UICollectionViewDelegate는 콜렉션뷰에서 사용자가 상호작용할 때 발생하는 이벤트나 동작을 처리하기 위한 프로토콜
이를 통해 셀 선택, 하이라이트, 스크롤 이벤트 등을 쉽게 제어한다
1. 셀 선택: 사용자가 특정 셀을 터치했을 때 호출되는 메서드
2. 셀의 표시 여부 관리: 셀이 표시되기 직전, 또는 표시된 후에 호출되는 메서드
3. 하이라이트 및 선택 상태 관리: 셀을 하이라이트하거나 선택 상태를 변경할 때 호출되는 메서드
4. 스크롤 이벤트 관리: 사용자가 스크롤할 때 호출되는 메서드
5. 레이아웃 조정: 셀의 크기 또는 위치를 커스터마이즈할 수 있는 메서드
3. 컬렉션 뷰의 셀
3-1. 셀 재사용 메서드
: 기존의 셀을 재사용하게 만들어준다
//기본셀
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "BasicCell")
//커스텀셀
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: "CustomCell")
→ 이부분이 많이 헷갈려서 조금 더 찾아보고 정리한 내용 ⭐️⭐️⭐️
1. register의 역할
셀을 미리 등록해 둔다
등록한 셀을 재사용하여 성능을 최적화한다
2. CustomCollectionViewCell.self
등록할 셀의 클래스명(Custom~~Cell)을 전달하는 부분
3. forCellWithReuseIdentifier: "CustomCell"
셀을 재사용하기 위한 식별자
사용자가 임의로 지정하는 문자열이며, 추후 dequeueReusableCell 메서드를 호출할 때 이 식별자를 통해 해당 셀을 가져온다
[ 예시 ]
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "BasicCell")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BasicCell", for: indexPath)
3-2. 셀에 데이터 추가
: indexPath.row로 label에 순서대로 배열의 데이터를 넣어준다
label.text = data[indexPath.row]
3-3. 셀 선택 처리(didSelectItemAt)
: 유저가 셀을 선택했을 때 호출되는 메서드다(프린트문으로 예제)
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedItem = data[indexPath.row]
print("\(selectedItem) 선택됨")
}
4. UICollectionViewFlowLayout
UICollectionViewFlowLayout란 컬렉션뷰에서 셀을 수평 또는 수직으로 스크롤이 가능한 그리드 형태로 배치한다
각 셀의 크기와 간격을 설정하고 헤더나 푸터를 추가할 수 있다
4-1. 자주 사용되는 속성
속성 이름 | 설명 |
itemSize | 각 셀의 크기 (가로, 세로) 기본값은 50 x 50. |
minimumLineSpacing | 셀 사이의 수직 간격 |
minimumInteritemSpacing | 셀 사이의 수평 간격 |
scrollDirection | 스크롤 방향 .vertical (기본값) 또는 .horizontal |
sectionInset | 섹션 내부의 여백(상, 좌, 하, 우) |
headerReferenceSize | 섹션 헤더의 크기 |
footerReferenceSize | 섹션 푸터의 크기 |
4-2. 기본적인 사용 방법
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var collectionView: UICollectionView!
let data = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
override func viewDidLoad() {
super.viewDidLoad()
// 1. 레이아웃 객체 생성
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 100, height: 100) // 셀 크기
layout.minimumLineSpacing = 20 // 수직 간격
layout.minimumInteritemSpacing = 10 // 수평 간격
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) // 섹션 여백
// 2. 컬렉션뷰 초기화
collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .white
collectionView.delegate = self
collectionView.dataSource = self
// 3. 셀 등록
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
// 4. 컬렉션뷰 추가
self.view.addSubview(collectionView)
}
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .blue
// 셀의 텍스트 추가
let label = UILabel(frame: cell.bounds)
label.text = data[indexPath.row]
label.textAlignment = .center
label.textColor = .white
cell.contentView.addSubview(label)
return cell
}
}
1) 레이아웃 객체 생성
- UICollectionViewFlowLayout 객체를 생성한다
- itemSize : 셀 크기 설정
- minimumLineSpacing : 수직 간격
- minimumInteritemSpacing : 수평 간격
- sectionInset : 섹션의 여백 *섹션: 셀을 그룹으로 묶는 단위
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 100, height: 100) // 셀 크기
layout.minimumLineSpacing = 20 // 수직 간격
layout.minimumInteritemSpacing = 10 // 수평 간격
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) // 섹션 여백
2) 컬렉션뷰 초기화
- 컬렉션뷰 초기화 시 1번에서 설정한 UICollectionViewFlowLayout 객체 전달
let layout = UICollectionViewFlowLayout()
// ~~ 레이아웃 설정 ~~
collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
3) 나머지 일반 컬렉션뷰와 동일
5. Custom UICollectionViewLayout
위에서 알아본 <4. UICollectionViewFlowLayout> 에서 제공하지 않는 복잡한 레이아웃을 구현한다
컬렉션뷰에서 셀, 헤더, 푸터를 배치하는 규칙을 정의하는 클래스이다
예를 들어 물결치는 레이아웃, 계단식 배치 등 커스텀 레이아웃을 만들 수 있다
5-1. 기본 작동 원리
셀의 위치 계산 : 셀이 화면의 어디에 배치될지 결정해야 한다
화면 크기 계산 : 전체 콘텐츠의 크기를 정의한다
레이아웃 속성 제공 : 컬렉션뷰가 각 셀이 어떻게 보여야할지 알게해야 한다
5-2. 핵심 메서드
CollectionViewLayout을 상속받아 메서드를 구현한다
메서드명 | 역할 |
prepare() | 셀의 위치, 크기 등의 정보를 미리 계산 |
layoutAttributesForElements(in:) | 특정 영역에 표시될 셀의 위치와 크기를 반환 |
layoutAttributesForItem(at:) | 특정 셀의 레이아웃 속성을 반환 |
collectionViewContentSize | 컬렉션뷰의 전체 콘텐츠 크기를 반환 (스크롤 가능한 크기) |
5-3. 기본적인 사용방법
import UIKit
class PinterestLayout: UICollectionViewLayout {
private var cache: [UICollectionViewLayoutAttributes] = [] // 셀 위치를 저장하는 캐시
private var contentHeight: CGFloat = 0 // 콘텐츠의 전체 높이
private var contentWidth: CGFloat {
guard let collectionView = collectionView else { return 0 }
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
override var collectionViewContentSize: CGSize {
// 전체 콘텐츠 크기를 반환 (스크롤 가능 영역 정의)
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
// 레이아웃 준비: 셀의 위치를 계산
guard let collectionView = collectionView else { return }
let numberOfColumns = 2 // 2열 레이아웃
let cellPadding: CGFloat = 8 // 셀과 셀 사이 여백
let columnWidth = contentWidth / CGFloat(numberOfColumns) // 열 너비
var xOffset: [CGFloat] = [] // 각 열의 X 좌표
for column in 0..<numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var yOffset: [CGFloat] = .init(repeating: 0, count: numberOfColumns) // 각 열의 Y 좌표
var column = 0 // 현재 열
for item in 0..<collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
// 임의로 셀 높이를 설정 (예: 랜덤 높이)
let cellHeight = CGFloat(arc4random_uniform(100) + 150)
let height = cellHeight + cellPadding * 2
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
// 레이아웃 속성 생성
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes) // 속성 캐시에 저장
contentHeight = max(contentHeight, frame.maxY) // 콘텐츠 높이 업데이트
yOffset[column] = yOffset[column] + height // 현재 열의 Y 좌표 업데이트
// 다음 열로 이동
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// rect(화면에 보이는 영역) 안에 포함된 셀의 레이아웃 속성을 반환
return cache.filter { $0.frame.intersects(rect) }
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// 특정 셀의 레이아웃 속성을 반환
return cache[indexPath.item]
}
}
'💡 Today I Learned > 스터디 자료정리' 카테고리의 다른 글
[스터디11일차][RxSwift] 검색 어플 만들 때 유용한 연산자 (0) | 2025.01.20 |
---|---|
[스터디10일차] Disposable (0) | 2025.01.08 |
[스터디8일차] Diffable Data Source (0) | 2024.11.13 |
[스터디7일차] 객체지향 & 프로토콜 (0) | 2024.11.12 |
[스터디6일차] 테스트 코드👩🏻🔧 (1) | 2024.10.30 |