먹고 기도하고 코딩하라

[UIKit] UICollectionView와 컨트롤러 알아보기 본문

앱/Swift

[UIKit] UICollectionView와 컨트롤러 알아보기

사과먹는사람 2022. 7. 15. 10:46
728x90
728x90

 

UICollectionView를 알아보기 전에 먼저 Collection View라는 건 뭘 뜻하는건지 알아보자. 문서를 참고했다.

Collection View는 설정할 수 있고, 커스텀할 수 있는 레이아웃을 이용해 중첩된 뷰를 보여주는 역할을 한다.

컬렉션 뷰는 순서가 있는 콘텐츠(사진 앱의 사진 그리드 등)의 셋(set)을 관리하고 시각적으로 표현한다.

컬렉션 뷰는 다른 많은 객체들과 함께 쓰일 수 있다.

  • Cells. 셀은 콘텐츠 하나하나에 대해 시각적 표현 제공
  • Layouts. 레이아웃은 컬렉션 뷰 콘텐츠에 시각적으로 잘 배치된 레이아웃 제공
  • data source object. 이 데이터 소스는 UICollectionViewDataSource 프로토콜을 따라야 하고, 컬렉션 뷰에 데이터를 제공하는 역할을 함
  • delegate object. 이 딜리게이트는 UICollectionViewDelegate 프로토콜을 따라야 하고, 컬렉션 뷰의 콘텐츠와 사용자 간 인터랙션(선택, 하이라이팅 등)을 관리한다.
  • Collection view controller. 보통 컬렉션 뷰를 관리하기 위해 UICollectionViewController를 사용한다. 다른 뷰 컨트롤러도 사용할 수 있지만, 어떤 컬렉션 관련 기능들은 컬렉션 뷰 컨트롤러를 사용해야만 동작한다.

 

이제 UICollectionView를 살펴보자.

UICollectionView는 순서가 있는 데이터 컬렉션 아이템을 관리하고, 레이아웃을 통해 데이터를 보여주는 객체이다.

 

선언

@MainActor class UICollectionView : UIScrollView

 

개요

UI에 컬렉션 뷰를 더할 경우, 독자가 만들 앱의 주된 임무는 컬렉션 뷰와 연관된 데이터를 관리하는 것이다. 컬렉션 뷰는 컬렉션 뷰의 dataSource 프로퍼티에 저장된 data source 객체로부터 데이터를 가져온다. data source는 UICollectionViewDiffableDataSource 객체를 사용할 수 있는데, 컬렉션 뷰의 데이터와 UI를 간단하고 효율적으로 관리하고 업데이트할 수 있도록 지원해준다. 다른 방법으로, UICollectionViewDataSource 프로토콜을 따르는 data source 커스텀 객체를 만들어 쓸 수 있다.

컬렉션 뷰의 데이터는 개별 아이템들로 구조화되어 있는데, 섹션으로 구분해서 보여줄 수 있다. 아이템은 보여줄 수 있는 가장 최소 단위이다. 예를 들면 사진 앱에서 아이템은 1개의 개별 이미지일 것이다. 컬렉션 뷰는 data source가 설정하고 제공하는 UICollectionViewCell 인스턴스인 셀(cell)을 이용해 아이템을 화면에 띄워 준다. 

셀과 더불어 컬렉션 뷰는 다른 타입의 뷰를 이용해 데이터를 보여줄 수도 있다. 예를 들면, supplementary views(보충 뷰)는 섹션 헤더, 푸터 등이 되어 각 뷰를 분리해주는 역할을 할 수도 있다. supplementary views를 쓰고 말고는 선택이며, 이것은 컬렉션 뷰의 layout 객체에 의해 정의된다. 

UICollectionView를 UI에 삽입하는 것에 덧붙여, data source 객체의 데이터들이 순서에 맞게 아이템을 잘 표현할 수 있도록 컬렉션 뷰의 메소드를 사용해야 한다. UICollectionViewDiffableDataSource 객체는 자동으로 이 과정을 처리해준다. 만약 커스텀 data source를 사용한다면, 컬렉션의 데이터를 더하거나 삭제하거나 다시 정렬할 때 UICollectionView의 insert, delete, rearrange 메소드를 사용해야 한다.

