티스토리 뷰

안녕하세요 Hani입니다.

이번에는 WWDC16에서 발표된 What's New in UICollectionView in iOS 10에 대하여 다뤄볼 거예요.

 

컬렉션뷰가 생각보다 너무 어렵더라구요 🥺

 

그래서 오래된 것부터 조져볼까합니다..

 

 

오늘의 목차 

전부 모르겠습니다..  ☺️

 

스뭇스한 스크롤링부터 시작할게요. ㅎ

 

스크롤링이 부드러우려면 어떤 조건을 만족시켜야 할까요?

 

옛날 폰은 60Hz까지 지원했습니다.

1초에 60번 프레임을 나타내 준다는 소리이므로 늦어도 1/60초에 한 번은 프레임을 뿌려줘야 프레임 손실이 일어나지 않아요.

 

얼마 전에 아이폰 13 프로가 등장했는데 이제 아이폰이나 아이패드도 120Hz를 지원하기 시작했습니다.

즉, 이제는 부드러운 화면을 위해서 8.33ms에 한 번은 프레임을 뿌려줘야 한다는 의미예요. 

 

 

이번 포스팅에서는 60Hz를 기준으로 알아보겠습니다.  ☺️

이렇게 오래 걸리는 프레임이 있어서는 안 된다는 말씀. 🥺

 

만약 프레임을 만들어줄 때 걸리는 시간이 오래 걸리는 녀석들이 군데군데 끼어있으면 너무 억울하겠죠?

나머지 프레임은 빨리 만들어줄 수 있는데..!

 

뾰족한 부분을 펴서 일관된 작업을 할 수 있도록 도와주는 방법이 이번에 소개될 아이들 이예요.

 

 

그 방법을 알아보기 전에!

iOS 9에서의 cell 생명주기에 대하여 알아봅시다. 🧐

컬렉션뷰가 화면에 띄워져 있고, 스크롤을 통해 새 cell을 가져오려고 합니다.

지금 막 들어오려는 cell이 기기에 거의 들어와 있는 것을 기억해주세요!!

 

 

ReusableQueue에서 cell을 꺼내고 prepareForReuse 메서드를 호출합니다.

 

prepareForReuse를 통해 cell을 default state로 초기화할 수 있어요. 👍

즉, 새로운 데이터를 받을 준비가 된 상태로 돌려놓는 작업입니다.

 

다음은 cellForItemAtIndexPath 메서드를 호출합니다.

 

여기서는 prepareForReuse에 의해 초기화된 cell에 데이터를 채워 넣는 작업을 합니다.

 

다음은 cell이 화면에 등장하기 직전에 willDisplayCell이 호출됩니다.

 

여기서는 cell이 화면에 등장하기 직전에 수행되어야 할 작업들을 할 수 있어요.

 

 

cell이 화면을 벗어나면 didEndDisplayingCell이 호출되며 reuse Queue에 들어갑니다.

이 순서가 iOS 9까지의 cell 생명주기입니다.

 

prepareForReuse

cellForItemAtIndexPath

willDisplayCell

didEndDisplayingCell

 

 

이제 iOS 10에서의 cell 생명주기를 알아볼게요. 🧐

iOS 9에서의 cell과 다르게 기기에 걸쳐있지 않은 상태부터 출발합니다.

즉, 훨씬 iOS 10부터는 기존과는 다르게 훨씬 앞 타이밍부터 생명주기가 시작되는 거예요.

 

 

iOS 9와 마찬가지로 prepareForReuse가 호출되어 cell이 초기화되고

 

마찬가지로 cellForItemAtIndexPath가 호출되어 cell에 데이터가 채워집니다.

 

단, willDisplayCell는 지금 이 상태에서는 호출되지 않아요.

 

이 타이밍!

cell이 막 표시되기 직전에 willDisplayCell이 호출됩니다.

 

 

이제 cell을 밀어 화면에 들여보내고 쭉 위로 스크롤해서 화면 밖으로 밀어봅시다.

예상대로 didEndDisplayingCell이 호출됩니다.

 

단, 이 타이밍에는 reuse Queue에 들어가지 않아요.

 

화면을 벗어난 순간까지도 cell을 붙잡고 있다가 

 

 

혹시 해당 cell이 다시 들어온다면

 

cell을 prepareForReuse로 초기화하는 것부터 시작하지 않고

