Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- tableView
- Refactoring
- swiftUI
- collectionview
- UITextView
- SWIFT
- Human interface guide
- rxswift
- Xcode
- combine
- 리펙터링
- swift documentation
- UICollectionView
- 애니메이션
- uitableview
- Clean Code
- 클린 코드
- ios
- clean architecture
- ribs
- HIG
- Protocol
- MVVM
- 리팩토링
- 리펙토링
- RxCocoa
- uiscrollview
- 스위프트
- map
- Observable
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 2. UICollectionView의 DecorationView, SupplementaryView, 커스텀 Layout 본문
iOS 응용 (swift)
[iOS - swift] 2. UICollectionView의 DecorationView, SupplementaryView, 커스텀 Layout
jake-kim 2022. 3. 9. 12:491. UICollectionView의 SupplementaryView(HeaderView, FooterView, UICollectionReusableView)
2. UICollectionView의 DecorationView, SupplementaryView 커스텀 CollectionViewFlowLayout
DecorationView 이란?
- collectionView에 Cell에 의존하지 않고 별도로 추가할 수 있는 뷰
- DecorationView 전용 뷰를 따로 만든 후, register()해서 사용
- UICollectionViewLayout에서 register()가 가능하므로, UICollectionViewFlowLayout이나 UICollectionViewLayout을 상속받아서 구현
- prepare() 메소드 안에서 register()후에 UICollectionViewLayoutAttributes(forDecorationViewOfKind:with:)로 attributes로 레이아웃 정의
예제에서 UI작성의 편의를 위해 사용한 프레임워크
pod 'SnapKit'
pod 'Then'
DeocraitonView 구현 방법
- DecorationView 커스텀 클래스 정의
- SupplementaryView와 동일하게 `UICollectionReusableView`를 상속받아서 구현
- 단순히 회색 배경을 가지고 있는 뷰
import UIKit
import SnapKit
final class BackgroundDecorationView: UICollectionReusableView {
static let id = "BackgroundDecorationView"
static var kind: String { Self.id }
override var reuseIdentifier: String? {
Self.id
}
override init(frame: CGRect) {
super.init(frame: frame)
let view = UIView()
view.backgroundColor = .systemGray3
view.layer.cornerRadius = 8.0
view.layer.masksToBounds = true
view.layer.borderColor = UIColor.clear.cgColor
view.layer.borderWidth = 1.0
self.addSubview(view)
view.snp.makeConstraints {
$0.edges.equalToSuperview()
$0.size.equalTo(50).priority(999)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
}
override func prepareForReuse() {
super.prepareForReuse()
self.prepare()
}
func prepare() {
}
}
- DecorationView를 사용하려면, UICollectionViewLayout의 prepare()메소드 안에서 register()하고 레이아웃을 정의해야 하므로 커스텀
- 델리게이트 하나 추가: 데코레이션 뷰의 CGRect값은 ViewController쪽에서 주입되도록 델리게이션
// DecorationCollectionViewFlowLayout.swift
import UIKit
protocol DecorationCollectionViewFlowLayoutDataSource: AnyObject {
func getDecorationViewRect(_ collectionView: UICollectionView, indexPath: IndexPath) -> CGRect
}
final class DecorationCollectionViewFlowLayout: UICollectionViewFlowLayout {
// TODO: register(), 데코레이션 뷰 레이아웃 설정
}
- 캐시와 델리게이트 준비
private var cachedDecorationViewAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
weak var dataSource: DecorationCollectionViewFlowLayoutDataSource?
- prepare()에서 DecorationView를 등록하고 레이아웃 계산
- 1. register()로 DeocrationView 등록
- 2. Section에 해당하는 Item들의 indexPath를 구하여 delegate를 통하여 VC에 DecorationView의 CGRect를 요청하여 반영
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
guard let dataSource = dataSource else { fatalError("Conform DecorationCollectionViewFlowLayoutDataSource") }
self.cachedDecorationViewAttributes.removeAll()
// 1. DecorationView 등록
self.register(BackgroundDecorationView.self, forDecorationViewOfKind: BackgroundDecorationView.id)
// 2. [Section, [Item]]
let numberOfItemsListForSection = (0..<collectionView.numberOfSections)
.map { section in return (section, collectionView.numberOfItems(inSection: section)) }
.map { ($0, (0..<$1).map { $0 }) }
numberOfItemsListForSection
.forEach { (section, itemList) in
itemList.forEach { [weak self] item in
let indexPath = IndexPath(item: item, section: section)
let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: BackgroundDecorationView.id, with: indexPath)
attributes.frame = dataSource.getDecorationViewRect(collectionView, indexPath: indexPath)
self?.cachedDecorationViewAttributes[indexPath] = attributes
}
}
}
- layoutAttributesForElements(in:) 메소드에서 데코레이션 뷰의 z-index값을 낮추는 작업 수행
- 데코레이션 뷰가 아닌 일반적인 셀의 attributes를 가져오려면, super.layoutAttributesForElements(in:) 호출 (현재 UICollectionViewFlowLayout을 상속받고 있으므로)
private enum Threshold {
static let cellStartZIndex = 100
static let decorationStartZIndex = 0
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// 1. DecorationView가 아닌 것들(Cell)을 획득
var array = super.layoutAttributesForElements(in: rect)
guard self.collectionView?.numberOfSections ?? 0 > 0 else { return array }
// 2. DecorationView가 아닌 것
var cellZIndex = 0
array?.forEach {
$0.zIndex = cellZIndex + Threshold.cellStartZIndex
cellZIndex += 1
}
// 3. DecorationView인 것
var decorationZIndex = 0
for (_, attributes) in self.cachedDecorationViewAttributes {
if attributes.frame.intersects(rect){
attributes.zIndex = decorationZIndex + Threshold.decorationStartZIndex
array?.append(attributes)
}
decorationZIndex += 1
}
return array
}
- layoutAttributesForDecorationView(ofKind:at:)에서 데코레이션 attributes를 반환
override func layoutAttributesForDecorationView(
ofKind elementKind: String,
at indexPath: IndexPath
) -> UICollectionViewLayoutAttributes? {
self.cachedDecorationViewAttributes[indexPath]
}
* cf) SupplementaryView도 DecorationView와 동일하게 사용하고, 단 메소드만 다른 것
override func layoutAttributesForSupplementaryView(
ofKind elementKind: String,
at indexPath: IndexPath
) -> UICollectionViewLayoutAttributes? {
<#code#>
}
* 전체 코드: https://github.com/JK0369/ExDecoration
* 참고
'iOS 응용 (swift)' 카테고리의 다른 글
Comments