App essentials in SwiftUI

WWDC 2020을 통해 SwiftUI에선 AppScene 개념이 추가됐다. 이를 소개하고 있는 App essentials in SwiftUI 세션을 보고 간단히 정리해보았다.

새롭게 등장한 App, Scene 개념으로 UIKit 없이 순수 SwiftUI로만 앱을 만들 수 있게 됐다.

Views, scenes and apps

화면에 보이는 모든 뷰가 하나의 앱에 속하는 것이 아니기 때문에, 하나의 앱이 전체 화면에 대해 완벽히 제어할 수 없다. 나누어진 영역에서 앱이 보여지는 방법은 플랫폼이 제어한다. SwiftUI에선 이렇게 화면 안에 구분된 영역을 Scene이라 부른다.

윈도우는 화면에 보여지는 Scene의 컨텐츠를 보여주는 가장 흔한 방법이다. iPadOS와 같은 플랫폼은 다수의 윈도우를 나란히 보여줄 수 있다. iOS나 watchOS 그리고 tvOS는 각각의 앱에 대해서 하나의 꽉 찬 단일 윈도우를 선호한다. macOS는 Scene의 컨텐츠가 얼마나 다양한 방법으로 보여질 수 있는가를 나타내는 좋은 예다.

macOS에선 아래와 같이 다수의 윈도우로 개별 Scene을 보여주거나 탭으로 여러 Scene을 묶어 보여줄 수 있다.


이렇게 다수의 SceneApp을 구성하고 App, Scene 그리고 View는 하나의 계층 구조를 이룬다.

아래의 앱과 코드를 살펴보자.

ReadingListViewerViewScene의 한 종류인 WindowGroup에 속한다. 그리고 WindowGroupApp 프로토콜을 따르는 BookClubAppScene으로 사용된다. 코드에서 확인할 수 있는 계층구조와 우리가 위에서 살펴본 계층구조가 일치하는 것을 확인할 수 있다.

그리고 BookClubAppReadingListViewer가 선언된 코드 구조도 유사한 것을 확인할 수 있다.

  • AppView, 둘 모두 Data Dependency를 선언할 수 있다.
    • BoolClubApp - @StateObject
      • @StateObject는 이번에 새로 등장한 개념으로 이는 추후에 살펴보도록 하자
    • ReadingListViewer - @ObservedObject
  • AppView, 둘 모두 body 프로퍼티를 통해 사용자 인터페이스를 표시한다.
    • BookClubApp - var body: some Scene
    • ReadingListViewer - var body: some View

세션의 주제와 별개로 Swift 5.3부터 등장한 @main이 선언되어 있는 것을 확인할 수 있다. 이는 프로그램의 시작점을 의미한다. 기본적으로 스위프트 프로그램은 main.swift를 필요로 하는데 @main을 통해 App 프로토콜을 따르고 있는 구조체에 해당 책임을 위임할 수 있다.

Understanding Scenes

WindowGroup

WindowGroup을 통해 다수의 윈도우를 독립적으로 관리할 수 있다.

그리고 이렇게 독립된 윈도우는 서로 독립된 상태를 갖는데 이것이 SwiftUI에서 Scene의 가장 중요한 특징이라고 할 수 있다.

각각의 독립된 윈도우는 서로의 상태에 영향을 주지 않는다. App은 각 Scene이 사용할 수 있는 Shared Model을 제공할 수 있지만, 각 Scene의 뷰들의 상태는 서로 독립적이다.

그리고 위와 같이 앱 스위처에서 보여지는 타이틀을 뷰 변경자를 통해 윈도우별로 다르게 지정할 수 있다. 이는 부모 Scene의 상태에 영향을 줄 수 있는 변경자 중 하나이다.

macOS에선 WindowGroup을 사용해 아래의 기능들을 제공할 수 있다.

  • 다중 윈도우
  • 파일 메뉴에 새 윈도우 생성 메뉴 아이템 추가
    • 단축키 지원 (Command + N)
  • 윈도우 메뉴
    • 개별 윈도우를 위한 메뉴 아이템(윈도우 타이틀)
    • 다수의 윈도우를 하나의 탭 인터페이스로 통합하는 기능을 지원하는 메뉴 아이템

이 모든 것들은 부가적인 코드 없이 SwiftUI가 자동으로 지원하는 기능들이다.

Scene의 생명주기는 실행되고 있는 플랫폼에 의해 관리된다. macOS에선 새 윈도우가 필요하면 WindowGroup은 새 자식 Scene을 생성한다. 이처럼 macOS나 iPadOS와 같이 다중 윈도우를 지원하는 플랫폼에선 WindowGroup은 다수의 자식 Scene을 생성할 수 있다.

각각의 윈도우는 사용자 인터페이스 정의를 공유하지만 모두 독립된 상태를 갖는다. 그렇기 때문에 하나의 윈도우에서의 변화는 다른 윈도우에 영향을 주지 않는다.

