티스토리 뷰

반응형

이번 글에서는 저번 글에 이어 컬렉션 뷰에서 데이터 소스와 델리게이트에 대해 알아보려고 한다. 해당 정보는 공식 문서를 참고했다.

 

Apple Developer Document - Designing Your Data Source and Delegate

Designing Your Data Source and Delegate

모든 컬렉션 뷰는 반드시 데이터 소스를 가지고 있어야 한다. 데이터 소스 객체는 앱에서 보이는 콘텐츠이며 앱 데이터 모델일 수도 있고 컬렉션 뷰를 관리하는 뷰 컨트롤러일 수도 있다. 데이터 소스를 정의할 때 반드시 필요한 것은 컬렉션 뷰의 필요한 정보를 제공할 수 있어야 한다는 것이다. 이러한 정보에는 컬렉션 뷰의 항목 수, 해당 항목을 보여주기 위한 뷰와 같은 정보가 있다.

 

델리게이트 객체는 컨텐츠의 표현, 사용자와의 상호작용과 관련된 측면을 관리하는 객체이다. 데이터 소스와는 다르게 델리게이트는 필수는 아니며 델리게이트의 주요 업무는 셀을 강조하거나 선택하는 것이지만 개발자가 원하는 정보를 제공하도록 확장할 수도 있다. 예를 들어 델리게이트의 기능을 확장하여 셀 크기, 셀 간격과 같은 레이아웃도 설정할 수 있다.

The Data Source Manages Your Content

데이터 소스 객체는 컬렉션 뷰로 보여지는 콘텐츠들을 관리하는 객체이다. 이를 정의하기 위해서는 UICollectionViewDataSource 프로토콜을 준수해야한다. 데이터 소스의 역할은 컬렉션 뷰에게 마치 다음과 같은 질문에 대한 답을 제공하는 것이다.

 

  • 컬렉션 뷰에 몇 개의 섹션이 있는가?
  • 특정 섹션에는 몇 개의 항목이 있는가?
  • 특정 섹션이나 항목에 대해 해당 내용을 보여주기 위해 어떤 뷰를 사용할 것인가?

셀렉션 뷰의 기본 구성은 섹션과 항목이다. 일반적으로 셀렉션 뷰에는 하나 이상의 섹션이 있고 각 섹션에는 0개 이상의 항목이 포함된다. 이러한 항목은 화면에 보이는 콘텐츠를 나타내며 섹션은 이러한 항목들을 그룹으로 가지고 있다고 보면 된다.

 

컬렉션 뷰는 NSIndexPath 객체를 사용하여 포함된 데이터를 보여주게 된다. 특정 항목을 찾고 싶을 때 컬렉션 뷰는 레이아웃 객체가 제공한 Index Path 정보를 사용한다. 이 정보에는 섹션 번호와 항목 번호가 포함되며 보충 뷰나 장식 뷰의 경우 해당 객체가 제공한 위치 정보가 포함된다. 보충 뷰와 장식 뷰의 경우 index path의 의미는 앱에 따라 다르지만 첫 번째 인덱스는 특정 섹션을 의미한다. 이렇게 index path를 사용하면 현재 어떤 뷰를 사용할 것인지 찾아내서 사용할 수 있다.

 

컬렉션 뷰의 셀은 UITableView의 클래스의 index path와 마찬가지로 섹션과 항목을 찾을 수 있는 경로만 지원한다. 보충 뷰와 장식 뷰는 조금 더 많은 경로를 가질 수도 있다.

 

대이터 객체에서 index path로 정렬하는 방법과는 상관없이 화면에 보이는 방법은 레이아웃 객체에 의해 결정된다. 위의 그림은 섹션과 항목들을 서로 다른 레이아웃으로 나타낸 방법이다. 왼쪽은 그냥 단순히 수직으로 배열한 것이고 오른쪽은 비선형 배열로 배치한 것이다.

 

Designing Your Data Objects

데이터 소스를 효율적으로 사용하면 섹션과 항목을 사용하여 데이터를 구성하는데 도움이 된다. 데이터를 섹션과 항목으로 구성하면 나중에 데이터 소스 메서드를 쉽게 구현할 수 있다. 또한 이렇게 구현한 메서드를 호출할 때에도 빠르게 데이터를 검색할 수 있게 해 준다.