바로 willDisplayCell을 통해 cell을 보여줄 준비를 합니다.

 

 

우리는 iOS 9까지의 cell 생명주기와 iOS 10부터의 cell 생명주기를 살펴봤어요.

 

prepareForReuse, cellForItemAtIndexPath 메서드는 조금 더 이른 시기에 호출되는 것과

didEndDisplayingCell이 되더라도 바로 reuse Queue에 들어가지는 않는다는 것을 알게 되었습니다.

 

 

부드러운 스크롤링에서 임팩트 있는 부분은 cell이 더 일찍 화면에 나타날 준비를 한다는 것입니다.

iOS 10 이전까지는 여러 개의 cell이 동시에 화면에 들어올 때

모든 cell이 화면에 나타나기 직전에 초기화가 한 번에 시작되었습니다.

 

 

아까 초록색, 빨간색 영역이 그려진 그림을 생각해보면

초기화가 한 번에 많이 일어나는 부분이 빨간색,

cell을 초기화하지 않는 부분이 초록색이네요. 🧐

 

iOS 10부터는 초기화가 일찍 시작되기 때문에 화면에 나타나기 직전 부랴부랴 준비할 필요가 없습니다.

 

 

하나씩 초기화를 진행시켜보면 

일단 한 cell만 초기화가 진행되는데

 

 

일단 개별 cell에 대하여 이 메서드까지만 호출해놓고

 

 

 다른 cell도 prepareForReuse로 초기화 

 

 

cellForItemAtIndexPath로 데이터 채워 넣기까지만 진행시킵니다.

 

 

그리고 화면에 들어가기 직전에

모든 cell에 대하여 willDisplayCell을 호출하여 화면에 한 번에 들어가도록 만들어줍니다.

 

이 ms를 다투는 작아 보이는 차이가  프레임 드랍을 막아주는 큰 역할을 하게 되는 거예요. 👍

 

pre-fetching

그러니까 미리 cell들을 가져와서 초기화해두는 작업은 default로 true로 되어있습니다.

그러니까 이 점에 대해서는 더 이상 신경 쓸 필요 없다!

 

 

이 pre-fetching을 잘 써먹기 위해선

cellForItemAtIndexPath에서 무거운 작업들을 전부 해놓고

willDisplayCell과 didEndDisplayingCell에서의 작업을 최소화해야 합니다.

 

단, cellForItemAtIndexPath까지 미리 해놓고 willDisplayCell가 호출되지 않을 수도 있으니

cellForItemAtIndexPath까지 작업한 cell이 화면에 나타나지 않을 수도 있겠네요. 

 

여기서 더 나아가 cellForItemAtIndexPath에서 했던

무거운 데이터들을 로딩하는 작업을 대신하는 새로운 방법이 제시됩니다.

 

기존의 방법은 비동기 네트워크 요청을 하는 동안 어쩔 수 없이 잠깐 빈 cell을 보여줘야 했기 때문이에요.

 

컬렉션뷰에서는 DataSource와 Delegate가 동반되는데

prefetchDataSource가 새롭게 등장합니다.

 

 

미리 불러오거나, 미리 불러오는 것을 취소하거나. 

취소는 옵션이기 때문에 사실상 하나만 구현하면 되니 간단!

 

 

우리는 생명주기의 개선을 통해 뾰족한 부분을 납작하게 만들어서 프레임 드랍이 일어나는 구간을 없앨 수 있었습니다.

하지만 이 방법은 특정 구간에서 프레임을 만들기 위해 main thread가 해야 할 일의 합을 덜어주진 않아요.

 

분산만 시켜주기 때문입니다.

 

prefetchDataSource를 통해 데이터를 미리 불러오는 작업은

모델을 읽어오는 과정을 백그라운드 큐로 이동시킬 수 있기 때문에 메인 큐를 확보할 수 있습니다 👍

 

단, 잘 써야 합니다. 😡

 

prefetch 잘 쓰는 법 🥰

 

prefetch가 호출되는 즉시 GCD나 NSOperationQueue로 작업을 보내서 후다닥 수행해야 합니다.

 

prefetch는 적응형 기술입니다.

즉, 피크가 있는 지점을 플랫하게 만들어주는 것에 특화된 기술이기 때문에

스크롤이 멈추지 않고 계속 진행되는 애플리케이션에 대해서는 영양가가 없습니다 🥺

 