컬렉션 뷰 객체를 선택된 아이템을 관리하는 데 사용할 수도 있다.

 

레이아웃

컬렉션 뷰 레이아웃은 컬렉션 콘텐츠의 시각적 배치를 정의한다. 레이아웃은 콘텐츠를 간단하게 혹은 복잡하게 배치할 수 있도록 유연하게 디자인되어 있다. 컬렉션 뷰 레이아웃의 가장 간단한 형태는 아이템을 그리드 형식으로 보여주는 건데, 이 방법 말고 어떤 방법으로든 원하는대로 레이아웃을 정의할 수 있다. 예를 들면, 아이템을 원으로 배치하거나 아이템끼리 사이즈가 다르게 배치하도록 레이아웃을 정의할 수도 있다. 또한 기기 회전 등으로 아이템을 다르게 보여줘야겠다 싶으면 런타임에 동적으로 레이아웃을 변경할 수 있다.

레이아웃 객체(a layout object)는 컬렉션 뷰 콘텐츠의 시각적 배치를 정의한다. UICollectionViewLayout의 서브클래스인 레이아웃 객체는 컬렉션 뷰의 모든 셀과 supplementary views의 위치와 구조를 정의한다. 위치를 정의하긴 하지만, 레이아웃 객체는 사실 상응하는 뷰에 그 위치 정보를 적용하지는 않는다. 컬렉션 뷰가 레이아웃 정보를 상응하는 뷰에 적용하는데, 이유는 셀과 supplementary views의 생성은 컬렉션 뷰와 data source 객체 간 coordination을 포함하기 때문이다. 레이아웃 객체는 아이템 데이터가 아니라 시각적 정보를 제공한다는 점만 빼면 data source와 비슷하다.

보통 컬렉션 뷰를 생성할 때 레이아웃 객체를 정의할 텐데, 컬렉션 뷰 레이아웃은 동적으로 변경할 수도 있다. 레이아웃 객체는 collectionViewLayout 프로퍼티에 저장되어 있다. 이 프로퍼티를 직접 변경하면 애니메이션 없이 레이아웃을 즉시 변경할 수 있다. 만약 애니메이션을 곁들이고 싶다면, setCollectionViewLayout(_:animated:completion:) 메소드를 대신 사용하면 된다.

인터랙티브 트랜지션(gesture recognizer나 터치 이벤트 등으로 발생하는 것)을 생성하려면 startInteractiveTransition(to:completion:) 메소드를 사용해 레이아웃 객체를 변경하면 된다. 이 메소드는 트랜지션 진행을 추적하는 gesture recognizer나 이벤트 핸들러에 동작하는 레이아웃 객체를 즉시 설치한다. 이벤트 핸들러가 트랜지션을 끝냈으면 좋겠다고 생각한다면 finishInteractiveTransition()이나 cancelInteractiveTransition() 메소드를 호출해서 레이아웃 객체를 즉시 삭제하고, 타겟 레이아웃 객체를 설치한다.

 

셀과 보충 뷰

컬렉션 뷰의 data source 객체는 아이템 콘텐츠와 콘텐츠를 보여주는 데 이용되는 뷰들을 제공한다. 컬렉션 뷰가 콘텐츠를 처음 로드하면, data source에게 아이템을 놓을 뷰를 제공해줄 것을 요청한다. 컬렉션 뷰는 data source가 재사용할 수 있다고 찍어둔 뷰 객체의 큐나 리스트를 갖고 있다. 코드에서 명시적으로 새로운 뷰를 생성하기보다, 뷰를 큐에서 빼서(dequeue) 사용하자.

뷰를 빼서 사용하는 메소드는 2가지가 있다. 어떤 뷰가 필요한지에 따라 어떤 메소드를 쓸지도 달라진다:

  • dequeueReusableCell(withReuseIdentifier:for:) : 컬렉션 뷰의 아이템을 위한 셀 가져오기
  • dequeueReusableSupplementaryView(ofKind:withReuseIdentifier:for:) : 레이아웃 객체가 요청한 supplementary view 가져오기