플랫폼이 Scene 생명주기 관리에 책임이 있기 때문에, 각 뷰의 상태를 관리할 수 있는 새 프로퍼티 래퍼인 @SceneStorage라는 개념이 새로 등장했다.

이는 고유 키 값을 이용해 저장될 상태를 식별한다. 그리고 상태는 SwiftUI에 의해 적절한 타이밍에 저장되고 복원된다.

Customizing Apps

Document based App

지금까지 살펴본 BookClubApp은 Data-Driven 앱으로 Shared Model을 기반으로 하는 형태의 앱이었다.

이런 형태의 앱뿐만 아니라, 문서 기반의 앱도 존재한다. 이런 형태의 앱에선 DocumentGroup을 사용할 수 있다.

DocumentGroup은 열기, 편집, 저장과 같이 문서 기반의 앱을 관리하는데 필요한 기능을 제공하는 Scene의 한 종류다.

Preferences Window

설정 윈도우(Preferences Window)는 macOS 앱들이 제공하는 공통적인 기능 중 하나이다.

이를 위해 macOS에는 새로운 Scene 타입인 Settings 타입이 추가되었다. 이는 기본적인 설정 윈도우와 관련 단축키도 제공한다.

그리고 우린 기본 단축키 이외의 단축키도 새 변경자 API를 통해 지원할 수 있다.


해당 세션을 통해 앱과 관련하여 새로 추가된 기능 및 API들에 대해 간단히 알아볼 수 있었다. 길지 않은 세션이기 때문에 출,퇴근길에 간단하게 시청할 수 있는 세션이었다.

Specifying the Scenes Your App Support

WWDC19에서 iOS 13이 발표되고 새로운 것들이 다수 생겼다. 바인딩을 지원하는 Combine, 선언형 UI 방식으로 UI를 구현할 수 있는 SwiftUI 프레임워크까지 많은 것들이 나왔다.

이와 함께 앱 생명주기와 관련된 새 공식 문서가 등장했는데 여기서 Scene이라는 개념이 등장한다. Managing Your App’s Life Cycle 문서를 살펴보기 전에 Specifying the Scenes Your App Support 문서를 먼저 살펴보며 Scene의 등장에 대한 이유를 이해하고자 한다.

참고로 아직은 베타 문서라 추후에 내용이 변경되거나 사용되는 클래스, 프로토콜 그리고 메소드 등의 이름이 변경될 수 있다.

Overview


iOS 13과 그 이상에서는 사용자가 앱 UI의 사본을 여러 개 만들어 앱 스위처 내에서 서로 전환할 수 있다. iPad에서 사용자는 또한 앱 UI의 사본과 사본을 나란히 디스플레이할 수 있다. 각각의 앱 UI 사본에 대해 Scene 객체를 사용하여 UI를 화면에 띄우는 윈도우, 뷰 그리고 뷰 컨트롤러를 관리한다.

WWDC19 키노트에서는 이를 Multi-Window Capability라 설명하며 동일한 메모 앱 두 개를 나란히 실행시키는 모습을 보여주었다.

사용자가 새 Scene을 요청할 때, UIKit은 이에 해당하는 Scene 객체를 만들고 이것의 초기화 설정을 다룬다. 이를 위해 UIKit은 당신이 제공한 정보에 의존한다. 앱은 반드시 자신이 지원하는 Scene의 유형과 해당 Scene을 관리하는데 사용하는 객체를 선언해야 한다. 이 작업은 앱의 Info.plist에 정적으로 정의하거나, 런타임에 동적으로 정의할 수 있다.

중요

Scene을 앱에서 지원하는 것은 선택이지만, 앱의 UI 사본들을 동시에 보여주고 싶다면 반드시 지원해야 한다.

Enable Scene Support in Your Project Settings


앱은 앱의 구성 설정을 갱신하여 Scene을 명시적으로 선택해야 한다.

  1. Xcode 프로젝트를 연다.
  2. General Settings로 이동한다.
  3. Deployment Info 섹션에서 “Support multiple windows” 체크박스를 활성화한다.

멀티플 윈도우 옵션을 활성화하면 Xcode는 앱의 Info.plist 파일에 UIApplicationSceneManifest 키를 추가한다. 이 키의 존재는 시스템에게 당신의 앱이 Scene을 지원한다는 사실을 전달한다. 이 키의 값은 딕셔너리로 초기에는 오직 UIApplicationSupportsMultipleScenes 키만 포함하고 있는 상태다.

UIApplicationSupportMultipleScenes 키의 값은 당신의 앱이 실제로 동시에 여러 Scene을 지원하는지를 시스템에게 알린다. Xcode는 이 값을 기본적으로 true로 지정한다. 하지만 한 번에 하나의 Scene만 보여주고 싶으면 비활성화하면 된다. 멀티플 Scene을 지원하기 위해선 서로 다른 Scene들이 서로를 침범하지 않도록 방지하기 위한 추가 작업이 필요하다. 예를 들어 Scene에서 동일한 공유 데이터 구조를 사용하는 경우 앱 데이터 무결성을 유지하기 위해 해당 구조에 대한 접근을 조정해야 한다.

