CollectionView 관철하기 2번째 포스팅입니다. CollectionView에 대해 공부하다 보니 글을 쓰면서도 아 이거는 따로 또 써야겠다 싶었던 토픽들을 하나씩 다룰 예정입니다 😃 이번에는 Layout에 대해 조금 더 자세히 알아보고 싶은데요, 이번에는 조금 실제로 구현을 해보며 Layout을 알아가려 합니다.
CollectionView는 Flow Layout을 사용하잖아요, 이 layout이 한줄을 쫙 채우죠?

이런식으로 한줄을 넘어갈때는 그 줄을 꽉 채우고 넘어가게 됩니다. 이게 의도할 수도 있지만 보통 Cell간에 간격이 일정하지 않아 보기에 이뻐 보이지 않습니다. 보통 Tag들을 표현할때는 아래 사진과 같이 왼쪽으로 정렬해서 표현합니다. 더 가지런해 보이고 정돈되어 보이죠 ㅎㅎ

이것을 구현하기 위해서 기존에 존재하는 FlowLayout을 조금 수정해서 구현해보도록 하겠습니다. 그러기 위해서 Layout이 어떻게 동작하는지 조금 알아보면서 가보도록 할꼐요 ~
⚙️Layout Process

apple에서 제공하는 그림 하나 보고 가시죠 CollectionView는 layout객체에게 prepareLayout, collectionViewContentSize, layoutAttributesForElementsInRect 세가지를 요구 합니다. 이 사진은 옛날 꺼라 메소드 이름이 조금 변경되었는데요 prepare, collectionViewContentSize, layoutAttributesForElements이렇게 이름이 변경되었으니 참고하시면됩니다. 즉 이 3가지의 메소드가 핵심입니다. 이 3가지만 부수고 갑시당~!
prepare
prepare단계는 layoutAttributes를 위한 준비 단계입니다. Cell마다 어떠한 속성값을 줄지 결정하는 단계는 세번째 layoutAttributesForElements 인데요, 이 단계를 위해 필요한 것들을 계산 해두기 위한 메소드입니다. 현재 우리가 하려는 tag는 FlowLayout을 상속해서 구현할 것이기 때문에 FlowLayout의 prepare만 호출하고 넘깁니다. 이부분이 필요한 것은 Circular layout이나 조금 더 복잡한 layout을 구현할때 사용할 것입니다.
override func prepare() {
print("Layout: prepare")
super.prepare()
}
collectionViewContentSize
collectionViewContentSize는 collectionView의 ContentSize를 몇으로 할것인가 결정하는 변수입니다. ContentSize는 어떻게 보면 view의 bounds라고 할 수 있는데요, 실기기는 width가 약 400, height는 800정도입니다. 하지만 우리가 표현해야할 Size는 Cell이 20개 30개가 된다면 이 크기를 넘어가니 스크롤을 해야하잖아요? 따라서 그 안에 Content들의 Size를 결정하는 곳입니다. 이부분에서 FlowLayout은 안에 itemSize의 값을 통해 계산 하던가
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
}
Delegate의 이 method를 통해 하나하나 Cell을 호출한 후 Size를 결정하게 됩니다. 우리가하는 프로젝트에서도 FlowLayout의 도움을 얻어
override var collectionViewContentSize: CGSize {
print("Layout: collectionViewContentSize")
return super.collectionViewContentSize
}
이렇게 진행하고 넘어가겠습니다.
layoutAttributesForElements
먼저 어떠한 속성들이 있는지 살펴보겠습니다
@available(iOS 6.0, *)
open class UICollectionViewLayoutAttributes : NSObject, NSCopying, UIDynamicItem {
open var frame: CGRect
open var center: CGPoint
open var size: CGSize
open var transform3D: CATransform3D
@available(iOS 7.0, *)
open var bounds: CGRect
@available(iOS 7.0, *)
open var transform: CGAffineTransform
open var alpha: CGFloat
open var zIndex: Int // default is 0
open var isHidden: Bool // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
open var indexPath: IndexPath
...
배치하기위한 frame과 zindex IndexPath를 포함하고 있습니다. 이것들로 Cell의 위치를 결정하는 것입니다. indexpath로 각각 지정된 Cell이있습니다. 배열의 원소 순서는 상관이 없습니다. 이 속성을 수정하여 Left align을 구현해보겠습니다.
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
print("Layout: layoutAttributesForElements")
guard let atts = super.layoutAttributesForElements(in: rect) else {
return []
}
for i in atts {
print(i.frame, i.indexPath.row)
}
return atts
}
frame, index | view |
---|---|
![]() |
![]() |
기존 FlowLayout이 배치한 Attributes들을 print로 출력해보았습니다. indexPath 별로 하나씩 비교해보시면 매칭이 되실것입니다. FlowLayout이 이미 한줄에 어떠한 Cell이 배치될지 결정을 해놓았기 때문에 할일 이 대폭 줄었습니다. 우리는 Flow layout이 배치해둔 이 Cell들을 왼쪽으로 일정한 간격으로 밀어주면 됩니당.😇 layoutAttributesForElements의 frame을 직접 수정할 수 있습니다. 진행해보겠습니다~
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
print("Layout: layoutAttributesForElements")
guard let atts = super.layoutAttributesForElements(in: rect) else {
return []
}
for i in atts {
print(i.frame, i.indexPath.row)
}
/* 추가 */
for (idx, val) in atts.enumerated() {
if val.frame.origin.x == 10 {
continue
}
val.frame.origin.x = atts[idx-1].frame.maxX + minimumLineSpacing
}
/* */
return atts
}
origin.x == 10 을 비교하는 부분은 제가 CollectionView의 Inset을 10으로 주어서 10부터 시작했습니다. 즉 가장 왼쪽에 있는 Cell은 넘어가고, 그 다음 Cell부터 그 전 Cell의 가장 끝 부분에 + spacing으로 배치하는 모습입니다. 쉽죠 ~? 직접 이 속성들을 조작하면서 layout을 완성하면 됩니다.!