위의 그림은 섹션과 항목을 사용하여 데이터를 구성하는 한 가지 방법을 나타낸 것이다. 섹션을 가지고 있는 배열과, 항목을 가지고 있는 배열로 구성한 위 구조는 어떠한 항목을 찾고 싶다면 해당 섹션 배열을 검색 한 뒤 해당 섹션에서 항목을 검색하면 된다.

 

데이터 구조를 설계할 때는 항상 간단한 배열로 시작하여 점점 더 효율적인 구조로 나아갈 수 있다. 이렇게 설계할 때 병목현상이 발생하면 안 된다는 것에 유의하자. 컬렉션 뷰는 데이터 소스에 접근하여 개체의 수를 계산한 뒤 화면의 뷰에 보여주게 되는데 레이아웃 객체가 데이터 객체의 데이터에만 의존한다면 데이터 소스에 어떠한 데이터가 있는지에 따라 성능에 심각한 영향을 줄 수 있다.

Telling the Collection View About Your Content

컬렉션 뷰에서 데이터 소스에게 섹션이 몇 갠지 섹션들에는 몇 개의 항목이 존재하는지에 대해 물어볼 수 있다. 이러한 질문은 다음 상황 중 한 가지에 해당하는 상황일 때 발생한다.

 

  • 컬렉션 뷰가 처음으로 보일 때
  • 컬렉션 뷰에 다른 데이터 소스 객체를 할당할 때
  • reloadData 메서드를 호출할 때
  • 컬렉션 뷰 델리게이트가 performBatchUpdates:completion: 메서드를 실행하거나 삽입, 삭제, 이동 등과 같은 메서드를 실행할 때

이런 질문을 받게 된다면 numberOfSectionsInCollectionView: 메서드를 사용하여 섹션 수를 collectionView:NumberofItemsInSections: 메서드를 사용하여 각 섹션의 항목 수를 제공하면 된다. 참고로 컬렉션 뷰를 정의할 때 collectionView:numberOfItemsInSection: 메서드는 반드시 구현해야 하고 numberOfSectionsInCollectionView 메서드는 만약 섹션이 한 개뿐이라면 선택적으로 구현하면 된다. 물론 이렇게 받은 정보가 오류는 없는지 검사도 수행해줘야 한다.

Configuring Cells and Supplementary Views

데이터 소스의 또 다른 중요한 작업은 컬렉션 뷰가 콘텐츠를 보여주는 데 사용할 뷰를 제공하는 것이다. 컬렉션 뷰는 앱의 콘텐츠를 추적하는 것이 아닌 그저 뷰에 레이아웃 정보를 적용하여 보여주기만 한다. 따라서 이러한 것들은 개발자가 구현해줘야 한다.

 

컬렉션 뷰는 데이터 소스에서 관리하는 섹션과 항목 수를 알아낸 후 레이아웃 객체에게 콘텐츠에 대한 레이아웃 속성 정보를 받아온다. 컬렉션 뷰는 레이아웃 정보를 바탕으로 데이터 소스에 셀과 보충 뷰를 요청한다. 여기서 데이터 소스가 셀과 보충 뷰를 제공하기 위해선 다음을 수행해야 한다.

 

  1. 스토리 보드 파일에 템플릿 셀과 뷰를 추가한다. (혹은 클래스와 nib 파일을 사용)
  2. 데이터 소스에 요청한 적절한 셀, 뷰를 큐에서 꺼내 구성한다.

셀과 보충 뷰가 효율적으로 사용되도록 컬렉션 뷰는 해당 객체들을 생성할 책임이 있다. 각각의 컬렉션 뷰는 현재 사용되지 않는 셀이나 보충 뷰를 내부적인 큐에 유지하고 다시 사용될 때 다시 제공한다. 이러한 부분이 재사용과 관련된 부분이며 만약 큐에 해당하는 정보가 없다면 컬렉션 뷰는 클래스와 nib 파일을 사용하여 새로 만들어 사용자에게 반환한다.

 

재사용 식별자를 사용하면 더욱 많은 타입의 셀과 보충 뷰를 재사용할 수 있다. 재사용 식별자는 등록된 셀과 뷰의 유형을 구별하는 데 사용하는 문자열이다. 이러한 내용은 데이터 소스 객체에만 관련되며 뷰나 셀을 요청할 경우 index path로 원하는 뷰나 셀을 결정한 후 재사용 ID를 dequeue 메서드에 전달할 수 있다.

