[Swift] 특정 IndexPath에 있는 collectionViewCell을 StickyHeader로 만들기
Make UICollectioViewCell StickyHeader using UICollectionViewFlowLayout
요구 사항
collectionView
에서 특정 indexPath
에 있는 cell
을 sticky header 로 만들어야 했슴다. 바로 이렇게 말이죠.
IndexPath(row: 4, section: 0)
로 설정 -> 분홍색 cell이 sticky Header 처럼 작동CollectionReusableView
를 이용해서 비슷하게 header 느낌을 낼 수 있다지만 내가 원하는건 UICollectionViewCell
을 sticky 하게 만드는 것인걸요..??
그렇다면 UICollectionViewFlowLayout
을 잘 조져서 커스텀해야죠 머.. 시작해봅시당. (설명이고 뭐고 다 됐고 코드만 보고 싶다면? 여기로!)
시작하기 with Starter Project
일단 이곳(starter branch)에서 간단히 작업해 놓은 collectionView
를 확인할 수 있습니다. 랜덤한 색상과 높이 (범위 — 10
~ 109
) 가 설정된 20개의 cell
을 갖습니다.
Customize UICollectionViewFlowLayout
이제 UICollectionViewFlowLayout
를 건드릴 차례입니다. 이게 무엇인고.. 하면
item 을 원하는 형태로 정렬하게 도와주는 layout object
라고 할 수 있는데요! 기본적으로 item, header, footer 등의 size 를 결정하기 위해 UICollectionViewDelegateFlowLayout
를 conform 하는 delegate와 상호작용합니다. 이렇게 delegate를 사용하면 레이아웃을 동적으로 조정할 수 있다구여~!
😞 근데 난 이 정도로 안돼..!! 내가 원하는.. 좀 더 세밀한 UI가 있다구!!!
🙋🏻♀️ UICollectionViewFlowLayout
을 상속 받아서 custom 레이아웃의 꿈을 마음껏 펼쳐보세요!
여기서부터 시작하는거예여! 🎢
collectionView
는 해당 객체의 특정 메소드들을 호출하면서 레이아웃을 처리하는데요, 각각의 메소드는 item 의 위치를 계산하고 필요한 기본 정보를 collectionView
에 제공 할 수 있는 기회가 됩니다.
특히 아래 세개의 과정은 layout process 에서 항상 호출되는 것들인데요, 한번 살펴보자구요
prepare()
- 레이아웃을 업데이트 할 때마다 가장 먼저 호출 됨
- 필요한 경우 레이아웃 정보 제공을 위한 사전 계산 수행
- 즉, 향후 레이아웃 작업을 준비 할 기회 제공
collectionViewContentSize
- 전체 콘텐츠 영역의 크기 반환
layoutAttributesForElements(in:)
- 지정된 사각형에 있는 모든 셀 및 뷰의 레이아웃 특성을 반환
- 레이아웃 프로세스의 마지막 단계에서 호출
저는 여기서 마지막으로 소개한 layoutAttributesForElements(in:)
메소드만 override
해서 사용할거예요! 로직은 super
호출해서 나온 attributes 배열에 stickyAttribute
만 개인적으로 계산해서 추가하는 것이랍니당. 간단하쥬?
stickyAttribute
를 return 하는 getStickyAttributes(at:)
함수는 이렇게 생겼습니다.
로직을 살짝 소개해보자면
- sticky header가 되길 원하는 cell의
IndexPath
를 인자로 받음 layoutAttributesForItem(at:)
함수를 통해 해당indexPath
의 layout 속성을 deep copy (.copy()
) 로 가져옴- 가져온 layout attribute의
minY
값이collectionView.contentOffset.y
보다 작다면 (= 즉, sticky header cell의minY
값보다 더 스크롤 됐다면) attributey
값 조정을 통해collectionView
의 상위에 붙어있는 것처럼 보이게함 zIndex
를 1로 조정해서 해당 layout attribute의 z축을 상위로 올림.zIndex
디폴트 값은 0- 이렇게 값이 조정된 attribute 객체 반환
여기까지 잘 따라오셨나여?! 👏🏻👏🏻
그렇다면 족굼만 더 집중해봅시다!! 우리에겐 override
해야할 함수가 남아있다구요~
바로 shouldInvalidateLayout(forBoundsChange:)
인데요, 여기서는 true
를 반환해줍시당. 그래야 스크롤 할 때마다 layout 이 다시 계산되거든여
마지막으로 어떤 cell 이 sticky 한지 파악하기 위해 stickyIndexPath
를 작성합니다 (사실 이미 위에서 사용했던 property 죠..?ㅎ)
didSet
에 설정된 invalidateLayout()
함수는 현재 레이아웃을 무효화하고 레이아웃 업데이트를 트리거하는 역할을 한답니다. 나중에 새로운 레이아웃을 적용하고 싶다면 이 값을 바꿔주면 되겠쥬? 이렇게 stickyIndexPath
만 바깥에서 따로 설정하는 상황을 대비해서 private
설정은 하지 않았습니다.
좋아여! 이제 collectionView
를 정의한 곳으로 돌아가서 collectionViewLayout
을 여태껏 작성한 CustomCollectionViewFlowLayout
로 설정해줍시당
let columnLayout = CustomCollectionViewFlowLayout(stickyIndexPath: stickyIndexPath)self.collectionView.collectionViewLayout = columnLayout
후 끗!!!!!!!
CustomCollectionViewFlowLayout.swift
마무리
어떻게.. 이번 포스팅도 잘 보셨나여 ?_?
사실 UICollectionViewFlowLayout
에 관한 글을 몇번 검색해보신 분들은 알겠지만, 보통 layout attribute에 대한 cache array를 만들어서 사용하는 예제가 많아여. 하지만 제 상황은 캐시를 만들어서 쓰기에는 애매한 경우라고 판단해서,, 캐시를 쓰지 않는 선에서 최선을 다해봤습니다,, ^.ㅜ
하지만 부족한 점이 많으니,, 혹시 개선 사항이 보인다면 댓글 부탁드려요!!!!!
그럼 20000!
전체 프로젝트
전체 프로젝트 내용은 아래 링크에서 확인할 수 있습니다.