그럼 오른쪽이나 중앙으로도 가능할것 같지 않나요? 사실 오른쪽으로 정렬하는건 쓸만하지 않지만 한번 해볼까요 ~?!
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
print("Layout: layoutAttributesForElements")
guard let atts = super.layoutAttributesForElements(in: rect) else {
return []
}
for i in atts {
print(i.frame, i.indexPath.row)
}
for (idx, val) in atts.enumerated() {
if val.frame.origin.x == 10 {
continue
}
val.frame.origin.x = atts[idx-1].frame.maxX + minimumLineSpacing
}
let contentMaxX: CGFloat = collectionViewContentSize.width - 10
let grouped = Dictionary(grouping: atts, by: { $0.frame.minY })
grouped.values.forEach { atts in
let maxXs = atts.map{ $0.frame.maxX }
let diff = (contentMaxX - maxXs.max()!)
atts.forEach { $0.frame.origin.x += diff }
}
return atts
}
마지막에 group으로 한줄한줄 묶었구 Cell의 maxX로 가장 마지막Cell의 최대 X값을 구한다음 그 차이만큼 x값을 더해주면 모든 오른쪽 정렬을 할 수 있습니다.
가운데 정렬은 diif / 2로 반만큼만 이동하면 됩니다.
오른쪽 정렬 | 가운데 정렬 |
---|---|
![]() |
![]() |
FlowLayout을 조금 조작해서 왼쪽 정렬 오른쪽 정렬 가운데 정렬 Layout을 구현해보았습니다. FlowLayout이 많은 것을 해주어서 생각보다 쉽게 구현할 수 있었습니다.
'Swift > UIKit' 카테고리의 다른 글
Swift UICollectionView 관철하기 - 개요 (0) | 2021.07.30 |
---|