메소드 호출 전에 컬렉션 뷰에게 만약 큐에서 꺼낼 게 없다면 상응하는 뷰를 만들 방법을 일러둬야 한다. 이를 위해 컬렉션 뷰에 클래스나 nib file(iOS와 Mac 앱 UI를 저장하는 데 쓰이는 리소스 파일)을 등록한다. 예를 들어 셀을 등록할 때 클래스를 등록하려면 register(_:forCellWithReuseIdentifier:) 메소드를 사용하고, nib file을 등록하려면 register(_:forCellWithReuseIdentifier:) 메소드를 사용한다. 등록 과정의 일부로서 뷰의 목적을 구별하는 reuse identifier를 특정해야 한다.

data source 메소드로 큐에서 적절한 뷰를 빼낸 다음에는 그 콘텐츠를 설정하고 컬렉션 뷰에 반환한다. 레이아웃 객체에서 레이아웃 정보를 얻은 다음에는 컬렉션 뷰가 그것을 뷰에 적용하고 보여준다.

 

데이터 프리페치

컬렉션 뷰는 반응성을 높이기 위해 2가지 프리페치 기술을 제공한다.

  • 셀 프리페치
    • 요구하기 전에 미리 셀을 준비해놓는다. 컬렉션 뷰가 동시에 많은 셀을 요구할 때(예를 들어, 그리드 레이아웃에 셀의 새로운 row가 필요할 때) 셀들은 화면이 그걸 필요하다고 하기 전에 미리 요청된다. 셀 렌더링은 여러 레이아웃에 거쳐 전달되고, 더 부드러운 스크롤 경험을 제공할 수 있다. 셀 프리페치는 기본으로 가능하다.
  • 데이터 프리페치
    • 셀에 대한 요청이 있기 전에 컬렉션 뷰의 데이터 요구 사항을 알려주는 메커니즘을 제공한다. 셀의 콘텐츠가 네트워크 요청 같은 긴 데이터 로딩 과정에 의존하는 경우에 특히 유용하다. 셀을 위한 데이터를 프리페치할 때 알림을 받으려면, UICollectionViewDataSourcePrefetching 프로토콜을 따르는 객체를 prefetchDataSource 프로퍼티에 할당한다.

 

아이템 재정렬하기

컬렉션 뷰는 사용자의 인터랙션으로 아이템을 옮길 수 있도록 허용한다. 보통 컬렉션 뷰의 아이템 순서는 data source에 정의되어 있기 마련이다. 만약 사용자가 직접 아이템을 재정렬할 수 있게 허용한다면, gesture recognizer를 설정해 컬렉션 뷰 아이템에 대한 사용자의 인터랙션을 추적해서 위치를 업데이트하면 된다.

사용자 인터랙션으로 아이템을 재정렬하기 위해 컬렉션 뷰의 beginInteractiveMovementForItem(at:) 메소드를 호출한다. gesture recognizer가 터치 이벤트를 추적하는 동안에는 터치 위치의 변화를 보고하는 updateInteractiveMovementTargetPosition(_:) 메소드를 호출한다. 만약 제스처 추적이 끝났다면, endInteractiveMovement()나 cancelInteractiveMovement() 메소드를 사용해 인터랙션을 종료하고 컬렉션 뷰를 업데이트한다.

사용자 인터랙션 중에는 컬렉션 뷰의 레이아웃은 동적으로 현재 아이템 위치를 반영할 수가 없다. 만약 아무것도 안 한다면 기본 레이아웃이 아이템을 재정렬해주겠지만, 필요하다면 레이아웃 애니메이션을 커스터마이징할 수 있다. 인터랙션이 끝나면 컬렉션 뷰는 아이템의 새로운 위치와 함께 data source 객체를 업데이트한다.

UICollectionViewController 클래스는 컬렉션 뷰가 관리하는 아이템을 재정렬할 수 있도록 기본 gesture recognizer를 제공한다. gesture recognizer를 갖다쓰려면 컬렉션 뷰의 installStandardGestureForInteractiveMovement 프로퍼티를 true로 설정한다.

 

인터페이스 빌더 속성

