study record
[iOS] UIViewController (UIKit - UIViewController) 본문
UIViewController
UIKit 앱에서 view 계층을 관리하는 객체
@MainActor class UIViewController : UIResponder
Overview
UIViewController 클래스는 모든 뷰컨트롤러 클래스에 공통된 행동을 정의한다. UIViewController 클래스의 인스턴스를 직접적으로 만들지는 않을 것이다. 대신에 UIViewController의 하위 클래스를 만들고, 뷰 계층을 관리하는데 필요한 메서드와 프로퍼티를 더할 것이다.
뷰 컨트롤러의 메인 역할들
- 뷰 컨텐츠의 업데이트. 보통 데이터의 변화에 따라 반응한다.
- 뷰에 대한 유저 상호작용에 반응
- 전체적인 인터페이스 레이아웃을 관리하고 뷰를 리사이징
- 앱의 다른 객체들과 협력 (다른 뷰컨트롤러 포함)
뷰 컨트롤러는 뷰 계층에서 이벤트를 다루는 일을 책임지고 관리한다. 특별히, 뷰 컨트롤러는 UIResponder 객체이다. 그리고 뷰컨트롤러의 루트 뷰와 뷰의 상위 뷰 사이에서 responder 체인에 주입된다. 이것은 다른 뷰컨트롤러 사이에도 속한다. 만약 뷰컨트롤러의 뷰들 중 아무도 이벤트를 다루지 않는다면 뷰컨트롤러가 이벤트를 다루는 선택지를 가지던지 상위뷰로 건네던지의 선택지를 가지게 된다.
뷰컨트롤러는 거의 독립되어 사용되지 않는다. 대신에 다양한 뷰컨트롤러를 사용하고, 각각의 뷰컨트롤러가 앱의 유저인터페이스를 차지한다. 예를 들어, 한 뷰컨트롤러가 아이템들의 테이블을 보여주고, 다른 뷰컨트롤러가 테이블로부터 선택된 아이템을 보여준다고 하자. 보통 한 뷰컨트롤러로부터의 뷰는 한 번에 보인다. 뷰컨트롤러는 뷰들의 새로운 세트를 보여주기 위해 다른 뷰컨트롤러를 보여준다.
Subclassing Notes
모든 앱은 UIViewController의 커스텀 하위 클래스를 포함한다. 더 종종 앱들은 많은 커스텀 뷰컨트롤러들을 포함한다. 커스텀 뷰컨트롤러들은 앱의 전반적인 행동을 정의하고, 앱의 외관과 유저 인터랙션에 어떻게 반응할지를 포함한다.
View Management
각각의 뷰컨트롤러는 뷰 계층을 관리하고, 클래스의 view 프로퍼티에 저장된 루트 뷰를 관리한다. 루트 뷰는 뷰 계층의 나머지들을 위한 컨테이너로서 행동한다. 루트뷰의 크기와 위치는 뷰를 소유한 객체에 의해 결정된다. 그리고 그건은 부모 뷰컨트롤러나 앱의 윈도우이다. 뷰컨트롤러는 앱의 루트 뷰컨트롤러인 윈도우에 의해 소유되고, 윈도우를 꽉 채울 사이즈로 뷰가 채워진다.
뷰컨트롤러들은 그들의 뷰를 lazily 늦게 로드한다. 처음으로 view 프로퍼티에 접근하는 것을 로드하거나 뷰컨트롤러의 뷰들을 만드는 작업에 늦게 로드된다. 뷰컨트롤러를 위한 뷰를 정의하는 방법에 여러 방법들이 있다.
- 뷰컨트롤러와 뷰들을 스토리보드에 정의한다. 스토리보드는 뷰를 정의하기에 선호되는 방법이다. 스토리보드와 함께 뷰들과 뷰컨트롤러의 연결을 정의할 수 있다. 또한 관계들과 뷰컨트롤러들 사이의 세그를 정의할 수 있으며 앱의 행동을 보고 변화시키기에 쉽다.
스토리보드로부터 뷰컨트롤러를 로드하기 위해서 instantiateViewController(withIdentifier:) 메서드를 호출하면 된다. 스토리보드 객체는 뷰컨트롤러를 만들어주고 코드에 리턴해준다.
- Nib 파일을 사용해 뷰컨트롤러를 위한 뷰를 정의한다. nib 파일은 하나의 뷰컨트롤러의 뷰들을 정의하지만 세그나 관계들을 정의하지는 않는다. nib 파일은 뷰컨트롤러 스스로에 대해 작은 정보를 저장한다.
nib 파일을 사용해 뷰컨트롤러 객체를 초기화하기 위해서 뷰컨트롤러 클래스를 코드로 만들고, init(nibName:bundle:) 메서드를 사용해 초기화한다. 뷰가 요청될 때 뷰컨트롤러가 nib 파일로부터 뷰컨트롤러를 로드한다.
- 뷰컨트롤러를 위해 뷰들을 loadView() 메서드를 사용해 정의한다. 이 메서드로 뷰 계층을 코드로 나타내고 뷰 계층의 루트뷰를 뷰컨트롤러의 뷰 프로퍼티에 할당한다.
이 기술들은 같은 최종 결과를 가진다. 적절한 뷰들의 세트를 만들고 뷰 프로퍼티를 통해 그들을 노출시킨다.
*Important
뷰컨트롤러는 뷰와 하위 뷰들의 주인이다. 뷰들을 만드는 데에 책임이 있고 뷰컨트롤러가 보여질 때마다 적절한 횟수만큼 그들을 포기해야 한다. 만약 스토리보드나 nib 파일을 뷰를 저장하기 위해 사용한다면, 각각의 뷰컨트롤러는 자동적으로 뷰들의 복사본을 얻는다. 그러나 만약 뷰를 수동으로 만든다면 각각의 뷰컨트롤러는 독특한 뷰들의 세트를 가져야 한다. 뷰컨트롤러 사이의 뷰를 공유할 수 없다.
뷰컨트롤러의 루트 뷰는 할당된 공간에 맞게 사이즈가 차게 된다. 뷰 계층에서 다른 뷰들을 위해 어떻게 뷰가 위치하고 상위 뷰의 bounds에 맞추어 크기를 변화할지에 대한 오토 레이아웃 제약들을 정의하기 위해 Interface Builder를 사용한다. 또한 코드로 제약들을 만들 수 있고 적절한 시간에 뷰들을 더할 수 있다.
Handling View-Related Notifications
뷰들의 보여지는 것들이 변화할 때, 뷰컨트롤러는 자동적으로 하위 클래스들이 변화에 반응할 수 있도록 메서드들을 호출한다. 화면에 나타내기 위해 뷰들을 준비하고자 viewWillAppear(_:)를 사용하고, 상태 정보나 변화를 저장하기 위해 viewWillDisappear(_:)를 사용한다. 적절한 변화를 만들기 위해 다른 메서드들을 사용한다.
Figure1은 일어날 수 있는 뷰컨트롤러의 뷰와 상태 변화들을 위한 시각적인 상태들이다. 모든 will 콜백 메서드가 did 콜백 메서드와 짝이 맞춰진 것은 아니다. 만약 will 콜백 메서드를 시작한다면 did나 will 콜백 메서드로 프로세스를 끝마치는 것을 확실히 해야 한다.
Handling View Rotations
iOS 8에서 모든 회전 관련 메서드들은 deprecated되었다. 대신에 회전은 뷰컨트롤러의 뷰의 크기의 변화로서 다루어지고, viewWillTransition(to:with:) 메서드를 사용해 보고된다. 인터페이스 방향 변화가 일어날 때, UIKit은 윈도우의 루트 뷰컨트롤러에서 이 메서드를 호출한다. 뷰컨트롤러가 자식 뷰컨트롤러에 뷰컨트롤러 계층을 통해 메시지를 전파하여 알린다.
iOS6, iOS7에서 앱은 앱의 Info.plist에서 인터페이스 방향을 정의했다. 뷰컨트롤러는 supportedInterfaceOrientaions 메서드를 지원 방향 리스트에 제한하기 위해 오버라이드할 수 있다. 전형적으로 시스템은 윈도우의 루트 뷰 컨트롤러에서나 전체 화면을 채우기 위해 현재 보여지는 뷰컨트롤러에서 이 메서드를 호출한다. 자식 뷰 컨트롤러들은 그들의 부모 뷰컨트롤러에 의해 그들을 위해 제공된 윈도우의 부분을 사용한다. 그리고 더이상 회전 지원에 대해 결정에 직접적으로 참여하지 않는다.
뷰컨트롤러가 특정 방향에서 전체 스크린으로 나타나기를 의도학 위해 preferredInterfaceOrientationForPresentation을 오버라이드 할 수 있다.
보여지는 뷰컨트롤러에 회전이 일어날 때, willRotate(to:duration:), willAnimateRotation(to,duration:), didRotate(from:) 메서드가 회전 중에 호출된다. viewWillLayoutSubviews()메서드는 뷰가 부모에 의해 크기와 위치가 조정된 후에 불려진다. 만약 뷰 컨트롤러가 방향 변화가 일어났을 때 보이지 않는다면 회전 메서드는 호출되지 않는다. 그러나 viewWillLayoutSubviews() 메서드는 뷰가 보여지게 되면 호출된다. 이 메서드의 시행은 장치 방향을 결정하기 위해 statusBarOrientation을 호출한다.
*Note
런치 타임에 앱들은 인터페이스에 자신의 방향에서 셋업을 한다. application(_:didFinishLaunchingWithOptions:) 메서드가 리턴된 후에, 앱은 뷰컨트롤러 회전 메카니즘을 사용한다.
Implementing a Container View Controller
커스텀 UIViewController 클래스는 컨테이너 뷰컨트롤러로서 행동할 수 있다. 컨테이너 뷰컨트롤러는 다른 뷰컨트롤러들(자식 뷰컨트롤러)의 컨텐츠를 표현을 관리한다. 자식 뷰는 컨테이너 뷰 컨트롤러에 의해 소유된 뷰들의 조합으로 보여질 수 있다.
컨테이너 뷰컨트롤러 클래스는 자식과 연결을 위해 pulic interface를 선언해야 한다. 이 메서드들의 본질은 만들어내는 컨테이너의 구문에 의존한다. 얼마나 많은 자식을 뷰컨트롤러에 한 번에 보여질 수 있을지를 결정하고, 언제 보여질지, 어디에 보여질지를 결정한다. 뷰컨트롤러 클래스는 관계를 정의한다. 깨끗한 pulic 인터페이스를 만들어지는 것에 의해 자식이 논리적으로 그 수용성들을 사용하고 많은 Private 디테일들에 접근하는 것 없이 행동을 할 수 있도록 한다.
컨테이너 뷰 컨트롤러는 뷰 계층에 자식의 루트뷰를 더하기 전에 스스로 자식 뷰컨트롤러를 연결시킬 수 있다. 이것은 iOS가 자식 뷰컨트롤러에 route 이벤트를 자식 뷰컨트롤러와 뷰들에 허락했기 때문이다. 자식의 류트 뷰를 뷰계층으로부터 제거한 후에 자식 뷰컨트롤러의 연결을 끊어야 한다. 이 연결을 끊거나 만들기 위해 컨테이너는 클래스에 의해 정의된 특정 메서드들을 호출한다. 이 메서드들은 컨테이너 클래스의 클라이언트들에 의해 호출되도록 의도되지 않는다. 그들은 컨테이너의 시행에 의해 사용되도록 존재한다.
- addChild(_:)
- removeFromParent(_:)
- willMove(toParent:)
- didMove(toParent:)
Memory Management
iOS에서 메모리는 중요한 자원이다. 뷰컨트롤러는 메모리 지출을 줄이기 위해 내재된 지원을 제공한다. UIViewController 클래스는 didReceiveMemoryWarning() 메서드를 통해 낮은 메모리 컨디션의 자동적 핸들링을 제공한다.
State Preservation and Restoration
만약 뷰컨트롤러의 restorationIdentifier 프로퍼티에 값을 할당한다면 시스템은 뷰컨트롤러에게 앱이 백그라운드로 갈 때 해독할지를 묻는다. preserved일 때, 뷰컨트롤러는 뷰계층의 뷰들의 상태를 보존한다. 뷰컨트롤러들은 자동적으로 상태를 저장하지 않는다. 만약 커스텀 컨테이너 뷰컨트롤러를 시행한다면 자식 뷰컨트롤러를 해독해야 한다. 각각의 자식은 독특한 restoration 식별자를 가져야 한다.