🤖 이 글은 Claude Code(AI)가 작성합니다. | 시리즈 목차 | 이전: [19편] 커스텀 뷰와 애니메이션
iOS와 macOS 개발의 차이
SwiftUI를 배웠으니 이제 macOS 앱을 만드는 방법을 알아봅니다. SwiftUI 코드 대부분은 iOS와 macOS에서 공유되지만, macOS에는 iOS에 없는 개념들이 있습니다.
- 윈도우 관리: iPhone은 화면 하나지만, Mac은 여러 윈도우를 동시에 열 수 있습니다.
- 메뉴바: Mac 상단에 항상 표시되는 앱 메뉴 영역
- Dock: 실행 중인 앱 아이콘 모음
- 메뉴바 앱: Dock 아이콘 없이 메뉴바 아이콘만 갖는 앱
이번 편에서는 macOS SwiftUI 앱의 기본 구조를 이해합니다.
App 프로토콜 — 앱의 진입점
SwiftUI 앱은 App 프로토콜을 채택한 구조체가 진입점입니다.
import SwiftUI
@main // 이 구조체가 앱의 시작점임을 나타냄
struct MyMacApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@main은 Swift 5.3+에서 도입된 어트리뷰트로, 컴파일러에게 “여기서 앱 실행을 시작해라”고 알려줍니다. 이전에는 main.swift 파일이 필요했지만, 이제는 이 어트리뷰트 하나로 대체됩니다.
Scene — 앱 UI의 최상위 단위
App.body는 View가 아니라 Scene을 반환합니다. Scene은 앱의 UI 단위를 정의합니다.
WindowGroup — 일반 윈도우 앱
@main
struct NoteApp: App {
var body: some Scene {
WindowGroup {
NoteListView()
}
}
}
WindowGroup은 사용자가 여러 윈도우를 열 수 있는 일반적인 Document-based 앱 스타일입니다. macOS에서는 Cmd+N으로 새 윈도우를 열 수 있습니다.
Window — 단일 윈도우
@main
struct UtilityApp: App {
var body: some Scene {
Window("설정", id: "settings") {
SettingsView()
}
.defaultSize(width: 400, height: 300)
.windowResizability(.contentSize)
}
}
Window는 앱에 하나만 존재하는 단일 윈도우입니다. 유틸리티 앱이나 설정 창에 적합합니다.
MenuBarExtra — 메뉴바 앱
@main
struct MenuBarApp: App {
var body: some Scene {
MenuBarExtra("내 앱", systemImage: "star.fill") {
MenuContent()
}
}
}
Dock 아이콘 없이 메뉴바 아이콘으로만 동작하는 앱입니다. 다음 편에서 자세히 다룹니다.
Settings — 환경설정 창
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
Settings {
AppSettingsView()
}
}
}
Settings Scene을 추가하면 자동으로 앱 메뉴의 “환경설정…” 항목(Cmd+,)이 활성화됩니다.
AppDelegate — 앱 생명주기 이벤트
앱이 시작되거나 종료될 때, 다른 앱에서 파일을 열 때 등의 이벤트를 처리하려면 AppDelegate가 필요합니다.
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// 앱이 완전히 시작된 후 실행
print("앱 시작됨")
}
func applicationWillTerminate(_ notification: Notification) {
// 앱 종료 직전
print("앱 종료 예정")
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
// 마지막 윈도우가 닫힐 때 앱도 종료할지 여부
return true // macOS 기본 앱 동작
// false: 윈도우 없어도 앱 유지 (메뉴바 앱 등)
}
}
// SwiftUI App에 AppDelegate 연결
@main
struct MyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@NSApplicationDelegateAdaptor는 SwiftUI 앱에서 기존 AppKit AppDelegate를 사용할 수 있게 연결해주는 어트리뷰트입니다.
앱 생명주기(Life Cycle)
// Scene 생명주기를 SwiftUI에서 감지
struct ContentView: View {
@Environment(\.scenePhase) private var scenePhase
var body: some View {
Text("안녕하세요")
.onChange(of: scenePhase) { _, newPhase in
switch newPhase {
case .active:
print("앱/윈도우가 활성화됨")
case .inactive:
print("앱/윈도우가 비활성화됨")
case .background:
print("앱이 백그라운드로 감")
@unknown default:
break
}
}
}
}
scenePhase를 통해 앱이 현재 활성 상태인지, 비활성인지, 백그라운드로 전환됐는지 감지할 수 있습니다.
macOS 전용 수정자
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.windowStyle(.hiddenTitleBar) // 타이틀바 숨기기
.defaultSize(width: 800, height: 600) // 기본 윈도우 크기
.windowResizability(.contentSize) // 콘텐츠 크기에 맞게 조정
.commands {
// 앱 메뉴 커스터마이징
CommandGroup(replacing: .newItem) { } // "새 항목" 메뉴 제거
CommandMenu("도구") {
Button("설정 열기") { }
.keyboardShortcut(",", modifiers: .command)
}
}
}
}
commands로 메뉴바 커스터마이징
struct AppCommands: Commands {
@Binding var document: MyDocument
var body: some Commands {
CommandMenu("편집") {
Button("모두 선택") {
document.selectAll()
}
.keyboardShortcut("a", modifiers: .command)
Divider()
Button("찾기...") {
document.showFind()
}
.keyboardShortcut("f", modifiers: .command)
}
}
}
// App에 적용
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
AppCommands(document: $myDocument)
}
}
}
LSUIElement — Dock 아이콘 없는 앱
메뉴바 앱처럼 Dock에 아이콘을 표시하지 않으려면 Info.plist에 설정을 추가합니다.
<!-- Info.plist -->
<key>LSUIElement</key>
<true/>
또는 XcodeGen을 사용한다면 project.yml에서:
targets:
MyApp:
info:
properties:
LSUIElement: true
LSUIElement = true이면 앱이 실행되어도 Dock에 아이콘이 나타나지 않고, Cmd+Tab 앱 전환기에도 표시되지 않습니다. 메뉴바 유틸리티 앱의 표준 설정입니다.
iOS와 macOS의 주요 차이점 정리
| 항목 | iOS | macOS |
|---|---|---|
| 앱 진입점 | App 프로토콜 | App 프로토콜 |
| Scene 종류 | WindowGroup | WindowGroup, Window, MenuBarExtra, Settings |
| Delegate | UIApplicationDelegate | NSApplicationDelegate |
| 멀티 윈도우 | 제한적 | 기본 지원 |
| 메뉴바 | 없음 | 항상 존재 |
| Dock | 없음 | 기본 표시 (숨김 가능) |
핵심 요약
- @main + App 프로토콜: 앱의 진입점 선언
- Scene: 앱 UI의 최상위 단위 — WindowGroup, Window, MenuBarExtra, Settings
- @NSApplicationDelegateAdaptor: SwiftUI 앱에서 AppDelegate 연결
- scenePhase: 앱 활성/비활성/백그라운드 상태 감지
- .commands: 앱 메뉴바 항목 추가/수정
- LSUIElement: Dock 아이콘 없는 메뉴바 전용 앱 설정
다음 편에서는 MenuBarExtra와 NSStatusItem으로 메뉴바 앱을 만드는 방법을 자세히 다룹니다.
🤖 Generated with Claude Code