View Controller Programming Guide for iOS - View Controller Definition
Defining Your Subclass
대부분의 커스텀 뷰 컨트롤러는 Content view controller이다. 각자의 뷰들을 소유하고 뷰에 나타나는 데이터에 대한 책임이 있다. 이와 대조적으로 Container view controller는 모든 뷰를 소유하지는 않는다.
Defining Your UI
프로그래밍으로 UI를 만들 수도 있지만 스토리보드로 만드는 것은 컨텐츠를 시각화하고 뷰 계층도를 커스터마이즈하기 좋은 방법이다. UI를 보이게 만드는 것은 변경을 빠르게하고 앱을 실행시키지 않고도 결과를 볼 수 있게 한다.
다음 그림의 뷰 컨트롤러 간 선은 뷰 컨트롤러의 relationship과 segue를 나타낸다.
- Relationship: 컨테이너 뷰 컨트롤러와 자식 뷰 컨트롤러들을 이어준다.
- Segue: 인터페이스에서 뷰 컨트롤러 간 이동을 하게 해준다.
다음과 같이 스토리보드 에디터를 사용하자.
- 뷰 컨트롤러의 뷰를 더하고, 정렬하고 설정하라.
- outlet과 action을 연결하라.
- 뷰 컨트롤러 간 relationship과 segue를 생성하라.
- 다른 사이즈 클래스를 고려하여 레이아웃과 뷰를 커스터마이즈하라.
- 뷰의 유저 인터랙션을 다루기 위해 gesture recognizer를 달아라.
Handling User Interactions
뷰 컨트롤러는 리스폰더 객체지만 직접 터치 이벤트를 처리하지는 않는다. 대신, 다음과 같은 방법으로 이벤트를 처리한다.
- 고차원의 이벤트를 처리하는 액션 메서드를 정의한다. 액션 메서드는 다음에 응답한다.
- 특정 액션을 처리하기 위해 컨트롤 객체나 뷰가 호출한다.
- Gesture recognizer: 제스처의 상태를 보고하기 위해 액션 메서드를 호출한다.
- 시스템이나 다른 객체들이 보내는 노티피케이션을 감지한다.
- 다른 객체의 data source나 delegate 역할을 한다.
Displaying Your Views at Runtime
UIKit은 자동으로 필요할 때 스토리보드의 뷰들을 로드한다. 다음과 같은 과정을 거친다.
- 스토리보드 파일 안의 정보를 통해 뷰를 초기화한다.
- outlet과 action을 연결한다.
- 뷰 컨트롤러의 프로퍼티에 루트 뷰를 할당한다.
- 뷰 컨트롤러의
awakeFromNib
메서드를 호출한다.
이 메서드가 불릴 때는 뷰 컨트롤러의 trait collection이 비어 있고 뷰의 위치가 확정되지 않았을 수도 있다. - 뷰 컨트롤러의
viewDidLoad
메서드를 호출한다.
이 메서드를 사용하여 뷰를 더하거나 제거하고, 레이아웃 제약을 수정하고 뷰의 데이터를 로드하라.
뷰를 보여주기 전에 UIKit은 뷰들을 준비할 수 있는 추가적인 기회를 준다. 다음과 같은 과정을 거친다.
- 뷰 컨트롤러의
viewWillAppear
를 호출하여 뷰가 화면에 곧 보일 것임을 알려준다. - 뷰의 레이아웃을 업데이트한다.
- 뷰를 화면에 보여준다.
viewDidAppear
를 호출한다.
뷰의 사이즈나 위치를 더하거나, 제거하거나, 변경할 때는 해당하는 제약도 더하거나 제거해야 함을 잊지 말아야 한다. 레이아웃과 관련된 변경을 하는 것은 UIKit이 레이아웃이 ‘dirty’하다고 표시하도록 한다. 다음 업데이트 주기 때, 레이아웃 엔진이 뷰 계층도에 이 변화를 반영한다.
Managing View Layout
뷰의 사이즈나 위치 변경이 있으면 UIKit은 뷰 계층도의 레이아웃 정보를 업데이트한다. 레이아웃을 업데이트하는 것은 오토레이아웃 엔진이나 Presentation 컨트롤러의 도움을 받는다.
레이아웃 과정에서 UIKit은 추가적인 레이아웃 작업을 할 수 있도록 몇몇 포인트를 제공해준다. 이 지점에서 마지막 변덕을 적용할 수 있다. 다음과 같은 과정을 거친다.
- 필요한 경우, 뷰 컨트롤러와 뷰의 trait colletion을 업데이트한다.
- 뷰 컨트롤러의
viewWillLayoutSubviews
메서드를 호출한다. - 현재
UIPresentationController
객체의containerViewWillLayoutSubviews
메서드를 호출한다. - 뷰 컨트롤러 루트 뷰의
layoutSubviews
를 호출한다.
이 메서드를 통해 현재 제약들을 통해 새로운 레이아웃 정보를 계산한다. 그리고 이 메서드는 뷰 계층도를 따라 subview들에서도 연속적으로 호출된다. - 뷰의 레이아웃 정보를 적용한다.
- 뷰 컨트롤러의
viewDidLayoutSubviews
를 호출한다. - 현재
UIPresentationController
객체의containerViewDidLayoutSubviews
메서드를 호출한다.
뷰 컨트롤러의 viewWillLayoutSubviews
나 viewDidLayoutSubviews
메서드에서 추가적인 업데이트를 할 수 있다.
다음은 효과적인 레이아웃을 위한 몇 가지 팁이다.
- 오토 레이아웃을 사용하라. 다양한 화면 크기에 대응하기 좋은 방법이다.
- 탑, 바텀 레이아웃 가이드를 활용하라. -> Safe Area로 변경!(iOS 11)
- 뷰를 더하거나 제거할 때 제약을 업데이트하는 것을 기억하라.
- 뷰 컨트롤러의 뷰를 애니메이팅 할 때 제약을 잠시 제거하라.
Managing Memory Efficiently
Places to allocate and deallocate memory
- 뷰 컨트롤러의 중요한 핵심 자료구조 Alloc: 초기화 메서드
- 뷰에 보여줘야 하는 데이터의 Alloc/Load:
viewDidLoad
- low-memory 알림에 대응:
didReceiveMemoryWarning
- 핵심 자료구조의 Release:
dealloc
Implementing a Container View Controller
컨테이너 뷰 컨트롤러는 여러 뷰 컨트롤러들의 컨텐츠를 묶어서 하나의 UI로 보여주는 방법이다.
Designing a Custom Container View Controller
컨테이너 뷰 컨트롤러도 컨텐트 뷰 컨트롤러와 같이 루트 뷰와 컨텐트를 관리하는 방법은 같다. 그러나 차이점은 컨테이너 뷰 컨트롤러는 그 컨텐츠를 다른 뷰 컨트롤러에게서 받아와 보여준다는 것이다.
컨테이너 뷰 컨트롤러를 만들 때는 항상 자식 뷰 컨트롤러와의 관계를 염두에 두어야 한다.
Example: Navigation Controller
UINavigationController
객체는 계층적인 데이터 세트 안에서의 이동을 지원한다. 네비게이션 인터페이스는 한 번에 하나의 자식 뷰 컨트롤러를 보여주게 된다. 네비게이션 바는 인터페이스의 상단에 위치해 있고 현재 데이터 계층에서 어느 위치에 있는지 보여준다.
네비게이션 컨트롤러는 compact나 regular 환경 모두에서, 하나의 자식 뷰 컨트롤러만을 보여준다.
Example: Split View Controller
UISplitViewController
객체는 ‘master-detail‘ 배치를 통해 두 뷰 컨트롤러를 보여준다. 이러한 배치에서 master에 해당하는 뷰 컨트롤러의 컨텐츠가 detail에 해당하는 뷰 컨트롤러의 컨텐츠를 결정한다.
두 뷰 컨트롤러의 보여짐은 현재 환경에 따라 달라진다. regular horizontal 환경의 경우, 두 뷰 컨트롤러 모두 보여줄 수 있지만, compact한 환경에서는 하나의 뷰 컨트롤러만을 보여주게 된다.
Implementing a Custom Container View Controller
컨테이너 뷰 컨트롤러를 적용하기 위해 뷰 컨트롤러와 자식 뷰 컨트롤러 같의 관계를 반드시 정립해야 한다. 인터페이스 빌더를 통해 할 수도 있지만 프로그래밍으로 하는 방법을 보겠다.
Adding a Child View Controller to Your Content
자식 뷰 컨트롤러를 통합하고 부모-자식의 관계를 만들기 위해 다음과 같은 절차를 따른다.
- 컨테이너 뷰 컨트롤러의
addChildViewController:
를 호출한다.
이 메서드를 통해 컨테이너 뷰 컨트롤러가 자식 뷰 컨트롤러의 뷰를 관리한다는 것을 UIKit에게 알려준다. - 자식의 루트 뷰를 컨테이너의 뷰 계층도에 더한다.
자식 뷰의 프레임은 정해져 있어야 한다. - 자식의 루트 뷰의 사이즈와 위치를 결정하는 제약을 더한다.
- 자식 뷰 컨트롤러의
didMoveToParentViewController:
를 호출한다.
참고로, 부모의 addChildViewContrller:
를 호출하는 것은 자식의 willMoveToParentViewController
를 호출시킨다. 오토 레이아웃을 사용할 때는 자식을 컨테이너의 뷰 계층도에 더한 뒤에 제약을 설정해야 한다.
Removing a Child View Controller
자식 뷰 컨트롤러를 제거할 때는 다음과 같은 절차를 거친다
- 자식의
willMoveToParentViewController:
를nil
값과 함께 호출한다. - 자식의 루트 뷰에 설정된 제약을 제거한다.
- 자식의 루트 뷰를 뷰 계층도에서 제거한다.
- 자식의
removeFromParentViewController
메서드를 호출하여 마무리한다.
Suggestions for Building a Container View Controller
- 컨테이너는 자식 뷰 컨트롤러의 루트 뷰에만 접근해야 한다.
- 자식 뷰 컨트롤러는 컨테이너에 대한 최소한의 정보만 가지고 있어야 한다.
만약 둘의 소통이 필요할 경우 delegation 패턴을 이용한다. - 일반적인 뷰를 통해 먼저 테스트를 하는 것이 좋다.
Delegating Control to a Child View Controller
부모는 자식의 모습을 결정하는 몇몇 요소를 자식에게 위임할 수 있다.
- 상태 바의 스타일을 결정하게 할 수 있다.:
childViewControllerForStatusBarStyle
와childViewControllerForStatusBarHidden
. - 선호되는 사이즈를 특정할 수 있다.:
preferredContentSize
.
Supporting Accessibility
Accessible한 앱은 조금 불편한 사람들도 유용한 도구로서 기능을 유지한 채로 이용할 수 있다. Accessible하기 위해 UI 요소의 정보를 VoiceOver에 전달해야 한다. 기본적인 UIKit 객체는 accessible하지만, 뷰 컨트롤러 관점에서 할 수 있는 몇 가지 점들이 있다.
- 모든 인터페이스가 accessbile 해야 한다.
- Accessible한 객체가 정확하고 도움이 되는 정보를 전달하도록 한다.
개발자는 VoiceOver focus ring의 위치를 설정하거나, VoiceOver 제스처에 대응하거나, accessibility 알림에 대응함으로써 VoiceOver 사용자 경험을 높일 수 있다.
Moving the VoiceOver Cursor to a Specific Element
VoiceOver의 포커싱이 되는 커서의 위치를 설정해줄 수 있다. 예를 들어 네비게이션 컨트롤러가 새로운 뷰 컨트롤러를 푸시하여 보여주면 해당 컨텐츠가 어떤 것인지 네비게이션 바의 타이틀에 포커싱을 할 수 있다.
위치를 바꾸기 위해 UIAccessibilityScreenChangedNotification
을 UIAccessibilityPostNotfication
함수를 통해 보낸다.
1 | override func viewWillAppear(_ animated: Bool) { |
회전이 일어나 레이아웃의 변경이 일어나면 UIAccessibilityLayoutChangedNotification
을 보내 커서의 위치를 재설정해야 한다.
Responding to Speical VoiceOver Gestures
VoiceOver는 앱에 특정한 액션을 야기하는 다섯 가지의 특별한 제스처를 가지고 있다.
- Escape: 두 손가락의 Z 모양의 제스처로 모달을 내리거나 팝 백할 때 사용.
- Magic Tap: 두 손가락으로 더블 탭.
- Three-Finger Scroll: 세 손가락으로 컨텐츠를 옆으로, 세로로 스크롤할 때 사용.
- Increment: 한 손가락으로 위로 하는 스와이프로 특정 값의 증가를 일으킨다.
- Decrement: 한 손가각으로 아래로 하는 스와이프로 특정 값의 감소를 일으킨다.
UIKit은 제스처가 들어오면 VoiceOver의 포커스가 되는 요소부터 리스폰더 체인을 따라 처리할 수 있는 객체를 찾는다. 만약 없다면 UIKit은 기본 시스템 액션을 실행한다.
Observing Accessibility Notifications
Accessibility notification을 듣는 것은 NotificationCenter
를 이용하여 Observer를 붙여 그에 따른 처리를 해주면 된다.