Displaying and managing views with a view controller

oto·2023년 8월 24일
0

이 전에는 SwiftUI를 주로 사용해서, UIKit에 대한 이해와 ViewController에 대한 이해가 필요하다고 생각했다. 그래서 차근차근 학습을 진행하려고 하고, View Controller에 대한 이해를 위해 Displaying and managing views with a view controller 의 Article을 살펴봤다.

Overview

MVC(model-view-controller) 디자인 체계에서, view controller는 화면에 정보를 표시하는 view 객체와 앱 컨텐츠를 저장하는 데이터 객체 사이에 위치한다.구체적으로 view controller는 뷰 계층 구조와 그 뷰들을 최신 상태로 유지하기 위해 필요한 상태 정보를 관리한다. UIKit 앱은 콘텐츠를 표시하기 위해 view controller를 중요하게 활용하며, 자주 custom view controller를 정의하여 view와 UI 관련 로직을 관리한다.

대부분의 custom view controller는 content view controller이다. 이는 view controller가 모든 뷰를 소유하고 해당 뷰들과의 상호작용을 관리한다. 콘텐츠 뷰 컨트롤러를 사용하여 앱의 사용자 정의 콘텐츠를 화면에 표시하고, 뷰 컨트롤러 객체를 사용하여 데이터를 사용자 정의 뷰로부터 전송하고 받는 관리를 한다.

Note
content view controller와 달리 container view controller는 다른 뷰 컨트롤러의 콘텐츠를 자체 뷰 계층에 통합한다. container view controller의 예로 UINavigationController가 있다. container view controller를 구현하는 방법은 Implementing a Custom Container View Controller를 참조할 수 있다.

content view controller를 정의하려면 먼저 UIViewController를 서브클래싱(subclassing)한다. 만약 인터페이스에 테이블 뷰나 컬렉션 뷰가 포함된다면 UITableViewController나 UICollectionViewController를 서브클래싱하는 것이 좋다. 새로운 Xcode 프로젝트를 만들면 수정할 수 있는 content view controller class가 포함되어 있고, 필요한 경우 더 추가할 수 있다.

view controller에 view 추가하기

UIViewController에는 뷰 계층의 루트 뷰로 작동하는 content view가 있으며, 이는 view 속성을 통해 접근할 수 있다. 이 루트 뷰에는 인터페이스를 표시하는 데 필요한 사용자 정의 뷰를 추가한다. 스토리보드에서는 뷰 컨트롤러 씬에 뷰를 끌어다 놓음으로써 뷰를 추가한다. 예를 들어 다음 그림은 iPhone에서 이미지 뷰와 버튼이 있는 뷰 컨트롤러를 보여준다.

뷰 컨트롤러에 뷰를 추가한 후에는 항상 Auto Layout 제약 조건을 추가하여 해당 뷰의 크기와 위치를 설정한다. 제약 조건은 각 뷰의 크기와 위치를 부모 또는 형제 뷰와 상대적으로 어떻게 설정할지를 지정하는 규칙으로, 이를 통해 뷰가 다양한 환경과 기기에 자동으로 적응할 수 있게 된다.

중요한 view에 대한 참조 저장

런타임 시에는 뷰 컨트롤러의 코드에서 뷰에 접근해야 할 수 있다. 예를 들어, 텍스트 뷰의 텍스트를 가져오거나 이미지 뷰의 이미지를 변경하려는 경우, 이를 위해서는 뷰 계층의 뷰에 대한 참조가 필요하다. 이러한 참조는 아울렛(outlets)을 사용하여 생성한다.

아울렛은 view controller 내에 있는 property로, IBOutlet 키워드가 포함된다. 이 키워드의 존재는 Xcode에 해당 프로퍼티를 스토리보드에서 노출시키라고 알려준다. 다음 예제 코드는 두 개의 아울렛 정의를 보여준다. Swift에서는 뷰 컨트롤러가 뷰 계층 자체에서 기인한 첫 번째 강력한 참조 이외에도 두 번째 강력한 참조를 보유하지 못하도록 weak 키워드를 포함시킨다.