마지막으로 아까 옵션이라고 했던 취소 메서드!

스크롤 방향이 바뀌면 데이터를 prefetch하는 작업을 취소하여

새 스크롤 방향에 대한 cell을 위한 작업을 하는 것이 좋습니다. ( 옵션까지 구현하면 훨씬 좋아진다!)

 

 

컬렉션뷰 뿐 아니라 테이블뷰에도 똑같은 것을 제공해준다. ☺️

 

 

 

 

prefetch를 봤으니 다음 기술을 구경하러 가봅시다. 🔥

 

Self-Sizing cell은 cell의 크기를 동적으로 결정하는 cell을 말하는데,

iOS 8에서 소개되었지만 이번에 좀 더 개선된 사항이 있다고 하네요.

 

 

먼저 컬렉션뷰에 대한 배경을 짚고 넘어가 봅시다.

 

컬렉션뷰가 화면에 띄워지기 위해선 CollectionView, Cell, Layout의 협동이 있어야 해요. 💪🏻

 

Layout이 Cell의 크기와 위치를 결정하고, CollectionView는 그 Cell을 그대로 보여주는 형식인데

 

여기서 Layout은 UICollectionViewFlowLayout 클래스를 사용합니다.

 

UICollectionViewFlowLayout은 기본적으로 이런 흐름을 가진 레이아웃이에요.

이제 여기다가 Cell의 크기를 동적으로 결정해야 하는 거죠.

 

 

암튼 배경지식은 여기까지 하구

 

Self-Sizing Cell을 위한 Layout을 설정할 때는 estimatedItemSize을 예상되는 CGSize로 설정해야 합니다.

 

이 estimatedItemSize는 아직 화면에 등장하지 않은 cell에 대하여 적용이 되고

실제 cell의 크기는 CollectionView에 컨텐츠가 표시될 때 결정됩니다.

 

 

굳이 estimatedItemSize를 넣어줘야 하는 이유는

화면에 표시될 크기와 근접한 CGSize를 넣어주면 포포몬스가 좋아지기 때문인데

 

그 크기를 어떻게 예측해야 하는지.. 허허

불-편

 

 

그래서 iOS 10에서 소개된 이 방법은 단순합니다.

FlowLayout의 estimatedItemSize를 UICollectionViewFlowLayoutAutomaticSize라는 상수로 설정하면 끝!

 

UICollectionViewFlowLayout.automaticSize
//지금은 이걸로 rename된 상태 ☺️

 

알아서 계산해 줄 테니 이제 더 이상 개발자가 머리 싸매지 않아도 됨. 👍

 

 

 

 

자 이제 마지막 관문

인터랙티브한 리오더링..

 

Item을 hit하고 끌어서 순서를 바꾸는 것을 말합니다.

 

(드래그 앤 드랍이라고 하지는 않는 것 같아요. 드래그 앤 드랍은 WWDC17에서 소개됩니다.)

 

iOS 9까지는 이런 것들을 다 구현했어야 했는데

 

 

iOS 10에서는 UICollectionViewController에서 해당 프로퍼티만 true로 바꿔주면 완성 ☺️

 

WWDC17에서 드래그 앤 드랍에 대한 새로운 방법이 나오는 듯 (소곤소곤)

 

 

 

+) 꼬다리로 붙어있는 파트 1

페이징 기능이 추가되었는데 UIScrollView의 bound의 절반 이상을 스크롤하면 다음 페이지로 넘어가고

그렇지 않으면 스크롤하기 전 상태로 되돌아갑니다.

 

 

 

+) 꼬다리로 붙어있는 파트 2

당겨서 새로고침하는 부분이 원래는 UICollectionViewController에 있었는데

 

아예 refreshControl이 UIScrollView의 프로퍼티로 들어가서

UITableView와 UICollectionView에서 사용 가능함~!

 

 

이 글을 통해 알게 된 것들!

 

하.. 다음 컬렉션뷰 보러 가봅니다..

 


References

https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro

https://developer.apple.com/videos/play/wwdc2016/219?time=258

https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CollectionViewBasics/CollectionViewBasics.html#//apple_ref/doc/uid/TP40012334-CH2-SW1

https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html#//apple_ref/doc/uid/TP40012334-CH3-SW1

https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout/1617709-estimateditemsize

 

 

댓글