SwiftUI 윈도우, 씬 및 멀티 윈도우 아키텍처

발행: (2025년 12월 29일 오전 07:42 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

App vs Scene (The Most Important Distinction)

App
앱이 무엇인지 정의하고, 전역 설정을 소유하며, 씬을 생성합니다.

Scene
앱이 어떻게 표시되는지를 정의하고, 윈도우 수명 주기를 소유하며, 동시에 여러 번 존재할 수 있습니다.

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
        }
    }
}

Rule: 앱은 여러 씬을 가질 수 있으며, 각 씬은 여러 윈도우를 가질 수 있습니다.

What a WindowGroup Really Does

WindowGroup {
    ContentView()
}
  • iOS: 다중 앱 윈도우 (iPad)
  • macOS: 다중 윈도우
  • visionOS: 다중 공간 인스턴스

각 윈도우:

  • 자신만의 뷰 계층을 가짐
  • 자신만의 상태를 가짐
  • ViewModel을 자동으로 공유하지 않음

The Biggest Multi‑Window Bug

@StateObject var vm = GlobalViewModel()

WindowGroup 안에 @StateObject를 배치하는 것은 다음 이유 때문에 잘못되었습니다:

  • 각 윈도우가 새로운 인스턴스를 가짐
  • 윈도우 간에 상태가 분기됨
  • 네비게이션이 동기화되지 않음

Correct Global State Placement

Global state must live above the scenes:

@main
struct MyApp: App {
    @StateObject private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            RootView()
                .environmentObject(appState)
        }
    }
}

Now:

  • 모든 윈도우가 동일한 상태를 공유
  • 네비게이션이 일관됨
  • 데이터가 동기화된 상태 유지

Scene‑Local State vs. App‑Global State

Scene‑local:

  • 네비게이션 스택
  • 선택
  • 포커스
  • 스크롤 위치

App‑global:

  • 인증
  • 사용자 세션
  • 캐시
  • 기능 플래그
  • 딥 링크

절대 혼합하지 마세요.

ScenePhase Is Per‑Scene (Not Global)

@Environment(\.scenePhase) var scenePhase

각 윈도우는 자체적인 단계(phase)를 가지며, 이는 다음을 의미합니다:

  • 하나의 윈도우를 백그라운드로 전환해도 앱 전체가 백그라운드가 되는 것은 아님
  • inactive는 파괴(destroyed)와 동일하지 않음
  • active는 모든 윈도우가 포그라운드에 있는 것과 동일하지 않음

이 정보를 현명하게 활용하세요.

Supporting Multiple Scene Types

SwiftUI allows you to declare several scene roles:

var body: some Scene {
    WindowGroup("Main") {
        MainView()
    }

    WindowGroup("Inspector") {
        InspectorView()
    }

    Settings {
        SettingsView()
    }
}

전형적인 사용 사례:

  • 인스펙터 패널
  • 설정 윈도우
  • 보조 도구
  • 디버그 오버레이

Opening New Windows Programmatically

@Environment(\.openWindow) var openWindow

Button("Open Details") {
    openWindow(id: "details")
}

열릴 윈도우를 정의합니다:

WindowGroup(id: "details") {
    DetailView()
}

이 패턴은 데스크톱급 SwiftUI 앱이 추가 윈도우를 관리하는 방식입니다.

Window‑Scoped Dependency Injection

Each window should receive:

  • 자체 네비게이션 상태
  • 공유 서비스
  • 공유 앱 상태

Example:

WindowGroup {
    RootView(
        router: Router(),
        services: services
    )
}
  • services는 윈도우 간에 공유됩니다
  • router(또는 유사한 네비게이션 객체)는 윈도우당 하나씩 존재합니다

Testing Multi‑Window Behavior

When testing, verify that:

  • 여러 윈도우를 올바르게 열고 닫을 수 있음
  • 하나의 씬을 백그라운드로 전환해도 다른 씬에 영향을 주지 않음
  • 상태 복원이 윈도우당 작동함
  • 공유 상태 변경이 예상대로 전파됨

대부분의 SwiftUI 버그는 두 개 이상의 윈도우가 활성화될 때만 나타납니다.

Final Thoughts

SwiftUI scenes are architectural, not boilerplate. Understanding:

  • 앱과 씬 구분
  • 윈도우 정체성
  • 씬‑로컬 vs. 전역 상태
  • 멀티‑윈도우 동작

이를 통해 다음과 같은 느낌의 앱을 만들 수 있습니다:

  • 네이티브
  • 올바른
  • 확장 가능한
Back to Blog

관련 글

더 보기 »

SwiftUI 접근성 내부

접근성은 Parallel View Tree이다. SwiftUI는 두 개의 트리를 구축한다: - visual view tree - accessibility tree 이들은 관련이 있지만 동일하지 않다. 하나의…

SwiftUI에서 ScrollView와 좌표 공간

스크롤 기반 UI는 현대 앱 어디에나 존재합니다 - collapsing headers - parallax effects - sticky toolbars - section pinning - scroll‑driven animations - pull‑to‑r...