본 글은 UIViewController (애플 공식 문서)를 한국어로 번역하여 옮긴 글입니다.
UIKit 앱에서 뷰 계층 구조를 관리하기 위한 객체(Object)
iOS 2.0+ | iPadOS 2.0+ | Mac Catalyst 13.1+ | tvOS 9.0+ | visionOS 1.0+
@MainActor
class UIViewController : UIResponder
UIViewController 클래스는 모든 뷰 컨트롤러에 공통적인 동작을 정의합니다. UIViewController 클래스의 인스턴스를 직접 생성하는 건 무척 드뭅니다. 그 대신, UIViewController를 서브 클래스(subclass)하여 뷰 컨트롤러의 뷰 계층 구조 관리를 위한 메서드나 프로퍼티를 추가할 수 있습니다.
뷰 컨트롤러의 주요 기능은 아래와 같습니다.
기본 데이터의 변경에 반응하여 뷰의 컨텐츠를 업데이트합니다.
뷰와의 사용자 상호작용에 반응합니다.
뷰의 크기 조정과 전체 인터페이스의 레이아웃을 관리합니다.
앱에서 -다른 뷰 컨트롤러를 포함해- 다른 객체와 소통합니다.
뷰 컨트롤러는 뷰 컨트롤러가 관리하는 뷰들과 밀접하게 연결되어 있으며 뷰 계층 구조에서 발생하는 이벤트를 처리합니다. 뷰 컨트롤러는 UIResponder 객체이며 뷰 컨트롤러의 최상위 뷰와 그 뷰의 (다른 뷰 컨트롤러에 속하는) 상위 뷰 사이에 응답자 체인(Responder Chain)이 삽입됩니다. 만일 뷰 컨트롤러의 어떤 뷰도 이벤트를 처리하지 않으면, 뷰 컨트롤러는 그 이벤트를 직접 처리하거나 상위 뷰로 전달합니다.
뷰 컨트롤러는 거의 독립적으로 사용되지 않습니다. 그 대신, 앱의 사용자 인터페이스의 일부로 차지하는 여러 뷰 컨트롤러를 사용하는 경우가 많습니다. 예를 들어, 한 뷰 컨트롤러는 테이블로 항목(items)들을 보여주는 데 반해, 다른 뷰 컨트롤러는 이 테이블에서 선택된 항목을 자세히 보여줄 수 있습니다. 일반적으로, 한 번에 한 뷰 컨트롤러의 뷰만 보입니다. 뷰 컨트롤러는 새로운 뷰들을 보여주기 위해 다른 뷰 컨트롤러를 프리젠트(present)할 수 있으며, 다른 뷰 컨트롤러의 컨텐츠를 담기 위한 컨테이너(container) 역할을 맡으며 원하는 방식으로 뷰를 애니메이션할 수 있습니다.
모든 앱은 적어도 하나 이상 UIViewController를 상속한 서브 클래스를 가지고 있습니다. 더욱이, 앱은 많은 커스텀 뷰 컨트롤러를 가지고 있습니다. 커스텀 뷰 컨트롤러는 앱의 전반적인 동작을 정의하며, 여기에는 앱의 외형(Appearance)과 사용자 상호 작용에 대한 반응이 포함됩니다. 아래 섹션은 커스텀 서브 클래스가 수행하는 몇 가지 작업에 대한 간략한 개요를 제공합니다. 뷰 컨트롤러 사용 및 구현에 대한 자세한 정보는 View Controller Programming Guid for iOS를 참조하세요.
각 뷰 컨트롤러는 뷰 계층 구조를 관리하며, 뷰 계층 구조의 최상위 뷰는 이 클래스의 view 프로퍼티에 저장됩니다. 최상위 뷰는 나머지 뷰 계층을 담는 컨테이너 역할을 합니다. 최상위 뷰의 크기나 위치는 상위 뷰 컨트롤러나 앱의 윈도우(window)와 같이 해당 뷰를 가지는 객체에 의해 결정됩니다. 윈도우가 소유한 뷰 컨트롤러는 앱의 최상위 뷰 컨트롤러이며 해당 뷰 컨트롤러의 뷰는 윈도우의 사이즈에 맞게 채워집니다.
뷰 컨트롤러는 뷰를 느리게(lazily) 로드합니다. view 프로퍼티에 처음 접근할 때 뷰 컨트롤러의 뷰가 로드되거나 생성됩니다. 뷰 컨트롤러의 뷰를 지정하는 방법은 여러 가지가 있습니다.
앱의 스토리보드에서 뷰 컨트롤러와 뷰를 작성하세요. 스토리보드는 뷰를 작성하기 위한 좋은 방법입니다. 스토리보드를 사용하면 뷰와 뷰 컨트롤러와의 커넥션(Connections)을 지정할 수 있습니다. 스토리보드에서 뷰 컨트롤러 간의 관계와 세그웨이(segue)를 지정하여 앱의 동작을 더 쉽게 파악하고 수정할 수 있습니다.
스토리보드로부터 뷰 컨트롤러를 로드하기 위해서 적절한 UIStoryboard 객체의 instantiateViewController(withIdentifier:) 메서드를 호출하세요. 스토리보드 객체는 뷰 컨트롤러를 생성하고 코드로 반환합니다.
nib 파일로 뷰 컨트롤러를 위한 뷰를 작성하세요. nib 파일로 단일 뷰 컨트롤러의 뷰를 작성할 수 있으며 뷰 컨트롤러 간의 관계 또는 세그웨이를 정의하지 않습니다. nib 파일은 뷰 컨트롤러 자체에 대한 최소한의 정보만을 저장합니다.
nib 파일로 뷰 컨트롤러 객체를 초기화하려면 뷰 컨트롤러 클래스를 코드로 작성하고 init(nibName:bundle:) 메서드를 사용해 초기화하세요. 뷰가 요청되면 뷰 컨트롤러는 nib 파일에서 뷰를 로드합니다.
loadView() 메서드를 사용하여 뷰 컨트롤러의 뷰를 명시할 수 있습니다. 이 메서드에서 뷰 계층을 코드로 생성하고 그 계층의 최상위 뷰를 뷰 컨트롤러의 view 프로퍼티에 할당하세요.
이 모든 방식은 뷰를 적절히 생성하고 이를 view 프로퍼티로 드러내게 하는 동일한 결과를 가져옵니다.
🟡 Important
뷰 컨트롤러는 자신이 생성한 뷰와 하위 뷰의 유일한 소유자입니다. 뷰 컨트롤러는 이러한 뷰를 생성하고 적절한 시점(예: 뷰 컨트롤러가 해제될 때)에 소유권을 포기해야 하는 책임이 있습니다. 스토리보드나 nib 파일을 사용하여 뷰 객체를 저장하는 경우, 뷰 컨트롤러가 요청할 때마다 각 뷰 컨트롤러 객체는 이러한 뷰의 복사본은 자동으로 얻습니다. 그러나, 뷰를 수동으로 생성하는 경우, 각 뷰 컨트롤러는 고유한 뷰만을 가져야 하며, 뷰 컨트롤러 간의 뷰를 공유하면 안됩니다.
뷰 컨트롤러의 최상위 뷰는 항상 할당된 공간에 맞게 배치됩니다. 뷰 계층 구조에서 각 뷰는 상위 뷰의 바운즈(bounds) 내에서 어떻게 배치되게 할 지 결정하는 오토 레이아웃 제약을 인터페이스 빌더로 적용해주어야 합니다. 코드로 제약을 생성하고 적절한 시점에 뷰에 적용할 수도 있습니다. 제약을 생성하는 방법에 대한 자세한 정보는 Auto Layout Guide를 참조하세요.
화면에 보이는 뷰가 변경되면, 뷰 컨트롤러는 자동으로 자신의 메서드를 호출하여 서브 클래스가 이 변화에 반응할 수 있게 합니다. viewIsAppearing(_:)과 같은 메서드를 사용하여 뷰가 화면에 보이기 전에 준비 작업을 하고, viewWillDisappear(_:) 메서드를 사용하여 다른 상태 정보나 변경점을 저장할 수 있습니다. 다른 메서드를 사용하여 적절한 작업을 수행해보세요.
아래 이미지는 뷰 컨트롤러의 뷰에 대해 화면에 보이는 상태와 발생할 수 있는 상태 전환을 보여줍니다. 모든 will 콜백 메서드가 did 콜백 메서드와 쌍을 이루는 건 아닙니다. will 콜백 메서드에서 프로세스를 시작했다면, 대응되는 did 콜백 메서드와 반대되는 will 콜백 메서드 모두에서 해당 프로세스를 마무리지어야 한다는 점을 확실히 할 필요가 있습니다.