@IBOutlet weak var imageView : UIImageView?
@IBOutlet weak var button : UIButton?

스토리보드에서는 각 아울렛을 해당하는 뷰에 연결한다. 이 과정은 "UI 개체에 메시지 보내기 위한 아울렛 연결 추가"에서 설명된 대로 진행한다. 모든 뷰 계층의 뷰에 대한 참조를 저장할 필요는 없고, 나중에 수정하는 뷰에 대한 참조만 저장한다.

뷰 컨트롤러를 인스턴스화할 때 UIKit은 스토리보드에서 구성한 모든 아울렛을 다시 연결한다. UIKit은 이러한 연결을 view controller의 viewDidLoad() 메서드를 호출하기 전에 다시 설정한다. 따라서 해당 메서드에서 해당 프로퍼티의 개체에 액세스할 수 있다. 프로그래밍적으로 뷰를 생성하는 경우 해당 뷰를 명시적으로 뷰 컨트롤러의 적절한 프로퍼티에 할당해야 한다.

Handle events occurring in views and controls

컨트롤들은 사용자 상호작용을 보고하기 위해 타겟-액션(target-action) 디자인 패턴을 사용하며, 일부 뷰는 변경 사항에 대응하여 알림을 게시하거나 델리게이트 메서드를 호출할 수 있다. 뷰 컨트롤러는 이러한 상호작용에 대해 알아야 하므로 뷰를 업데이트할 수 있어야 하며, 이를 수행할 수 있는 여러 가지 방법이 있다:

  • 뷰 컨트롤러 내에 델리게이트와 액션 메서드를 구현한다. 이 옵션은 간단하고 구현이 쉽지만, 유연성이 떨어지며 코드의 테스트와 검증이 어려워질 수 있다.

  • 뷰 컨트롤러의 클래스 익스텐션(class extension)에 델리게이트와 액션 메서드를 구현한다. 이 옵션은 이벤트 처리 코드를 뷰 컨트롤러의 나머지 부분과 분리하여 코드의 테스트와 검증을 쉽게 만들어준다.

  • 특정 객체에 델리게이트와 액션 메서드를 구현하고, 해당 객체가 관련 정보를 뷰 컨트롤러로 전달하도록 합니다. 이 옵션은 가장 유연성과 재사용성을 제공한다. 역할의 분리로 인해 단위 테스트 작성이 용이해진다.

컨트롤들과의 사용자 상호작용에 응답하기 위해, 다음 코드 목록에 표시된 서명 중 하나를 가진 액션 메서드를 정의한다. 메서드 정의 내에서 제네릭한 UIControl 참조를 더 구체적인 컨트롤 클래스로 대체할 수 있다.

@IBAction func doSomething()
@IBAction func doSomething(sender: UIControl)
@IBAction func doSomething(sender: UIControl, forEvent event: UIEvent)

Prepare your views to appear onscreen

UIKit은 화면에 표시되기 전에 뷰 컨트롤러와 뷰를 구성할 수 있는 여러 가지 기회를 제공합니다. 스토리보드에서 뷰 컨트롤러를 인스턴스화할 때, UIKit은 해당 객체를 init(coder:) 메서드를 사용하여 생성한다.

note
만약 뷰 컨트롤러가 코더 객체로 제공할 수 있는 것 이상의 사용자 정의 초기화가 필요한 경우, UIStoryboard의 instantiateInitialViewController(creator:) 메서드를 사용하여 프로그래밍 방식으로 인스턴스화할 수 있다. 이 메서드를 사용하면 블록과 UIKit에서 제공하는 코더 객체를 사용하여 직접 뷰 컨트롤러를 생성할 수 있다. 이 옵션을 사용하면 뷰 컨트롤러를 필요한 사용자 정의 데이터로 초기화하고, 여전히 스토리보드 내의 뷰와 다른 객체의 설정을 복원할 수 있다.