Registering Your Cells and Supplementary Views

컬렉션 뷰의 셀과 뷰는 스토리 보드에서 구성하거나 코드로 직접 구성할 수 있다.

 

만약 스토리 보드에서 구성할 경우 셀과 보충 뷰를 그저 컬렉션 뷰에 드래그 인 하면 된다. 셀과 뷰를 드래그 인으로 추가했다면 셀의 클래스 및 재사용 ID를 적절하게 설정해야 한다.

 

코드로 셀을 구성할 땐 registerClass:forCellWithReuseIdentifier:, registerNib:forCellWithReuseIdentifier: 메서드를 사용해서 셀의 재사용 식별자를 설정한다. 마찬가지로 코드로 보충 뷰를 구성할 땐 registerClass:forSupplementaryViewOfKind:withReuseIdentifier:, registerNib:forSupplementaryViewOfKind:withReuseIdentifier: 메서드를 사용하여 재사용 식별자를 설정하면 된다. 이러한 부분은 생성자로 처리할 수도 있다.

 

보충 뷰에서는 재사용 식별자 말고도 kind String이라는 것도 추가해줘야 한다. 이는 보조 뷰의 종류를 나타내는 것으로 Header, Footer와 같은 것이 해당한다. UICollectionViewFlowLayout 클래스에서 Header, Footer를 구분하기 위해서는 UICollectionElementKindSectionHeader, UICollectionElementKindSectionFooter를 정의하면 된다. 레이아웃을 처리할 때 이러한 정보를 요청하게 되고 컬렉션 뷰는 데이터 소스로 요청을 전달한다. 데이터 소스는 이러한 정보로 큐에서 꺼낼 뷰 객체를 결정하게 된다.

 

직접 레이아웃을 구현하는 경우 레이아웃이 지원하는 보충 뷰의 종류를 정의해야 한다. 레이아웃은 kind String 정보를 가지고 있는 여러 개의 보충 뷰를 지원할 수 있다. 이는 나중에 알아볼 Creating Custom Layouts에서 자세히 알아보자.

Registration은 셀이나 뷰를 큐에서 빼기 전에 발생하는 일회성 이벤트이다. 셀이나 뷰를 등록한 후에는 재등록할 필요 없이 언제든 큐에서 꺼낼 수 있다. 이렇게 꺼냈을 때 등록 정보를 변경하는 것은 좋지 않으며 한 번 등록했다면 그 상태 그대로 계속 사용하도록 하자.

Dequeueing and Configuring Cells and Views

데이터 소스 객체는 컬렉션 뷰가 셀과 보충 뷰를 요청하면 제공할 의무가 있다. UICollectionViewDataSource 프로토콜은 이러한 요청을 처리하기 위해 collectionView:cellForItemAtIndexPath:, collectionView:viewForSupplementaryElementOfKind:atIndexPath: 메서드를 가지고 있다. 여기서 컬렉션 뷰에서 셀은 반드시 있어야 하기 때문에 collectionView:cellForItemAtIndexPath: 메서드는 반드시 구현해야 한다. 두 메서드의 구현은 아주 간단한 방식으로 할 수 있다.

 

  1. dequeueReusableCellWithReuseIdentifier:forIndexPath, dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: 메서드를 사용하여 적절한 타입의 셀과 뷰를 큐에서 뺀다.
  2. Index path로 데이터를 찾아 뷰를 구성한다.
  3. 뷰를 반환한다.

Dequeue 되어 반환된 셀은 새로운 데이터로 구성할 수 있게 깨끗한 상태여야 한다. 셀이나 뷰가 만들어지려면 dequeue 과정이나 일반적인 과정을 통해 생성되어야 한다. 스토리보드나 nib 파일 , initWithFrame: 메서드의 새로운 인스턴스로 셀이나 뷰를 생성할 수 있다. 반대로 생성되진 않았지만 재사용 큐에 검색된 경우 이전에 사용한 데이터가 포함되어있을 수 있다. 그러한 경우 dequeue 메서드는 prepareForReuse 메서드를 호출하여 재사용할 수 있다. 셀이나 뷰 클래스를 구현할 때 이 메서드를 대체하여 프로퍼티들을 기본 값으로 설정하고 추가적인 cleanup을 할 수 있다.

 

