View Controller Programming Guide for iOS - Overview

The Role of View Controllers

뷰 컨트롤러는 앱 내부 구조의 기반으로 다음과 같은 역할을 한다.

  • UI를 관리
  • 데이터와의 인터랙션
  • UI의 전환

이러한 중요한 역할을 하기 때문에 뷰 컨트롤러는 개발자가 하는 모든 것의 중심에 있다. 다음과 같은 두 유형의 뷰 컨트롤러가 존재한다.

  • Content view controller: 앱 컨텐츠의 개별 부분을 맡으며 개발자가 만드는 가장 주요한 타입의 컨트롤러.
  • Container view controller: 다른 뷰 컨트롤러를 관리하고 뷰 컨트롤러 간 이동을 원할하게 또는 컨텐츠를 보여주는 것을 뷰 컨트롤러마다 달리한다.

대부분의 앱은 이 두 가지 형태의 뷰 컨트롤러가 혼합된 형태이다.

View Management

뷰 컨트롤러의 가장 중요한 역할은 뷰 계층도 관리이다. 모든 뷰 컨트롤러는 컨텐츠를 담고 있는 단일 루트 뷰를 가지고 있는데 이 루트 뷰에 컨텐츠를 표현하는 다른 뷰를 더한다. 뷰 컨트롤러는 항상 루트 뷰의 참조를 유지하고 각각의 뷰는 그 아래 subview들에 대한 강한 참조를 유지한다.

뷰 컨트롤러 계층에서 뷰에 접근할 때 보통 outlet을 사용하는데 이 outlet은 스토리보드로부터 뷰가 로드될 때 자동으로 뷰에 연결된다.

컨테이너 뷰 컨트롤러는 자식들의 컨텐츠를 관리하지 않는다. 다만, 컨테이너의 디자인에 따라 루트 뷰의 사이즈와 위치를 관리할 뿐이다. Figure 1-2는 Split 뷰 컨트롤러가 자식 뷰들의 사이즈와 위치를 정해주는 모습을 보이고 있다.

Data Marshaling

뷰 컨트롤러는 뷰와 데이터 간의 중재자 역할을 한다. UIViewController를 상속하고 데이터를 관리하는 변수를 설정하는 것으로 데이터를 관리한다.

뷰 컨트롤러와 데이터 객체 사이는 항상 분명한 책임의 나눔이 있어야 한다. 자료 구조의 무결성을 확인하는 대부분의 로직은 데이터 객체 자체에 속해야 한다. 뷰 컨트롤러는 뷰로부터 오는 입력 값의 유효성을 검사하고 데이터 객체에서 요구하는 포맷으로 포장하는 역할을 한다. 그러나 뷰 컨트롤러가 실제 데이터를 관리하는 역할은 최소화해야 한다.

UIDocument 객체가 뷰 컨트롤러와 데이터를 분리하여 관리하는 한 방법이 된다. 도큐먼트 객체는 저장소의 데이터를 읽고 쓰는 방법을 제공하는 컨트롤러 객체다.

UIDocument

앱의 데이터를 관리하는 추상 클래스로 도큐먼트를 사용함으로써 얻을 수 있는 이점은 다음과 같다.

  • 백그라운드 큐에서 비동기적으로 데이터를 읽거나 쓰기가 가능. 반응성이 좋아진다.
  • 도큐먼트 파일의 읽기나 쓰기가 클라우드 서비스에 자동으로 결합됨.
  • 다른 버전의 도큐먼트의 충돌 감지를 지원.
  • 임시 파일에 먼저 저장하고 이후 실제 파일을 대체함으로써 안전한 저장이 가능.
  • 자동 저장 기능.

User Interactions

뷰 컨트롤러는 리스폰더 객체기 때문에 리스폰더 체인을 따라 내려오는 이벤트를 처리할 수 있다. 하지만 대부분 뷰가 터치 이벤트를 처리한다. 뷰는 터치를 받고 일반적으로 뷰 컨트롤러가 맡는 타겟 객체나 델리게이트에게 이 결과를 전달한다.

Target-Action 패턴

타겟-액션 패턴은 객체가 이벤트가 일어날 시에 다른 객체에게 보내는 데 필요한 정보를 보유하는 패턴으로 다음과 같은 정보를 가지고 있는다.

  • Action selector: 실행될 메서드를 식별.
  • Target: 메시지를 받을 객체.
