2020. 12. 6. 21:31ㆍiOS
UICollectionView을 자주 사용하실 것 같은데 UITableView와 가장 크게 다른 점은 커스텀하게 Layout을 설정할 수 있는 프로퍼티가 있다는 것 같아요.
기본적으로 2가지 방법으로 Layout을 설정할 수 있는데, 상황에 따라 다르게 사용할 수 있어요.
2가지 방법으로는
✔️ UICollectionViewFlowLayoutDelegate 활용
✔️ UICollectionViewFlowLayout 객체 활용
이렇게 2가지 방법으로 설정할 수 있다.
UICollectionViewFlowLayout 객체를 활용하는 경우는 엄청 복잡한 레이아웃을 커스텀해야하는 경우나 간단하게 초기 값을 설정하기 위해 사용했어요.
UICollectionViewFlowLayoutDelegate 같은 경우는 마찬가지로 데이터를 이용해서 분기처리로 레이아웃을 설정해야하는 경우에 사용했어요.
UICollectionViewFlowLayout 활용
한 번 간단히 어떤 방법으로 사용했는지 예제를 보면서 알아볼게요.
먼저, UICollectionViewFlowLayout 객체를 이용하는 경우입니다.
var homeCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 36
layout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
layout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width - 20*2, height: 200)
let collectionView = UICollectionView(frame: .zero,
collectionViewLayout: layout)
return collectionView
}()
다음과 같이 간단하게 초기 값을 설정하는 경우에는 UICollectionVIewLayout 객체를 이용해서 프로퍼티를 설정하는 방법으로 설정할 수 있어요. Delegate을 사용하는 것보다 훨씬 간단하겠죠⁉️
UICollectionViewFlowLayout 객체를 Custom해서 상속받는 방식으로도 사용할 수 있어요.
헤더가 Scroll에 따라 늘어나는 헤더를 구현할 때 썻던 예제로 확인해볼게요.
class StretchHeaderLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)
layoutAttributes?.forEach { attributes in
if attributes.representedElementKind == UICollectionView.elementKindSectionHeader {
guard let collectionView = collectionView else { return }
let contentOffsetY = collectionView.contentOffset.y
if contentOffsetY > 0 { return }
let width = collectionView.frame.width
let height = attributes.frame.height - contentOffsetY
attributes.frame = CGRect(x: 0, y: contentOffsetY, width: width, height: height)
}
}
return layoutAttributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
class ViewController: UIViewController {
@IBOutlet weak var stickyCollectionView: UICollectionView!
let stickyFlowLayout = StretchHeaderLayout()
override func viewDidLoad() {
super.viewDidLoad()
stickyCollectionView.setCollectionViewLayout(stickyFlowLayout, animate, true)
}
}
다음과 같이 UICollectionViewFlowLayout의 메소드를 오버라이드해서 커스텀하게 레이아웃을 설정할 수 있어요.
그럼 마지막에 저 UICollectionViewFlowLayout의 메소드와 불리는 호출 주기를 알아보고 우선 Delegate을 활용한 방법 먼저 알아볼게요.
UICollectionViewFlowLayoutDelegate 활용
어떤 ViewController에 UICollectionViewFlowDelegate을 채택해서 사용하는 방법입니다.
예제로 볼게요.
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let estimateHeight = (collectionView.frame.height - 100) / 2
let estimateWidth = (collectionView.frame.width - collectionView.frame.width / 14.43 - (collectionView.frame.width / 5.86)) / 2
return CGSize(width: estimateWidth, height: estimateHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return collectionView.frame.width / 14.43
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 29
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: (collectionView.frame.width / 11.72), bottom: 70, right: (collectionView.frame.width / 11.72))
}
}
이렇듯 사용할 ViewController에서 해당 프로토콜을 채택해서 데이터에 맞게 CollectionView의 레이아웃을 커스텀 할 수 있어요.
아무래도 쓸 코드도 조금 많아지긴해서 위에 UICollectionViewFlowLayout 객체를 이용하는 것보다는 복잡하죠..⁉️
하지만 데이터에 맞게 커스텀 해야할 경우에는 사용하기 좋은 것 같아요. (약간 중간 난이도 정도..⁉️)
이렇게 Delegate을 이용할 때는 위의 코드와 같이 사용할 수 있어요!
마지막으로 UICollectionViewFlowLayout의 메소드와 호출시기에 대해 알아볼게요~
UICollectionViewLayout 메소드
아래 그림이 CollectionView와 UICollectionViewFlowLayout 객체가 상호작용하는 과정입니다.
그림을 보면 처음 CollectionView가 prepareLayout을 통해 레이아웃의 초기값을 설정하고 이후 CollectionView의 크기를 받고 각 Cell의 크기들을 지정하고 받는 것을 알 수 있죠⁉️
그렇다면 그러한 동작들은 어느 메소드에서 이루어 질까요.
하나씩 볼게요.
- prepare() : 초기에 CollectionView의 레이아웃을 설정할 때 호출됩니다. 즉, 사이즈를 위한 어떤 셋팅이 필요할 때, 이 메소드를 override해서 사용할 수 있습니다. (CollectionView의 초기 설정이 아닌 레이아웃 연산이 일어나기 전입니다.)
- layoutAttributesForElements(rect:) -> [UICollectionViewLayoutAttributes]? : CollectionView 안의 모든 요소에 대한 Layout 요소들을 리턴합니다. (현재 화면에 보이는 요소)
- layoutAttributesForItem(indexPath:) -> UICollectionViewLayoutAttributes? : CollectionView에 요구한 IndexPath의 Layout 요소들을 리턴합니다.
- shouldInvalidateLayout(newBounds:) -> Bool : Bounds에 변화가 있을 때마다, 함수를 호출할 지 결정합니다. InvalidateLayout(with:)을 호출합니다.
아직 UICollectionViewFlowLayout을 활용해서 복잡한 뷰는 구현해보지 않았는데, 다음에는 이 객체를 이용해서 Pinterest Layout을 구현하는 연습을 해볼게요.
혹시 틀린 내용이나 궁금한게 있으면 말해주세요~~
'iOS' 카테고리의 다른 글
[iOS] 화면전환 애니메이션 Custom - UIViewControllerAnimatedTransitioing, UIViewControllerTransitioningDelegate (0) | 2020.12.20 |
---|---|
[iOS] CollectionView Carousel 3D Effect (0) | 2020.12.13 |
[iOS] Version + Build 이용 프로젝트 관리 (0) | 2020.11.28 |
[iOS] Paging Tabbar 커스텀하기 (3) | 2020.11.08 |
[iOS] QR Code Scanner 만들기 - AvFoundation 이용 (0) | 2020.10.16 |