Configure the Details for Each Scene


UIKit은 당신이 제공한 정보를 사용해 앱의 Scene 생성을 담당한다. 가장 간단한 방법은 이 정보를 앱의 Info.plist를 사용하는 것이다.

  1. Xcode를 열고 Info.plist 파일을 선택한다.
  2. Application Scene Manifest 항목의 (+) 버튼을 누른다. 이 항목은 UIApplicationSceneManifest 키에 해당한다. 없는 경우 프로젝트 설정에서 위에서 언급한 대로 이를 추가하면 된다.
  3. 메뉴가 등장하면 Scene Configuration을 선택한다.
  4. Scene Configuration 항목에서 (+) 버튼을 클릭한다.
  5. 당신의 앱에 메인 Scene을 추가하기 위해 Application Session Role을 선택한다.
  6. 제공된 항목에 Scene의 상세 정보를 기입한다.

대부분의 앱은 오직 하나의 메인 Scene만 필요하지만 멀티플 Scene을 추가하고 각각을 다르게 구성할 수 있다. 예를 들어 알림과 관련된 컨텐츠를 보여주기 위한 두 번째 Scene을 포함시킬 수 있다. UIKit은 각 Scene에 대해 다음 정보를 필요로 한다.

  • UIWindowScene 클래스의 이름
  • 당신의 앱이 Scene을 관리하는데 사용하는 사용자 정의 델리게이트 객체 클래스의 이름. 이 클래스는 반드시 UIWindowSceneDelegate 프로토콜을 따라야 한다.
  • 앱에서 Scene을 내부적으로 식별하는데 사용하는 고유한 이름
  • Scene의 초기 UI를 포함하는 스토리보드의 이름. .storyboard 파일 확장자를 제외한 이름을 명시한다.

Scene을 구성하는데 필요한 정보 추가적인 정보는 UISceneConfigurations를 참고하라.

Create the Interface for Your Scene


스토리보드를 사용해 Scene의 UI를 지정한다. UISceneStoryboardFile 키에 지정한 스토리보드는 Scene을 보여주는데 사용되는 초기 뷰 컨트롤러를 포함한다. Scene 객체를 생성하는 것 외에도 UIKit은 Scene에 대한 윈도우를 생성하고 Scene의 스토리보드에서 초기 뷰 컨트롤러 지정한다. UIWindowSceneDelegate 객체의 메소드를 사용해서 코드로 이 뷰 컨트롤러를 교체할 수 있다.

중요

스토리보드의 초기 뷰 컨트롤러를 지정하는 것을 잊지 말아야 한다. UIKit는 UI를 구성할 때 이 뷰 컨트롤러의 존재에 의존한다.

Change Your Scene’s Configuration Dynamically


실제로 Scene 객체를 생성하기 전에 UIKit은 앱 델리게이트의 메소드인 application(_:configurationForConnecting:options:)를 호출하여 당신이 Scene과 연관된 상세 정보를 수정할 수 있도록 한다. 이 방법을 사용하여 UIKit에서 제공하는 옵션에 따라 Scene 구성을 조정할 수 있다. 예를 들어 시스템이 Scene에 알림 응답(Notification response)을 전달할 때 알림과 연관된 인터페이스와 함께 다른 스토리보드를 지정할 수 있다.

동적으로 Scene 구성을 구현하지 않으면 UIKit은 Scene을 생성하는데 Info.plist의 정보를 사용한다.

Adopt Scene-Based Life-Cycle Semantices


Scene에 대한 지원을 추가하면 앱이 생명 주기 이벤트에 대응하는 방식이 변경된다. Scene을 사용하지 않는 앱에선 앱 델리게이트 객체가 포그라운드 혹은 백그라운드로의 전환을 담당한다. 앱에서 Scene을 지원하게 되면 UIKit은 이러한 책임을 당신이 지정한 Scene 델리게이트 객체에 위임한다. Scene 생명 주기는 다른 Scene에 독립적이고, 앱 자체와도 독립적이다. 그러므로 당신이 지정한 Scene 델리게이트 객체가 이러한 전환을 담당해야 한다.

만일 앱이 iOS 12를 지원한다면 앱 델리게이트와 Scene 델리게이트 객체 모두에서 생명 주기 전환을 처리할 수 있다. UIKit은 오로지 하나의 델리게이트 객체에만 생명 주기 관련 이벤트 알림을 보낸다. iOS 13 이상에선 UIKit은 Scene 델리게이트 객체에 알림을 보내고, iOS 12 이하에선 UIKit은 앱 델리게이트에 해당 알림을 보낸다.

생명 주기 이벤트를 어떻게 다루는지에 대한 추가적인 정보는 Managing Your App’s Life Cycle를 참고하라.