https://developer.apple.com/documentation/uikit/uiviewcontroller
"An object that manages a view hierarchy for your UIKit app."
UIKit
앱의 뷰 계층구조를 관리하는 객체입니다.
@MainActor class UIViewController : UIResponder
UIViewController
클래스는 모든 뷰 컨트롤러에 공통되는 공유된 움직임을 정의합니다. 아마 UIViewController
클래스의 인스턴스를 생성하는 일은 거의 없을 것입니다. 대신 UIViewController
의 서브클래싱 하고 뷰 컨트롤러의 뷰 계층구조를 다루기 위한 메소드와 속성을 정의하게 될 것입니다.
뷰 컨트롤러의 주요 책임은 아래와 같습니다.
뷰 컨트롤러는 스스로가 다루는 뷰와 묶여있습니다. 그리고 뷰 계층구조 내에서 이벤트를 다루는 것에 참여합니다. 구체적으로 뷰 컨트롤러는 UIResponder
객체이자 리스폰더 체인에 속해있습니다. 리스폰더 체인 내에 뷰 컨트롤러의 위치는 뷰 컨트롤러의 루트 뷰와 그 루트 뷰가 가지고 있는 슈퍼뷰의 사이가 됩니다. 이벤트를 다루는 뷰가 없다면, 뷰 컨트롤러는 이벤트를 다루는 옵션을 가지고 있거나 상위 뷰로 전달합니다.
뷰 컨트롤러는 보통 고립된 형태로 사용하지 않습니다. 대신 여러 뷰 컨트롤러를 사용하게 될 것이며, 각 뷰 컨트롤러는 앱의 UI를 일정 양에 맞춰 보유하고 있을 것입니다. 예를 들어 하나의 뷰 컨트롤러는 테이블의 내용물을 보여주고 있을 것이며, 다른 뷰 컨트롤러는 테이블에서 선택된 내용물을 보여주게 될 것입니다. 보통 한 번에 하나의 뷰 컨트롤러가 가지고 있는 여러 가지 뷰만이 보일 것입니다. 뷰 컨트롤러는 다른 뷰 컨트롤러에게 새로운 뷰의 집합을 보여주길 요청하거나 다른 뷰 컨트롤러의 컨텐트를 가지고 있을 것입니다.
모든 앱은 적어도 하나의 UIViewController
가 부모인 하위 클래스를 가지고 있을 것입니다. 앱은 여러 커스텀 뷰 컨트롤러를 가질 수도 있습니다. 커스텀 뷰 컨트롤러는 앱의 전반적임 움직임을 정의하게 될 것이며, 앱의 모습과 사용자 상호작용을 다루는 방법을 지니고 있을 것입니다. 아래 섹션들에서 커스텀 서브클래스가 수행하는 몇 가지 간단한 overview를 소개하게 될 것입니다. 뷰 컨트롤러를 사용하기에 더 많은 세부적인 정보가 필요하다면 다음 링크를 참고하면 됩니다.
View Controller Programming Guide for iOS
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/index.html#//apple_ref/doc/uid/TP40007457
각각의 뷰 컨트롤러가 뷰 계층구조를 다루며, 루트 뷰는 이 객체의 뷰 속성에 저장됩니다. 루트 뷰는 뷰 계층구조의 남은 부분을 위한 컨테이너 역할을 합니다. 이 루트 뷰를 가지고 있는 객체(부모 뷰 컨트롤러 혹은 앱의 윈도우)에 의해 루트 뷰의 크기와 위치가 결정됩니다. 윈도우가 가지고 있는 뷰 컨트롤러는 앱의 루트 뷰 컨트롤러가 되며, 이 뷰 컨트롤러의 뷰가 윈도우를 채웁니다.
뷰 컨트롤러는 가지고 있는 뷰를 게으르게 로드합니다. 뷰 속성에 처음 접근하는 것을 통해 뷰 컨트롤러의 뷰가 로드되거나 생성됩니다. 뷰를 구체화하는 방법은 여러 가지가 있으며, 아래에 소개되어 있습니다.
instantiateViewController
메소드 호출이 필요합니다. 스토리보드 객체는 뷰 컨트롤러를 생성하고 반환합니다.init(nibName:bundle:)
메소드를 사용해 초기화하면 됩니다. 뷰가 요청될 때 뷰 컨트롤러는 nib 파일로부터 뷰를 로드합니다.loadView()
메소드를 사용해 뷰 컨트롤러에 있는 v뷰를 구체화할 수 있습니다. 이 메소드를 통해 뷰 계층구조를 코드로 생성할 수 있고, 그 계층구조의 루트 뷰를 뷰 컨트롤러의 뷰 속성에 할당할 수 있습니다.위에 소개된 모든 방법은 같은 결과를 갖고 있으며, 필요한 뷰를 생성하고 뷰 속성을 통해 뷰가 화면에 나타나도록 해줍니다.
중요
뷰 컨트롤러는 가지고 있는 뷰의 유일한 소유자이고, 뷰 컨트롤러가 생성하는 하위뷰 역시 뷰 컨트롤러만 갖고 있습니다. 뷰를 생성하는 것에 책임을 갖고 있고 적당한 때에 이 뷰를 해제해주는 것에도 책임이 있습니다. 뷰 객체를 저장하기 위해 스토리보드 혹은 nib 파일을 사용한다면, 각각의 뷰 컨트롤러 객체는 뷰 컨트롤러가 뷰에 요청을 할 때 자동으로 이러한 뷰의 복사본을 얻게 됩니다. 그러나 만약 뷰를 코드로 작성하면, 각각의 뷰 컽느롤러는 고유의 뷰만을 가지고 있어야 합니다. 뷰를 다른 뷰 컨트롤러와 공유할 수 없습니다.
뷰 컨트롤러의 루트 뷰는 항상 주어진 공간에 가득한 형태로 있습니다. 뷰 계층구조에서 상위 뷰 내부에 다른 뷰의 위치를 지정하거나 크기를 설정할 수 있는 오토 레이아웃을 구체화하려면 인터페이스 빌더를 사용해야 합니다. 인터페이스 빌더를 사용하는 것이 아니라 코드 작성을 통해 오토 레이아웃을 설정할 수도 있으며, 원하는 시점에 뷰를 추가할 수 있습니다. 오토 레이아웃에 대한 더 자세한 내용은 아래 링크를 참고하시기 바랍니다.
Understanding Auto Layout
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html#//apple_ref/doc/uid/TP40010853
뷰의 변화를 시각화할 때 뷰 컨트롤러는 자동으로 가지고 있는 메소드를 호출해 하위 클래스가 변화에 응답할 수 있도록 합니다. viewWillAppear()
와 같은 메소드는 뷰가 화면에 나타날 수 있도록 뷰를 준비합니다. 그리고 viewWIllDisappear()
메소드는 변경된 내용이나 다른 상태 정보를 저장합니다. 원하는 변화가 있다면 이를 구현할 수 있는 다른 여러 가지 메소드가 있으니 목적에 맞게 사용하면 됩니다.
아래 그림은 뷰 컨트롤러가 갖고 있는 뷰의 시각적 상태와 발생할 수 있는 상태 전환을 보여줍니다. 모든 will callback method가 did callback method에 한 쌍을 이루는 것은 아닙니다. 만약 will callback method에서 프로세스를 시작할 필요가 있다면, did와 반대되는 will에 사이에 상응하는 call back method 모두의 프로세스를 종료시켜야 합니다.
관련 자료에 대한 링크를 남기겠습니다.
https://velog.io/@panther222128/ViewController-Life-Cycle
iOS 8부터 화면 전환(rotation, 아이폰을 세로로 보다가 가로로 눕힌 형태) 메소드는 사라졌습니다. 대신 화면 전환은 뷰 컨트롤러의 뷰 사이즈가 바뀌는 것처럼 다루게 됐습니다. 이를 사용하려면 viewWillTransition
메소드를 활용하면 됩니다. 인터페이스의 방향이 변경될 때 UIKit
은 이 메소드를 윈도우의 루트 뷰 컨트롤러에 호출합니다. 해당 뷰 컨트롤러는 자식 뷰 컨트롤러에게 알리고, 뷰 컨트롤러의 계층구조를 통해 메시지를 전달합니다.
iOS 6과 iOS 7에서 앱은 앱 내에 있는 Info.plist 파일에서 결정된 인터페이스 orientation을 지원합니다. 뷰 컨트롤러는 지원되는 orientation의 내역들을 제한하기 위해 supportedInterfaceOrientation
메소드를 오버라이드 합니다. 보통 시스템은 이 메소드를 윈도우의 루트 뷰 컨트롤러 혹은 전체 화면을 채우는 뷰 컨트롤러에서만 호출합니다. 자식 뷰 컨트롤러는 부모 뷰 컨트롤러로부터 받은 윈도우의 일정 부분만 사용하며, 기기 회전과 관련이 있는 의사결정에 직접적으로 관여하지 않습니다. 앱의 orientation mask와 뷰 컨트롤러의 orientation mask의 intersection은 어떤 orientation이 뷰 컨트롤러가 회전될 수 있는지를 결정하기 위해 사용됩니다.
특정 orientation에 있는 가득찬 화면이 제공되기를 의도하려면 preferredInterfaceOrientationForPresentation method
를 오버라이드하면 됩니다.
시각적인 뷰 컨트롤러의 회전이 발생할 때, willRatate()
, willAnimateRoation()
, didRotate()
메소드가 회전이 일어나는 동안 호출됩니다. 또한, 뷰의 크기와 위치가 부모에 있었던 형태와 바뀌도록 조정된 후에 viewWillLayoutSubviews()
메소드도 호출됩니다. orientation 변화가 발생했음에도 뷰 컨트롤러를 볼 수 없다면, 회전 메소드는 절대 호출되지 않습니다. 그러나 viewWillLayoutSubviews()
메소드는 뷰를 눈으로 볼 수 있을 때 호출됩니다. 기기의 orientation을 결정하기 위해 statusBarOrientation
메소드를 호출하면 됩니다.
Note
launch 시, 앱은 화면 orientation에서 인터페이스를 형성시켜야 합니다. applcation(didFinishLaunchingwithOptions:)
메소드가 반환한 후에, 뷰 컨트롤러는 윈도우를 보여주기에 앞서 적절한 orientation에 뷰를 회전시키기 위해 앞서 이야기 한 회전 메커니즘을 사용합니다.
UIViewContoller
의 하위 클래스도 컨테이너 뷰 컨트롤러의 역할을 할 수 있습니다. 컨테이너 뷰 컨트롤러는 자식 자식 뷰 컨트롤러를 다룰 수 있습니다. 자식 뷰 컨트롤러의 뷰는 있는 그대로 보여질 수 있으며, 컨테이너 뷰 컨트롤러가 가지고 있는 뷰와 함께 보여질 수도 있습니다.
컨테이너 뷰 컨트롤러 서브클래스는 자식 뷰 컨트롤러들을 연관시킬 수 있도록 공용 인터페이스를 선언해야 합니다. 이러한 메소드의 본질적인 부분은 직접 설정해주는 것에 달려있으며, 생성하는 컨테이너의 맥락에 의존합니다. 정의한 뷰 컨트롤러에서 한 번에 얼마나 많은 자식 뷰 컨트롤러를 보여줘야 하는지 결정해줘야 하며, 언제 자식 뷰 컨트롤러가 보여지게 될지, 그리고 뷰 컨트롤러 계층구조에서 어디에 보여지게 될지도 정의해줘야 합니다. 또한, 자식 뷰 컨트롤러 사이의 관계도 정의해야 합니다. 잘 작성된 공용 인터페이스를 통해 자식 뷰 컨트롤러 각자가 갖는 기능을 논리적으로 사용할 수 있도록 해줘야 하며, 어떻게 컨테이너가 움직임을 수행하는지를 나타내는 세부사항에는 접근하지 못하게 해야 합니다.
컨테이너 뷰는 반드시 자식 뷰 컨트롤러와 연결되어야 합니다. 이 연결은 뷰 계층구조 안에 자식의 루트 뷰를 추가하기 전에 이뤄져야 합니다. 이렇게 해야만 iOS가 이벤트를 도달하고자 하는 이벤트의 위치(자식 뷰 컨트롤러, 자식 뷰 컨트롤러가 갖는 뷰)까지 잘 도달할 수 있도록 명확하게 경로를 만들어줄 수 있습니다. 유사하게 자식의 루트 뷰가 뷰 계층구조로부터 삭제된 후, 컨테이너 뷰가 자식 뷰 컨트롤러와의 연결을 끊습니다. 이렇게 연결을 만들거나 연결을 끊기 위해 컨테이너 셀은 베이스 클래스에 의해 정의된 특정 메소드를 호출할 것입니다. 이런 메소드들은 클라이언트에 의해 호출되도록 의도된 것이 아닙니다. 이런 메소드들은 컨테이너가 의도하는 움직임을 제공할 수 있도록 해야 하는데, 그렇기 때문에 오직 컨테이너만 사용할 수 있습니다.
호출해야 하는 필수 메소드는 아래와 같습니다.
addChild()
removeFromParent()
willMove(toParent:)
didMove(toParent:)
Note
컨테이너 뷰 컽느롤러를 생성할 때 모든 메소드의 오버라이드가 요구되지는 않습니다.기본값으로 기기 회전과 appearance 콜백은 자동으로 자식들에게 전달됩니다. 이 작동을 제어하려면 선택적으로
shouldAutomaticallyForwardRotationMethods()
,shouldAutomaticallyForwardAppearanceMethods
메소드를 오버라이드하면 됩니다.
메모리는 iOS에서 중요한 리소스입니다. 그리고 뷰 컨트롤러는 중요한 시점에 메모리가 차지하는 공간을 줄이기 위해서 내장된 지원기능을 제공합니다. UIViewController
클래스는 적은 메모리 상태를 자동으로 다루기 위한 몇 가지를 제공하고, 이러한 메모리 관리는 didReceiveMemoryWarning()
메소드를 통해 실현됩니다.
뷰 컨트롤러의 resotrationIdentifier
속성에 값을 할당하면, 시스템은 앱이 백그라운드로 전환될 때 뷰 컨트롤러에게 스스로 인코딩할 것을 요청합니다. 보존이 된 경우 뷰 컨트롤러는 복구 아이덴티파이어를 갖고 있는 뷰 계층구조에서 모든 뷰의 상태를 보존할 것입니다. 뷰 컨트롤러는 자동으로 상태를 저장하지는 않습니다. 커스텀 컨테이너 뷰 컨트롤러를 구현하는 경우 모든 자식 뷰 컨트롤러를 직접 인코딩해야 합니다. 인코딩한 각각의 자식은 고유한 복구 아이덴티파이어를 가지고 있어야 합니다.
어떤 뷰 컨트롤러를 보존하고 복구할지 결정하는 방법에 대한 더 많은 정보는 App Programming Guide for iOS를 살펴보시기 바랍니다. 상태 보존과 복구에 대한 예시는 Restoring Your App's State를 살펴보시기 바랍니다.
App Programming Guide for iOS
https://developer.apple.com/documentation/uikit#//apple_ref/doc/uid/TP40007072
https://velog.io/@panther222128/UIKit
Restoring Your App’s State
https://developer.apple.com/documentation/uikit/uiscenedelegate/restoring_your_app_s_state