iOS 8부터 회전(rotation)과 관련된 모든 메서드가 Deprecated 되었습니다. 그 대신, 회전은 뷰 컨트롤러의 뷰 크기가 변하는 것으로 취급되며, 따라서 viewWillTransition(to:with:) 메서드를 사용하여 이 사실을 알립니다. 인터페이스 방향(orientation)이 변하면, UIKit은 윈도우의 최상위 뷰 컨트롤러에서 이 메서드를 호출합니다. 그러면 해당 뷰 컨트롤러는 자식 뷰 컨트롤러에게 이 사실을 알리며, 뷰 컨트롤러 계층 구조에 따라 이 메시지를 전파합니다.
iOS 6 및 iOS 7에서는 앱이 Info.plist 파일에서 정의된대로 인터페이스 방향을 지원합니다. 뷰 컨트롤러는 지원하는 방향 목록을 제한하기 위해supportedInterfaceOrientation 프로퍼티를 재정의할 수 있습니다. 일반적으로 시스템은 이 프로퍼티를 윈도우의 최상위 뷰 컨트롤러나 화면 전체를 채우는 프리젠트된 뷰 컨트롤러(예: 풀스크린 커버)에서 호출합니다. 하위 뷰 컨트롤러는 상위 뷰 컨트롤러가 제공하는 윈도우의 일부만 사용하며, 지원하는 회전에 대한 결정에 더 이상 직접적으로 참여하지 않습니다. 앱의 방향 마스크(mask)와 뷰 컨트롤러의 방향 마스크의 교차점이 뷰 컨트롤러의 회전 가능한 방향을 결정하는 데 사용됩니다.
preferredInterfaceOrientationForPresentation 프로퍼티를 재정의하여 뷰 컨트롤러가 화면 전체를 채우며 프리젠트될 때 특정 방향으로 띄우도록 할 수 있습니다.
화면에 보이는 뷰 컨트롤러에 회전이 발생하면, 회전하는 동안 willRotate(to:duration:), willAnimateRotation(to:duration:)과 didRotate(from:) 메서드가 호출됩니다. 또한, 최상위 뷰가 상위 뷰 컨트롤러에 의해 크기와 위치가 재조정되고 나면 viewWillLayoutSubviews() 메서드가 호출됩니다. 방향이 변경될 때 뷰 컨트롤러가 화면에 보이지 않는다면 회전 메서드는 절대로 호출되지 않습니다. 그러나, 뷰가 화면에 보이면 viewWillLayoutSubviews() 메서드가 호출됩니다.
⚪️ NOTE
앱의 런치(Launch)되는 시점에 앱은 항상 portrait 방향으로 인터페이스를 설정합니다. application(_:didFinishLaunchingWithOptions:) 메서드가 반환되고 나면 앱은 위에서 설명한 회전 메커니즘을 사용하여 윈도우를 보여주기 전에 뷰를 적절한 방향으로 회전시킵니다.
커스텀 UIViewController의 서브 클래스는 컨테이너 뷰 컨트롤러(container view controller)로도 사용될 수 있습니다. 컨테이너 뷰 컨트롤러는 자식 뷰 컨트롤러로도 알려진 다른 뷰 컨트롤러가 가지는 컨텐츠 표시를 관리합니다. 자식 뷰 컨트롤러의 뷰는 그대로 표시되거나 컨테이너 뷰 컨트롤러가 소유한 다른 뷰와 함께 표시될 수 있습니다.
컨테이너 뷰 컨트롤러 서브 클래스는 자식 뷰 컨트롤러와의 연결을 위한 공개 인터페이스(public interface)를 선언해야 합니다. 이 메서드의 성격은 개발자에게 달려있으며 생성한 컨테이너의 의미에 따라 좌우됩니다. 개발자는 뷰 컨트롤러에서 자식 뷰 컨트롤러를 한번에 얼마나 보여주고, 자식 뷰 컨트롤러가 언제 보여지고, 뷰 컨트롤러의 뷰 계층 구조 안에서 어디서 드러나게 할 지를 결정할 필요가 있습니다. 뷰 컨트롤러는 자식 뷰 컨트롤러 간 공유되는 관계가 있다면, 그 관계를 정의합니다. 컨테이너를 위한 명확한 공개 인터페이스를 설정함으로써, 컨테이너의 동작이 어떻게 구현되어 있는지 세세한 세부 사항을 들여다보지 않더라도 자식 뷰 컨트롤러가 컨테이너의 기능을 논리적으로 사용하도록 보장할 수 있습니다.
컨테이너 뷰 컨트롤러는 자식 뷰 컨트롤러의 최상위 뷰를 뷰 계층 구조에 추가하기 전 자식 뷰 컨트롤러를 자신과 연결해야 합니다. 이를 통해 iOS는 이벤트를 자식 뷰 컨트롤러와 해당 컨트롤러가 관리하는 뷰에 전달할 수 있습니다. 마찬가지로, 자식 뷰 컨트롤러의 최상위 뷰를 뷰 계층 구조에서 제거하면, 해당 자식 뷰 컨트롤러를 자신으로부터 연결을 끊어야 합니다. 이 관계를 형성하고 끊으려면, 컨테이너는 베이스 클래스에 정의된 특정 메서드를 호출해야 합니다. 이 메서드는 컨테이너 클래스의 클라이언트가 호출하도록 설계되지 않았으며, 예상된 포함 동작을 제공하기 위해 컨테이너의 구현 내부에서만 사용되어야 합니다.
아래는 호출해야 할 수 있는 필수 메서드입니다.
⚪️ NOTE
컨테이너 뷰 컨트롤러를 생성할 때, 어느 메서드도 재정의를 할 필요가 없습니다.
기본적으로, 회전과 화면 전환 콜백은 자동으로 자식 뷰 컨트롤러에게 전달됩니다. 이러한 동작을 제어하기 위해 선택적으로 shouldAutomaticallyForwardRoationMethods() 메서드와 shouldAutomaticallyForwardAppearanceMethods 프로퍼티를 재정의할 수 있습니다.
메모리는 iOS에서 핵심적인 자원이며, 뷰 컨트롤러는 위험한 시점에 메모리 공간을 줄이기 위해 내부적인 지원책을 제공하고 있습니다. UIViewController 클래스는 불필요한 메모리를 해제하는 didReceiveMemoryWarning() 메서드를 통해 저메모리 상태를 자동으로 처리하는 기능을 제공합니다.
뷰 컨트롤러의 restorationIdentifier 프로퍼티에 값을 할당하면, 앱이 백그라운드 상태로 전환될 때 시스템은 뷰 컨트롤러에게 자기 자신을 인코드(encode)하라고 요청할 수 있습니다. 뷰 컨트롤러가 보존되면, 뷰 컨트롤러는 뷰 계층 구조 내 복구 식별자(restoration identifier)를 가지는 다른 뷰의 상태도 보존합니다. 뷰 컨트롤러는 그 외의 상태를 자동으로 저장하지 않습니다. 커스텀 컨테이너 뷰 컨트롤러를 구현하는 경우, 자식 뷰 컨트롤러를 직접 인코드해야 합니다. 인코드하는 각 자식 뷰 컨트롤러는 고유한 복구 식별자를 가져야 합니다.
시스템이 어떤 뷰 컨트롤러를 보존하고 복구할 지 결정하는 방식에 대한 자세한 내용은 App Programming Guid for iOS를 참조하세요. 상태 보존과 복구에 대한 내용은 Restoring Your App's State를 참조하세요.