OSX의 테이블뷰 사용법
iOS의 테이블과 달리, OSX의 테이블뷰는 행과 열이 모두 존재하는 애트리뷰트들을 가진 레코드들의 집합을 표시하는 테이블이다. 예를 들어 종업원들의 명부를 표시하는 테이블 뷰는 성, 이름, 지점명의 각각의 칼럼을 가진 테이블을 표시하게 된다.
테이블 뷰는 하나 혹은 그 이상의 칼럼을 가질 수 있으며 횡방향, 종방향으로 스크롤 되고, 선택 영역을 가질 수 있고, 칼럼의 드래깅을 지원한다. 각각의 행은 데이터 집합 내에서 대응하는 하나 이상의 셀을 포함한다.
이 문서에서 셀은 테이블 내 실제 칸 하나를 의미한다. 셀을 나타내는 NSCell 혹은 그 서브 클래스는 필요하면 클래스명으로 직접 언급한다.
테이블 뷰 이해하기
일반적인 의미에서 테이블 뷰는 행과 하나 이상의 열로 구성되어 데이터 집합의 내용을 표시한다. 각각의 행은 데이터 모델 내의 하나의 아이템을 표현하며, 각각의 열은 데이터 모델의 각 애트리뷰트를 나타낸다. 셀은 행과 열의 교차지점으로 특정한 아이템의 특정 프로퍼티를 가리키게 된다. 사용자는 행들을 선택할 수 있고, 앱은 그에 맞는 액션을 취할 수 있다.
테이블은 다양한 UI 요소로 구성되며, 단순한 데이터 목록에서부터 복잡한 연결데이터, 기능, 컨트롤들을 포함하여 풍부한 사용자 경험을 제공할 수 있다. 예를 들어 어떤 테이블은 두 개의 열로 나뉘면서 첫번째 열에는 이미지와 텍스트를 같이 표시하고, 다른 두 번째 열에는 텍스트를 표시할 수 있다.
간단한 테이블 뷰를 구축하는 것은 특별히 서브클래싱 작업을 필요로 하지 않는다. 셀은 기본적으로 NSTableViewCellView
클래스의 인스턴스이다. 간단한 테이블 뷰는 인터페이스빌더로부터 끌어와서 배치한 다음 적절한 값을 지정해주기만 하면 된다.
반면 바탕화면 관리와 같은 UI는 셀 하나에 텍스트, 그림, 컬러, 버튼들이 들어간다. 이러한 것들은 AppKit의 기본 뷰들을 가져와서 NSTableCellView
클래스 내에 적절히 배치하여 만든 것이다.
NSView 기반
대부분의 테이블은 NSView 기반이다. 이 말은 테이블의 각 셀이 NSView
의 서브클래스로 제공된다는 말이다. 종종 이들은 NSTableCellView
혹은 그 서브 클래스인데, 어떤 테이블은 NSCell
기반으로 만들어질 수도 있다. 대부분의 경우 NSCell
기반의 테이블은 레거시 코드를 지원하기 위해 사용된다.
뷰 기반의 테이블은 인터페이스 빌더를 통해 디자인 시에 강력함을 발휘한다. 셀 뷰 내로 다른 뷰를 끌어와서 적절한 셀뷰 클래스를 만들 수 있고, 이를 프로토타입 셀로 설정하면, 데이터 소스는 각 행의 각 셀에 대해 적절한 프로토타입으로부터 셀을 생성하여 할당할 수 있다. 테이블 뷰의 셀은 실행시에 재활용되므로, 복잡한 셀 구성을 갖더라도 비교적 메모리에 영향을 최소화하는 방향으로 동작할 수 있다.
테이블은 셀의 이동, 삽입, 제거 시에 애니메이션을 지원한다.
테이블은 여러 구성 요소의 복합체이다.
테이블은 다음 요소로 구성된다.
NSTableView
: 이 클래스는 테이블뷰의 외양을 설정하는 메소드들을 정의하고 있다. 셀의 높이는 얼마이며, 칼럼 헤더를 제공하는 등의 여부를 결정한다. 또한 현재 선택된 행과 셀에 액세스할 수 있게 한다. 그외 선택 영역을 제어하거나 테이블 뷰를 스크롤하고 행과 섹션을 삽입/제거하 수 있다.NSTableColumn
: 이 클래스는 칼럼의 위치, 폭등을 결정한다. 칼럼은 크기변경, 재정렬, 정렬이 가능하도록 설정할 수 있으며, 앱이 그 설정을 기억하도록 할 수도 있다. 각각의 테이블 칼럼은 고유의 식별자를 가지고 있어서 칼럼을 찾거나 셀을 액세스할 떄 키로 사용할 수 있다.NSTableHeaderView
: 칼럼헤더 셀을 관리한다. 칼럼헤더 셀은NSTableHeaderCell
클래스이며, 이것이 보일 때는 헤더 컨텐츠와 정렬 인디케이터, 하이라이트 등을 표시한다.NSScrollView
/NSClipView
: 이들은 필수요소는 아니지만, 테이블 뷰는 스크롤, 클리핑등에 의존하고 있다.NSTableRowView
,NSTableCellView
: 각 행과 셀을 관리한다.
정리하면 테이블 뷰는 스크롤뷰나 클립뷰 내에 들어가 있게 된다. 다시 테이블 뷰는 그 속에 수직 방향으로 쌓여있는 NSTableRowView
를 가지고 있는데, 이 row view는 다시 NSTableColumn
들이 가지고 있는 정보(순서, 폭)에 따라서 그 내에 표시될 셀(NSTableCellView)들의 순서와 크기를 알 수 있게 된다. 그리고 최종적으로 각 셀은 NSTableCellView
가 표현하게 된다.
위 그림에서 파란색 행은 선택되어 하이라이트로 표시되는 행을 가리키며, 빨간 행은 서브클래싱되어 별도의 배경을 그리고 있는 row를 의미한다. 또한 테이블 뷰에서는 특정 셀은 데이터가 없거나 하는 경우 빠지는 것을 허용한다.
테이블 뷰는 여러 개의 NSTableRowView
의 집합으로 구성되는데, row 뷰의 프레임은 테이블의 전체 폭과 각 row의 높이에 의해 결정되어, intercellSpacing
으로 결합된다. row 뷰는 선택 영역을 그리고, 드래그앤드랍에 반응하며 하이라이팅, 칼럼구분자, 그외 칠요한 부가적인 인디케이터들을 그리는 임무를 맡고 있다. 하나의 row 뷰는 각각의 칼럼 셀을 담당하는 NSTableCellView
의 집합을 가지고 있다.
델리게이트 메소드인 tableView:rowViewForRow:
를 이용해서 row 뷰를 커스터마이징할 수 있다. 인터페이스 빌더를 사용하여 테이블뷰 내에 NSTableRowView
를 집어넣고 프로토타입으로 만들 수 있다.
각각의 NSTableCellView
는 NSTableRowView
의 서브뷰로 들어가게 된다. 그리고 이들이 합쳐저서 하나의 전체 행을 표현한다. 표준 셀뷰는 하나의 텍스트 필드와 이미지 뷰를 포함하고 있으며, 보이스오버는 자동으로 해당 텍스트 뷰의 내용을 읽어내려갈 수 있다.
데이터 소스와 델리게이트
MVC 디자인패턴을 따르기 위해, NSTableView
의 인스턴스는 반드시 하나의 데이터 소스를 가져야 한다. 또한 데이터 표시르 제어하기 위해 테이블 뷰는 하나의 델리게이트도 필요로 한다.
데이터소스 클래스(NSTableViewDataSource 프로토콜을 따르는 클래스)는 테이블 뷰와 테이브 뷰의 실제 모델 데이터를 중계하는 역할을 한다. 또한 페이스트 보드로 복사하기 위한 데이터 타입을 제공하거나, row의 드래그를 지원하기 위한 기능도 구현한다. (하나의 행을 파인더로 끌고 가기 위해서는 데이터 모데 클래스가 NSPasteboardWriting
프로토콜을 따라야 한다.) 또한 테이블 뷰 내로 드어오는 드래그 액션을 확인하고 그에 대해 응답할 것인지를 결정하는 것도 데이터 소스의 역할이된다.
델리게이트(NSTableViewDelegate 프로토콜) 클래스는 테이블 뷰 자체를 서브클래싱하지 않고도 테이블뷰의 동작을 커스터마이징할 수 있게 한다. 델리게이트는 테이블 칼럼 관리와 타입-투-셀렉트 기능을 지원할 수 있게 하고, 특정 셀이나 row가 선ㅌ택 가능한지 여부를 결정해주며, 다른 여러 동작들에 관여하게 된다.
테이블 뷰 셀의 컨텐츠를 편집하기 위해서 타깃-액션 패턴이 사용된다. 코코아 바인딩을 적용하면 테이블 뷰를 파퓰레이팅하기가 손쉬워지고, 그런 경우 델리게이트와 데이터소스 크래스의 역할은 조금 달라질 수 있다. 이는 [[ㅋ코코아 바인딩과 테이블 뷰]]에서 확인하도록 하고… 외관상 밋밋한(?) NSCell 기반의 테이블을 쓰고 있다면 데이터소스와 델리게이트의 역할은 또 달라질 수 있다.
칼럼과 셀 식별자
테이블 뷰의 모든 칼럼은 고유한 구분자로 쓰이는 문자열을 가지고 있다. (이 값은 인터페이스 빌더에서 줄 수 있다.) 이는 어떤 칼럼을 특정하기 위한 편리한 방법을 제공해준다. 칼럼 이름은 데이터 모델의 프로퍼티 이름과 관련이 깊다. 만약 테이블 뷰에서 특정한 칼럼을 숨기거나 표시하려면 tableColumnWithIdentifier
를 통해서 특정한 칼럼을 구할 수 있다. 각 칼럼의 식별자는 사실 매우 중요한데, 왜냐면 OSX에서 테이블 뷰 내 칼럼은 사용자에 의해서 드래그를 통해서 순서 변경이 가능하기 때문에 인덱스는 고정적이지 않기 때문이다.
칼럼 식별자는 테이블 내 하나의 칼럼을 그 칼럼을 표시하는 뷰 인스턴스와 연관짓는다. NSTableView
의 델리게이트 메소드인 -tableView:viewForTableColumn:row:
는 특정한 테이블 칼럼에 대한 셀을 지정하기 위해서 칼럼 식별자를 이용한다. 만약 식별자가 수동으로 주어진다면, 칼럼이 사용자에 의해 옮겨지더라도 칼럼명과 칼럼이 항싱 싱크됨을 보장한다. 만약 식별자가 싱크에서 제거된다면 이 바인딩은 제대로 동작하지 않을 것이다. 만약 바인딩을 사용할 계획이라면 식별자 값을 자동으로 둘 것이 권장된다.
만약 복잡한 테이블 칼럼 관리 기능을 별도로 구현하지 않는다면 인터페이스 빌더가 제공하는 자동 설정을 이용하는 것이 좋다.
테이블 뷰의 셀 재사용
테이블 뷰는 사용된 셀을 큐에 넣고 이를 재사용한다. 큐의 셀들은 다음과 같은 경우 “사용중”으로 인식한다.
- 셀이 현재 보이고 있는 경우
- 현재 선택된 셀
- 편집이 진행중인 셀
이러한 경우에 해당되지 않는 셀은 즉시 재사용 큐에 반입된다. 테이블 뷰 셀들은 인터페이스 빌더 내에서 마치 자체적인 nib 파일을 별도로 가지고 있는 것처럼 취급된다. 보통 셀의 소유주는 테이블 뷰의 소유주와 같으며, 이는 주로 테이블 뷰 델리게이트이다.
- “Main Cell”이라는 식별자를 가진 셀이 필요하다.
- 델리게이트의
-makeViewWithIdentifier:"MainCell" owner:self
를 호출한다. MainCell
이 재사용 큐에 있으면 큐에서 하나를 빼 리턴한다.- 그렇지 않은 경우
MyView
라는 ID를 가진 디자인된 뷰가 있는지 확인, 있으면 테이블 뷰는instanstiateNibWithOwner:self topLevelObjects:...
를 호출하여 셀을 로드하고 해당 뷰를 리턴한다. - 그렇지 않으면 nil이 리턴된다.