1
2
3
4
5
myButton.addTarget(self, action: #selector(doSomething), for: .touchUpInside)
...

@objc func doSomething() {...}
@IBAction func doSomething() {...}

@objc를 명시: Swift-Evolution - Limiting @objc inference

액션 메시지를 발생시키는 이벤트나 메세지를 보내는 객체는 아무것이나 될 수 있다. 그러나 일반적으로 UIContol(iOS)이나 NSControl(OS X)의 하위 클래스가 이벤트를 발행한다.

Resource Management

뷰 컨트롤러는 그것이 생성한 뷰와 객체들에 대해 필요 없을 시에 메모리에서 해제할 책임을 가지고 있다.

시스템이 여유 메모리가 별로 없을 때 UIKit은 앱이 필요없는 자원을 비우기를 요청하는데, 그 중 하나가 didReceiveMemoryWarning 메서드를 호출하는 것이다. 이 메서드에서 더 이상 필요없는 객체나 쉽게 재생성할 수 있는 것들을 메모리에서 해제하면 된다.

Adaptivity

iPad와 iPhone 등 다양한 사이즈에 대응하기 위해 다양한 뷰 컨트롤러를 만들기보다 한 뷰 컨트롤러를 다양한 요구에 맞추는 것이 좋다.

변형에는 두 가지가 있다.

Coarse-grained

뷰 컨트롤러의 trait의 변경이 일어날 때. 디스플레이 스케일 같은 전체적인 환경의 변화를 뜻한다.

Fine-grained

어느 때나 일어날 수 있는 사이즈 변경을 뜻하며 유저가 기기를 회전시키는 것이 해당됨.

UITraitCollection

iOS 인터페이스 환경을 나타내는 클래스로 가로, 세로 사이즈 클래스, 디스플레이 스케일, 유저 인터페이스 idiom trait 등으로 이루어진다.

  • Size class: compactregular로 표현되는 enum 타입으로 기기마다 다르다.
  • Display scale: CGFloat으로 표현되는 값으로 1.0은 비레티나, 2.0은 레티나 디스플레이를 나타낸다.
  • User interface idiom: phone, pad, tv, carPlay로 표현되는 enum 타입으로 인터페이스 타입을 나타낸다.

참고: PacaLog - UITraitCollection 공부

The View Controller Hierarchy

UIKit은 뷰 컨트롤러들이 미리 정해진 방법대로 사용되기를 원한다. 적절한 뷰 컨트롤러 간의 관계를 유지하는 것이 특정 뷰 컨트롤러가 필요할 때 정확한 특정 뷰 컨트롤러를 전달함에 있어 중요하다.

The Root View Controller

루트 뷰 컨트롤러는 뷰 컨트롤러 계층도의 앵커이다. 모든 윈도우는 컨텐츠를 표현하는 하나의 루트 뷰 컨트롤러를 가진다. 윈도우는 보일 수 있는 컨텐츠를 보유하지 않기 때문에 뷰 컨트롤러가 컨텐츠를 제공한다.

루트 뷰 컨트롤러는 UIWindow 객체의 rootViewContoller 프로퍼티로 접근이 가능하다. 스토리보드를 사용해 뷰 컨트롤러를 설정할 경우 자동으로 UIKit이 루트 뷰 컨트롤러를 설정해주지만 윈도우를 직접 코드로 만드는 경우 루트 뷰 컨트롤러를 설정해 주어야 한다.

참고: PacaLog - UIWindow 공부

Continer View Controllers

컨테이너 뷰 컨트롤러는 더욱 관리하기 쉽고 재사용이 가능한 조각들로 복잡한 인터페이스를 만들 수 있게 한다. 이는 자식 뷰 컨트롤러를 가지며 UINavigationController, UISplitViewController, UIPageViewController가 이러한 종류에 해당한다.

컨테이너 뷰 컨트롤러도 물론 다른 컨테이너의 자식이 될 수 있다. 또 자식 뷰 컨트롤러는 컨테이너와 다른 자식 뷰 컨트롤러에 대해 최소한의 정보만 가진다.

다음 그림은 컨테이너 뷰 컨트롤러가 루트 뷰 컨트롤러로서 역할을 하는 모습을 보여주고 있다.

Presented View Controllers

뷰 컨트롤러를 Presenting하는 행위는 전의 뷰 컨트롤러의 컨텐츠를 가리면서 새 컨텐츠로 대체하는 행위로 주로 Modally(모달)하게 띄우는 데 사용된다.

모달을 띄우는 뷰 컨트롤러는 Presenting view controller, 띄워지는 Presented view controller로 불리며 프로퍼티로 접근이 가능하다.

모달 형식은 유저가 특정 작업을 완료할 때까지 특정 뷰에 주의를 집중시킬 때 사용한다. Action sheets, Alerts, Acitvity views가 여기에 해당된다. 주의를 집중시키는 만큼 다음과 같은 유의 사항이 존재한다.

  • 모달의 사용은 최소화해야한다.
  • 모달 작업에서 빠져나가는 명확하고 안전한 방법을 제공해야 한다.
  • 해야 하는 작업을 명확하게 표시해야 한다.
  • 적절할 형태의 모달 뷰 스타일을 제공한다.

참고: Human Interface Guidelines - Modality

모달을 띄우는 방식은 개발자가 임의로 정해줄 수 있으며 적절하게 바꿔주면 된다.(추후에 나옴)

Design Tips

다음과 같은 가이드라인을 따름으로써 시스템에서 기대하는 자연스러운 뷰 컨트롤러의 사용을 제공할 수 있다.

Use System-Supplied View Controllers Whenever Possible

iOS에서 기본으로 제공하는 뷰 컨트롤러를 사용할 수 있다면 사용하는 것은 개발 시간을 단축시킬 뿐 아니라 유저에게 일관성 있는 경험을 줄 수 있다.

시스템에서 기본 제공하는 뷰 컨트롤러의 뷰 계층도를 절대로 바꾸면 안 된다. 임의로 바꾸는 행위는 중대한 버그를 일으킬 수 있다.

Make Each View Controller an Island

뷰 컨트롤러는 다른 내부 작업이나 뷰 컨트롤러에 대해 알 필요가 없다. 만약 다른 뷰 컨트롤러와 소통해야 한다면 프로토콜과 함께 사용하는 Delegation 패턴을 따르는 것이 좋다.

Use the Root View Only as a Container for Other Views

Know Where Your Data Lives

MVC 패턴 하에서 뷰 컨트롤러는 모델 객체와 뷰 객체 사이에서 데이터의 이동을 원활하게 하는 역할을 한다. 실제 데이터를 관리하는 책임은 모델 객체가 가지고 있어야 하며 뷰 컨트롤러 객체는 그저 정확한 데이터가 뷰에 보일 수 있게만 한다.

Adapt to Changes

앱은 다양한 iOS 기기에 대해 대응해야 한다. 다양한 뷰 컨트롤러를 만들 필요 없이 내장된 Adativity 도구를 사용하면 된다.

Reference