컬렉션 뷰의 인터페이스 빌더를 설정할 수 있는 속성들이다.

  • Items
    • 프로토타입 셀들의 개수. 이 프로퍼티는 스토리보드에서 설정할 수 있도록 프로토타입 셀의 특정 숫자를 관리한다. 컬렉션 뷰는 언제나 적어도 1개의 셀을 갖고 있고, 콘텐츠의 다른 타입을 표현하거나 같은 콘텐츠를 다른 방식으로 표현하는 여러 셀을 갖고 있다.
  • Layout
    • 레이아웃 객체. UICollectionViewFlowLayout 객체와 커스텀 레이아웃 객체 중 하나를 선택할 때 사용한다.
    • flow layout을 선택하면 flow layout이 헤더나 푸터 뷰를 갖고 있는지 여부와 상관없이 컬렉션 뷰 콘텐츠의 스크롤 방향을 설정할 수 있다. 헤더와 푸터 뷰를 가능하게 하면 스토리보드에 재사용 가능한 뷰를 추가하게 된다. 코드로도 구현 가능하다.
    • 커스텀 레이아웃을 선택하면 UICollectionViewLayout 서브클래스로 정의해야 한다.

flow layout이 선택되면, 컬렉션 뷰의 Size 인스펙터가 flow layout metrics를 설정하기 위한 추가적인 속성을 갖게 된다.  이 속성들은 셀의 크기, 헤더와 푸터의 크기, 셀 간 최소 간격, 셀들의 각 섹션의 마진 등을 설정할 수 있다.

 

국제화

컬렉션 뷰는 국제화에 대한 직접적인 내용이 없다. 대신 셀과 재사용 가능한 뷰를 국제화할 수는 있다.

 

접근성

컬렉션 뷰는 접근성에 대한 직접적인 내용이 없다. 만약 셀과 재사용 가능한 뷰가 UILabel, UITextField 같은 표준 UIKit 컨트롤들을 갖고 있다면, 그것들을 접근성을 고려해 만들 수 있다. 컬렉션 뷰가 화면의 레이아웃을 변경할 때, UIAccessibilityLayoutChangedNotification 알림을 준다.

 

 

 

UICollectionViewController

컬렉션 뷰를 관리하는 데 특화된 뷰 컨트롤러이다.

 

선언

@MainActor class UICollectionViewController : UIViewController

 

개요

뷰 컨트롤러는 다음 행위를 구현한다:

  • 컬렉션 뷰 컨트롤러가 할당된 nib 파일이 있거나 스토리보드에서 로드되었다면, 상응하는 nib 파일, 스토리보드로부터 뷰를 로드한다. 컬렉션 뷰를 코드로 만들었다면, collectionView 프로퍼티로 접근 가능한 unconfigured 컬렉션 뷰 객체를 자동으로 만든다.
  • 스토리보드나 nib 파일로 컬렉션 뷰를 로딩했다면, 컬렉션 뷰의 데이터 소스와 딜리게이트 객체(the data source and delegate objects)는 nib 파일로부터 온다. 만약 데이터 소스나 딜리게이트가 특정되어 있지 않다면, 컬렉션 뷰는 그 자신을 할당한다.
  • 컬렉션 뷰가 처음으로 나타나려고 할 때, 컬렉션 뷰 컨트롤러가 컬렉션 뷰 데이터를 다시 로딩한다. 또한 뷰가 보일 때마다 현재 selection을 clear한다. clearsSelectionOnViewWillAppear 프로퍼티를 false로 만들어서 이 행위를 변경할 수 있다.

UICollectionViewController의 커스텀 서브클래스를 만들어서 관리하고 싶은 각 컬렉션 뷰에 붙여줄 수도 있다. 컨트롤러를 초기화할 때는 init(collectionViewLayout:) 메소드를 사용하는데, 이 때 컬렉션 뷰가 갖고 있어야 할 레이아웃을 특정해야 한다. 갓 만들어진 컬렉션 뷰는 차원(방향)이나 콘텐츠가 없기 때문에 컬렉션 뷰의 데이터소스와 딜리게이트(보통 컬렉션 뷰 컨트롤러 그 자체)는 반드시 이 정보를 제공해야 한다.

loadView() 메소드나 다른 수퍼클래스를 오버라이딩할 수도 있지만, 그럴 경우 오버라이딩한 메소드에서 super를 꼭 호출해야 한다. 그렇지 않으면 컬렉션 뷰 컨트롤러는 컬렉션 뷰의 통합성을 유지하는 데 필요한 모든 작업들을 정상적으로 수행하기 어려울 수 있다.

 

 

728x90
반응형
Comments