데이터 소스가 뷰를 dequeue 되면 뷰는 새로운 데이터로 구성된다. 데이터 소스 메서드에 전달된 index path를 사용하여 적절한 데이터를 찾은 뒤 해당 데이터를 뷰에 적용할 수 있다. 이렇게 뷰를 구성한 뒤 메서드에서 반환하면 셀과 뷰를 구성하는 과정이 끝나게 된다. 주의할 점은 데이터 소스에서 뷰를 반환할 때 항상 유효한 뷰를 반환해야 한다. 만약 nil이 반환되면 assertion이 발생하고 앱이 종료된다.

Inserting, Deleting, and Moving Sections and Items

하나의 섹션이나 항목을 추가, 삭제, 이동하는 방법은 다음과 같다

 

  1. 데이터 소스 객체에 데이터를 업데이트한다.
  2. 삽입, 삭제와 관련된 컬렉션 뷰의 메서드를 호출한다.

컬렉션 뷰에 변경 사항을 알리기 전에 데이터 소스를 먼저 업데이트해야 한다. 그 이유는 컬렉션 뷰의 메서드는 데이터 소스에 항상 올바른 데이터가 존재한다고 생각하기 때문에 그렇지 않은 경우엔 앱을 종료해버린다.

 

만약 코드로 삽입, 삭제, 이동을 구현하고 싶다면 컬렉션 뷰의 메서드가 애니메이션을 자동으로 만들어 변경 사항을 반영한다. 여러 변경을 함께 애니메이션으로 나타내고 싶다면 하고 싶은 변경들을 모두 수행하고 performBatchUpdates:completion: 메서드에 전달하면 된다. 이렇게 하면 모든 변경 사항이 동시에 처리되게 된다.

 

[self.collectionView performBatchUpdates:^{
   NSArray* itemPaths = [self.collectionView indexPathsForSelectedItems];

   // Delete the items from the data source.
   [self deleteItemsFromDataSourceAtIndexPaths:itemPaths];

   // Now delete the items from the collection view.
   [self.collectionView deleteItemsAtIndexPaths:itemPaths];
} completion:nil];

위의 코드는 현재 선택된 항목들의 삭제를 한 번에 처리하기 위해 배치 업데이트를 사용하는 방법의 예이다. performBatchUpdates:completion: 메서드에 전달된 블록으로 데이터 소스를 업데이트한다. 그런 뒤 컬렉션 뷰에서 삭제를 하게 되면 업데이트 블록과 완료 블록이 모두 동기적으로 실행되게 된다.

Managing the Visual State for Selections and Highlights

컬렉션 뷰는 기본적으로 단일 항목의 선택을 지원한다. 여러 개의 항목을 선택하는 것이나 선택하는 행위를 아예 무효시키는 것도 가능하다. 컬렉션 뷰는 경계 내부의 탭을 감지하여 해당 셀을 Selecting, Highlighting 하게 된다. 대부분의 경우 컬렉션 뷰는 셀의 속성만 수정하여 셀의 Selecting, Highlighting을 표시하는데 한 가지 예외적인 상황을 제외하곤 셀의 시각적인 부분은 변하지 않는다. 이 예외적인 상황은 셀의 selectedBackgroundView 프로퍼티에 valid view가 포함된 경우이다.

컬렉션 뷰의 델리게이트는 Selecting, Highlighting을 용이하게 하는 다음과 같은 메서드를 제공한다.

 

  • collectionView:shouldSelectItemAtIndexPath:
  • collectionView:shouldDeselectItemAtIndexPath:
  • collectionView:didSelectItemAtIndexPath:
  • collectionView:didDeselectItemAtIndexPath:
  • collectionView:shouldHighlightItemAtIndexPath:
  • collectionView:didHighlightItemAtIndexPath:
  • collectionView:didUnhighlightItemAtIndexPath:

이러한 메서드들은 컬렉션 뷰에서 Selecting, Highlighting의 동작을 원하는 대로 조작할 수 있게 해 준다. 예를 들어 selectedBackgroundView 속성을 nil로 설정하고도 델리게이트를 사용하여 셀에 시각적 변경 사항을 적용할 수도 있다.

