SwiftUI는 2019년 iOS 13과 함께 공개된 Apple의 선언형 UI 프레임워크다. 화면을 "어떻게 바꿀지"의 절차가 아니라 "지금 상태에서 어떤 모습이어야 하는지"를 기술하면, 프레임워크가 상태 변화와 화면 갱신을 연결한다. 문제는 이 편의가 공짜가 아니라는 점이다. 어떤 API를 쓸 수 있는지는 최소 배포 버전이 결정하고, 어떤 뷰가 갱신되는지는 프로퍼티 래퍼 선택이 결정한다. 여기서는 공식 문서에 명시된 사양과 제약을 기준으로 시작 단계에서 반드시 확인해야 할 것들을 정리한다.
선언형 UI의 기본 구조
View 프로토콜과 body
SwiftUI의 모든 화면 요소는 View 프로토콜을 채택한 구조체다. 클래스가 아니라 값 타입이며, 필수 요구사항은 연관 타입 Body를 반환하는 body 계산 프로퍼티 하나뿐이다. body에는 @ViewBuilder가 적용되어 있어 여러 뷰를 나열하면 자동으로 하나의 합성 타입으로 묶인다. 이 때문에 뷰의 실제 타입은 중첩이 깊어질수록 길어지고, 타입 검사 부담도 함께 커진다. 하나의 body에 수십 줄을 몰아넣으면 컴파일 시간이 급격히 늘어나거나 타입 추론 실패 메시지를 만나게 되는데, 이는 버그가 아니라 구조상 예상되는 결과다. 공식 문서가 권하는 대응은 단순하다. 뷰를 잘게 나누고, 반복되는 묶음은 별도 타입으로 추출한다.
앱 진입점과 Scene
iOS 14부터 App 프로토콜과 @main 속성으로 앱 진입점 전체를 SwiftUI로 작성할 수 있다. WindowGroup 같은 Scene이 최상위 컨테이너 역할을 하고, 그 안에 루트 뷰를 넣는다. iOS 13만 지원하는 프로젝트라면 이 방식을 쓸 수 없고 UIHostingController를 UIKit 앱 델리게이트에 얹는 형태가 된다. 즉 같은 SwiftUI라도 최소 지원 버전에 따라 앱의 골격 자체가 달라진다.
레이아웃 협상 규칙
SwiftUI의 레이아웃은 부모가 크기를 제안하고, 자식이 그 제안 안에서 자신의 크기를 스스로 정하고, 부모가 그 결과를 배치하는 3단계로 동작한다. 자식이 부모의 제안을 반드시 따르지 않는다는 점이 핵심이다. frame 수정자를 걸었는데 의도대로 되지 않는 대부분의 상황은 이 협상 순서를 오해한 결과다. 오토레이아웃처럼 제약을 선언하고 해결기를 신뢰하는 모델이 아니라는 점을 초반에 잡고 가야 한다.
상태 관리 — 프로퍼티 래퍼 선택 기준
소유와 참조의 구분
상태 관련 실수는 대부분 "이 값을 누가 소유하는가"를 잘못 정한 데서 나온다. 값을 직접 소유하는 뷰는 @State, 상위가 소유한 값을 읽고 쓰기만 하는 뷰는 @Binding을 쓴다. 참조 타입도 같은 원칙이 적용되는데, iOS 14에서 추가된 @StateObject는 뷰 갱신 사이에도 인스턴스 수명을 유지하지만 @ObservedObject는 그렇지 않다. 객체를 생성하는 위치에 @ObservedObject를 쓰면 상위 뷰가 다시 평가될 때마다 객체가 새로 만들어져 상태가 초기화된다. 화면이 이유 없이 리셋되는 증상의 전형적인 원인이다.
환경을 통한 전달
중간 뷰들을 거치지 않고 값을 내려보내야 할 때 @Environment와 @EnvironmentObject를 쓴다. 다만 @EnvironmentObject는 주입되지 않은 상태로 접근하면 런타임에 중단된다. 컴파일 타임 보장이 없다는 것이 명시된 제약이므로, 프리뷰나 테스트용 뷰를 만들 때 주입 누락을 반드시 확인해야 한다.
Observation 도입 이후
iOS 17부터는 Observation 프레임워크의 @Observable 매크로를 쓸 수 있고, 이 경우 ObservableObject·@Published·@StateObject 조합 대신 일반 프로퍼티 접근만으로 추적이 이루어진다. 실제로 접근한 프로퍼티만 추적하므로 불필요한 갱신이 줄어든다. 다만 iOS 16 이하를 지원해야 한다면 사용할 수 없다. 상태 관리 방식 선택은 취향이 아니라 배포 타깃이 먼저 결정한다.
시작 전 확인해야 할 제약
최소 배포 버전이 곧 API 범위
SwiftUI 학습에서 가장 자주 부딪히는 벽은 문법이 아니라 가용성이다. 튜토리얼 코드가 그대로 컴파일되지 않는다면 대개 그 API의 최소 버전이 프로젝트 설정보다 높기 때문이다. 주요 API의 도입 시점은 다음과 같다.
| 기능 | 최소 iOS 버전 | 비고 |
|---|---|---|
| 기본 뷰(List, TabView, NavigationView) | iOS 13 | NavigationView는 이후 대체됨 |
| App 프로토콜 기반 라이프사이클, @StateObject | iOS 14 | iOS 13은 UIHostingController 필요 |
| .task 수정자, .searchable | iOS 15 | async/await 연동 |
| NavigationStack, NavigationSplitView, Layout 프로토콜, Swift Charts | iOS 16 | 내비게이션 모델 전면 변경 |
| @Observable(Observation), SwiftData | iOS 17 | 상태·영속성 방식 변경 |
내비게이션은 특히 주의해야 한다. iOS 15 이하를 지원해야 하면 NavigationStack을 쓸 수 없어 구형 API와 신형 API를 if #available로 갈라 관리해야 하고, 두 경로의 동작 차이를 따로 검증해야 한다. 지원 범위를 한 단계 낮추는 결정이 코드량과 테스트 범위를 함께 늘린다는 뜻이다.
개발 환경 요건
Apple 개발의 전제 조건은 도구 쪽에도 있다. Xcode는 macOS에서만 동작하고, 프리뷰와 시뮬레이터도 여기에 묶여 있다. 사용 가능한 SDK와 시뮬레이터 런타임은 설치된 Xcode 버전이 정하므로, 최신 OS의 새 API를 쓰려면 Xcode부터 올려야 한다. 실기기 실행과 App Store 배포에는 Apple Developer Program 관련 요건이 따르며, 무료 개인 팀으로도 기기 실행은 가능하지만 유효 기간과 앱 수에 제한이 있다는 점이 공식 문서에 명시되어 있다. 요금과 정책은 변동될 수 있으므로 등록 전 개발자 사이트에서 직접 확인하는 편이 안전하다.
UIKit 상호운용
SwiftUI가 모든 것을 대체하지는 않는다. 세밀한 텍스트 편집 제어, 일부 카메라·미디어 인터페이스, 성숙한 서드파티 UI 라이브러리는 여전히 UIKit 기반인 경우가 많다. 이를 위해 UIViewRepresentable과 UIViewControllerRepresentable로 UIKit 컴포넌트를 감싸고, 반대 방향으로는 UIHostingController로 SwiftUI 뷰를 UIKit 화면에 넣는다. 양방향 통로가 공식 API로 제공된다는 사실은, 기존 iOS 앱을 한 번에 전환하지 않고 화면 단위로 점진 도입할 수 있다는 뜻이기도 하다.
SwiftUI, UIKit, 크로스플랫폼 비교
사양 비교
| 항목 | SwiftUI | UIKit | 크로스플랫폼 계열 |
|---|---|---|---|
| 대상 플랫폼 | iOS·iPadOS·macOS·watchOS·tvOS·visionOS | iOS·iPadOS 중심(Mac Catalyst) | Apple 플랫폼 + Android 등 |
| 언어 | Swift | Swift·Objective-C | Dart·JavaScript·Kotlin 등 |
| UI 작성 방식 | 선언형, 상태 기반 | 명령형, 뷰 계층 직접 조작 | 대체로 선언형 |
| 최소 지원 OS | iOS 13 이상(기능별 상향) | 사실상 제한 없음 | 프레임워크 정책에 따름 |
| 새 OS 기능 대응 | OS SDK와 동시 제공 | OS SDK와 동시 제공 | 브리지·플러그인 지원 대기 |
| 개발 환경 | macOS + Xcode 필수 | macOS + Xcode 필수 | 배포 단계에서 macOS + Xcode 필요 |
선택 기준
- 지원해야 할 최소 iOS 버전: 이것이 첫 번째 기준이다. iOS 17 이상만 지원한다면 Observation과 SwiftData를 전제로 설계할 수 있고, iOS 15까지 내려가면 내비게이션과 상태 관리 모두 구형 방식으로 맞춰야 한다. 기능 목록이 아니라 배포 타깃을 먼저 확정하는 순서가 맞다.
- 화면의 성격: 목록·폼·설정처럼 상태에 따라 구성이 바뀌는 화면은 SwiftUI가 코드량을 크게 줄인다. 반대로 픽셀 단위 커스텀 드로잉, 복잡한 제스처 조합, 대규모 컬렉션의 세밀한 스크롤 제어가 필요하면 UIKit 래핑을 전제로 계획해야 한다.
- Android 동시 지원 여부: SwiftUI는 Apple 플랫폼 전용이다. 하나의 UI 코드로 Android까지 덮어야 한다면 애초에 검토 대상이 아니다. 반대로 Apple 플랫폼만 대상이라면 여러 폼팩터를 같은 코드베이스로 확장하기 쉽다는 점이 크로스플랫폼 도구 대비 이점이 된다.
- 팀의 UIKit 자산: 기존 코드가 많다면 전면 재작성보다
UIHostingController를 이용한 화면 단위 도입이 현실적이다. 두 프레임워크의 공존은 공식적으로 지원되는 구성이다.
초반에 자주 걸리는 지점
ForEach의 식별자를 매번 새로 만들면(예: 렌더링 시점에 UUID 생성) 애니메이션과 내부 상태가 유지되지 않는다.Identifiable준수 또는 안정적인 키 지정이 전제 조건이다.@ViewBuilder블록은 받을 수 있는 하위 뷰 개수에 제한이 있어, 초과하면Group으로 묶거나 서브뷰로 분리하는 것이 표준 대응이다.- 프리뷰가 동작한다고 해서 실기기 동작이 보장되지 않는다. Dynamic Type 확대, 다크 모드, 접근성 설정, 세이프 에어리어가 다른 기기에서의 확인은 별도 작업이다.
어떤 경우에 적합한가
SwiftUI로 시작하기 좋은 조건
- 신규 프로젝트이고 최소 지원 버전을 iOS 16 또는 17 이상으로 잡을 수 있는 경우. 내비게이션·상태 관리·데이터 계층을 최신 API 하나로 통일할 수 있어 분기 비용이 없다.
- iPhone과 iPad, 나아가 Apple Watch나 Mac까지 같은 코드베이스로 확장할 계획이 있는 경우.
- 화면 대부분이 표준 컴포넌트로 구성되고, 접근성과 다국어 대응을 기본 동작에 맡기고 싶은 경우. Dynamic Type과 문자열 지역화가 기본 경로에 포함되어 있다.
- 기존 iOS 앱에 새 화면을 추가하면서 점진적으로 전환하려는 경우.
다른 선택지를 함께 검토해야 하는 조건
- Android를 동일 우선순위로 지원해야 하는 경우. Apple 개발 범위를 벗어나므로 크로스플랫폼 프레임워크 또는 로직 공유 방식이 먼저 검토 대상이다.
- 최소 지원 버전을 iOS 15 이하로 유지해야 하는 경우. 사용 가능한 API가 눈에 띄게 줄고 분기 코드가 늘어나므로, 이득이 실제로 남는지 화면 단위로 따져야 한다.
- 커스텀 렌더링, 고빈도 실시간 갱신, 정밀한 제스처 처리가 제품의 핵심인 경우. UIKit 기반 설계에 SwiftUI를 부분 도입하는 방향이 위험이 적다.
정리하면 SwiftUI 학습의 출발점은 문법이 아니라 두 가지 확정이다. 지원할 최소 iOS 버전과, 각 상태를 누가 소유하는지. 이 둘이 정해지면 나머지 API 선택은 대체로 따라온다. 버전별 가용성과 동작 세부는 계속 갱신되므로, 판단 근거는 Apple 개발자 문서의 해당 심볼 페이지에 표시된 가용성 정보에서 확인하는 것이 정확하다.