UITraitCollection 공부

이전에 화면 회전과 관련된 Enumeration인 UIInterfaceOrientation의 공식 문서를 보던 도중 iOS 8부터 UITraitCollection을 쓰라고 되어 있어 얼핏 보았던 기억이 있다. 타고 들어간 공식 문서를 보아도 감이 잘 안 잡히고 Building Adaptive Apps with UIKit - WWDC 2014을 대충 봐도 잘 이해가 되지 않았다. 그래서 다시 한 번 정리를 해보려고 한다.
(제가 이해한 대로 써보았으니 이상한 것이 있으면 피드백 주시면 감사하겠습니다.)

UITraitCollection이란?

원래 아이폰은 단일 디바이스 크기를 지원했지만 아이패드, 애플 워치 등 새로운 기기의 등장과 아이폰 자체의 화면 크기 변화, 그리고 폰트나 로컬라이징 이슈 등 다양한 것들의 변화에 대응하여 UI를 효과적으로 다룰 도구가 필요했고 그래서 iOS 8 이후 소개된 개념이 UITraitCollection이다.

위의 그림은 Making Apps Adaptive, Part 1 - WWDC 2016에서 나온 것으로 UITraitCollectioniOS의 인터페이스 환경을 나타내는 trait들의 모음이라고 할 수 있다. UI를 배치하는 Layout, 모습을 표현하는 Appearance, 그리고 3D Touch의 가능 여부까지 담고 있다. UIScreen, UIWindow, UIViewController, UIPresentationController, 그리고 UIViewUITraitEnvironment 프로토콜을 따르는 데 이 프로토콜은 Required인 traitCollection 프로퍼티를 가지고 있기 때문에 우리가 보통 제어하는 뷰와 뷰 컨트롤러는 traitCollection을 기본 값으로 가지고 있다.

Size Class

그 중 중요한 Size Class를 보자.

Size Class는 UIUserInterfaceSizeClass라는 열거형으로 표현이 되며 여기에는 unspecified, compact, regular가 있다. compact는 쉽게 말해서 콘텐츠를 표현하는 데 있어 제약이 크다는 것이다. 반면 regular는 제약이 없어 자유롭게 표현할 수 있다고 생각하면 된다. UITraitCollection은 인스턴스 프로퍼티로 horizontalSizeClass, verticalSizeClass를 가지고 있다. 그래서 나올 수 있는 Size Class의 조합은 총 4가지이다.

기기별 Size Class

아래 그림은 기기별 풀 사이즈 스크린을 보일 때 기본적인 가로, 세로 사이즈 클래스를 나타낸다.

특이한 것은 아이폰 플러스(6+, 7+, 8+) 같은 경우 가로 모드 시 가로의 사이즈 클래스가 Regular로 나타난다는 것이다. 그래서 아이폰 플러스에서 애플이 기본으로 제공하는 메일 앱이나, 메시지 앱을 가로 모드로 보면 아이패드에서 볼 수 있는 UISplitViewController를 볼수 있다.

traitCollectionDidChange(_:)

이렇듯 사이즈 클래스를 나누어 놓아 개발자로 하여금 기기, 오리엔테이션 등 상황에 따라 UI를 변화시켜 컨텐츠를 효과적으로 보여줄 수 있는 반응형 UI를 만들 수 있게 하였다. 이것을 만드는 데 가장 중요한 메소드가 UITraitEnvironment 프로토콜의 traitCollectionDidChange(_:)이다. 이 메소드를 통해 개발자는 기기의 동작 환경이 바뀌었을 때적절한 UI로 재구성하는 기회를 얻는다. (UIScreen, UIWindow, UIViewController, UIPresentationController, 그리고 UIView가 UITraitEnvironment 프로토콜을 따른다.)

traitCollectionscreen에서부터 값을 가진다. 값을 가진다는 의미는 기기의 방향, 기기 종류, 외향 등 모든 trait이 값을 가진다는 뜻이다. 이렇게 구성된 traitCollectionscreen에서 window로, window에서 root viewController로, 그 다음 view, subview … 순으로 전파가 된다. 이렇게 아래로 전파가 될 때마다 traitCollectionDidChange(_:) 메소드가 불리게 된다.

그림과 같이 만약 UI를 traitCollection의 변경에 따라 바꾸고 싶다면 아래와 같이 viewControllerview 단에서 이 메소드를 오버라이드하여 UI를 재구성하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)

guard previousTraitCollection?.horizontalSizeClass != traitCollection.horizontalSizeClass else { return }
switch traitCollection.horizontalSizeClass {
case .compact:
setupConstraintForCompactEnvironment()
case .regular:
setupConstraintForRegularEnvironment()
case .unspecified:
break
}
}

기타 바뀌는 것들

이외에도 Xcode 시스템적으로 자동으로 traitCollection의 변화에 따라 적용되는 것들이 있다.

  • Interface Builder

인터페이스 빌더에서 +버튼을 누르면 traitCollection에 따른 변화를 줄 수가 있다.

  • Asset Catalog

기기가 지원하는 해상도에 따라 이미지를 다르게 보여준다.


traitCollection을 잘 적용한다면 기기별 대응을 좀 더 잘 할 수 있을 것 같다. 지금까지는 오토레이아웃밖에 몰랐지만 말이다. 그리고 이번 공부를 하면서 느낀 점은 WWDC를 좀 더 챙겨봐야겠다고 생각이 들었다.

Reference