셀의 Selecting, Highlighting 사이에는 미묘하지만 중요한 차이가 있다. Highlighting 상태는 사용자의 손가락이 화면에 닿아 있는 동안만 상태가 변경되고 Selecting 상태는 터치가 종료된 후에 변경된다.

 

위의 그림과 같이 손가락이 화면에 붙어있을 때와 떼어 졌을 때의 상태변화를 비교하면 이해가 쉽다.

 

Showing the Edit Menu for a Cell

사용자가 셀에서 길~게 누르는 제스처를 수행하는 경우 셀렉션 뷰는 해당 셀에 대한 편집 메뉴를 표시하려고 한다. 편집 메뉴를 사용하면 컬렉션 뷰의 셀로 잘라내기, 붙여 넣기, 복사하기를 할 수 있다. 이렇게 편집 메뉴를 사용하기 위해선 몇 가지 조건이 충족되어야 한다.

 

  • 델리게이트는 작업 처리와 관련된 다음 세 가지 메서드를 구현해야 한다.
    • collectionView:shouldShowMenuForItemAtIndexPath: (Yes를 반환해야 함)
    • collectionView:canPerformAction:forItemAtTndexPath:withSender: (원하는 액션 중 하나 이상을 Yes로 반환해야 함)
    • collectionView:performAction:forItemAtIndexPath:withSender:

컬렉션 뷰는 위의 조건들이 충족되면 cut, copy, paste를 구현할 수 있다. 구현할 땐 collectionView:performAction:forItemAtIndexPath:withSender: 메서드로 원하는 걸 수행하면 된다.

Transitioning Between Layouts

레이아웃 전환을 하는 가장 쉬운 방법은 setCollectionViewLayout(_ :animated) 메서드를 사용하는 것이다. 하지만 어떠한 상호작용에 의해 전환을 하고 싶다면 UICollectionViewTransitionLayout 클래스를 사용하자.

 

UICollectionViewTranstionLayout 클래스는 새 레이아웃으로 전환할 때 컬렉션 뷰의 레이아웃 객체로 설치되는 특수한 유형의 레이아웃이다. 이를 사용하면 객체가 non linear path, different timing algorithm, move according to imcoming touch events 들을 사용할 수 있다. 따라서 이 클래스를 서브 클래싱 하여 원하는 효과를 만들 수 있다. 커스텀 레이아웃을 만들 때와 동일한 방법으로 구현하고 사용자의 입력, 제스처에 반응하도록 만들어주면 된다.

 

UICollectionViewLayout 클래스는 레이아웃이 전환된 것을 추적하기 위한 몇 가지 메서드를 제공한다. UICollectionViewTransitionLayout 객체의 transitionProgress 프로퍼티는 전환이 완료되었는지를 추적한다. 전환이 발생하면 이 프로퍼티를 업데이트하여 완료가 됐는지를 나타낼 수 있다. updateValue(_ :forAnimatedKey:), valueForAnimatedKey() 메서드로는 레이아웃과 관련된 값을 추적할 수 있다. 이 메서드들은 특수한 부동 소수점 값들을 추적하여 레이아웃이 전환될 때 레이아웃의 중요 정보를 주고받을 수 있다. 예를 들어 핀치 제스처를 사용하여 레이아웃이 전환된다면 이 메서드들을 사용하여 뷰의 offset이 전환 후엔 어딘지 알릴 수 있다.

 

이러한 UICollectionViewTransitionLayout 객체를 포함시키는 단계는 다음과 같다.

 

  1. initWithCurrentLayout(_ :nextLayout) 메서드나 사용자 정의 클래스로 인스턴스를 만든다.
  2. transitionProgress 프로퍼티는 주기적으로 수정하여 전환이 얼마나 진행되었는지를 알려준다. 전환이 완료되면 기존의 레이아웃은 invalidateLayout 메서드를 사용하여 레이아웃을 무효화시켜줘야 한다.
  3. 컬렉션 뷰 델리게이트에 collectionView(_ :transitionLayoutForOldLayout:newLayout:) 메서드를 구현하고 전환된 레이아웃 객체를 반환한다.
  4. 필요에 따라 updateValue(_ :forAnimateKey) 메서드를 사용하여 레이아웃 객체의 바뀐 값들을 수정해준다.
반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
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
글 보관함