뷰 컨트롤러를 화면에 표시할 때 UIKit은 먼저 해당하는 뷰를 로드하고 구성해야 합니다. 이를 위해 다음과 같은 순서로 작업을 수행한다:

  1. 각 뷰를 해당 뷰의 init(coder:) 메서드를 사용하여 생성.
  2. 뷰들을 뷰 컨트롤러 내의 해당하는 액션 및 아울렛에 연결.
  3. 각 뷰와 뷰 컨트롤러의 awakeFromNib() 메서드를 호출.
  4. 뷰 계층 구조를 뷰 컨트롤러의 view 프로퍼티에 할당.
  5. 뷰 컨트롤러의 viewDidLoad() 메서드를 호출.

로드 시간에는 뷰 컨트롤러를 사용할 준비를 위해 필요한 일회성 설정 단계만 수행한다. 추가적인 뷰를 생성하고 구성하는 작업은 로드 시간을 활용하여 스토리보드에 포함되지 않은 뷰를 만들고 구성하는 데 사용한다. 뷰 컨트롤러가 화면에 표시될 때마다 수행되어야 하는 작업을 수행하지 않는다. 예를 들어, 애니메이션을 시작하거나 뷰의 값을 업데이트하는 작업을 로드 시간에 수행하지 않는다.

뷰가 화면에 처음 나타날 때 관련된 최종 뷰 작업을 수행한다. UIKit은 뷰 컨트롤러의 뷰가 화면에 나타날 때 해당 컨트롤러에 알림을 보내며, 다음과 같은 방식으로 해당 뷰의 레이아웃을 현재 환경에 맞게 업데이트한다:

  1. 전환 시작 시 viewWillAppear(_:) 호출됨
  2. 뷰를 계층에 추가함
  3. 뷰 컨트롤러와 해당 뷰의 트레잇(trait) 컬렉션을 업데이트함
  4. 뷰의 기하학적 요소를 업데이트함. 이에는 뷰의 크기 및 부모 뷰 내 위치가 포함됨. 레이아웃 여백과 안전 영역을 업데이트하며, 필요한 경우 viewLayoutMarginsDidChange()와 viewSafeAreaInsetsDidChange()를 호출함
  5. viewIsAppearing(_:) 메서드를 호출하여 뷰 컨트롤러의 뷰가 화면에 나타나고 있음을 알림
  6. viewWillLayoutSubviews() 메서드 호출됨
  7. 뷰 계층의 레이아웃을 업데이트함
  8. viewDidLayoutSubviews() 메서드 호출됨
  9. 뷰를 화면에 표시함
  10. 애니메이션이 완료된 후 viewDidAppear(_:) 메서드 호출됨

뷰 컨트롤러의 viewIsAppearing(:) 메서드에서 뷰의 내용을 업데이트한다. 시스템이 이 메서드를 호출할 때 이미 뷰를 뷰 계층에 추가하고 프레임, 바운드, 여백 및 인셋을 정의한 상태이다. 시스템이 viewIsAppearing(:)에서 추가한 콘텐츠는 뷰가 화면에 처음 표시될 때 표시된다.

시스템은 뷰가 레이아웃을 수행할 때마다 viewWillLayoutSubviews() 및 viewDidLayoutSubviews() 메서드를 호출하는데, 이는 뷰가 표시되는 동안 언제든지 발생할 수 있다. 시스템은 viewIsAppearing(_:)를 나타나는 트랜지션의 일부로 한 번만 호출하므로, 이 메서드에서 수행하는 변경 사항은 뷰가 레이아웃을 수행할 때마다 반복되지 않는다.

시스템이 viewIsAppearing(_:)을 호출할 때 뷰 컨트롤러와 해당 뷰의 트레잇(trait) 컬렉션이 최신 상태이다. 뷰 컨트롤러의 traitCollection 프로퍼티를 사용하여 현재 환경에 관한 정보(디스플레이 스케일 또는 세로 및 가로 크기 클래스와 같은)에 액세스할 수 있다.

profile
iOS Developer

0개의 댓글