SwiftUI는 Apple 생태계의 모든 플랫폼에서 사용자 인터페이스를 구축하기 위한 선언형 프레임워크입니다. 단계를 명령형으로 설명하는 대신, 개발자는 인터페이스가 어떻게 보여야 하는지 선언하고 SwiftUI가 렌더링과 업데이트를 관리합니다. Apple Developer Documentation (2025)에 따르면 SwiftUI는 iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+ 및 tvOS 15+를 지원하며 모든 인터페이스 구성 요소의 기본 빌딩 블록으로 View Protocol을 사용합니다.
핵심 요약
body 속성은 SwiftUI UI 구성 요소의 기초이며, 뷰 구성을 통해 화면 설명을 반환합니다.SwiftUI는 2019년 Apple이 새로운 프로젝트에서 UIKit을 대체하기 위해 도입한 선언형 프레임워크입니다. UIView 인스턴스를 수동으로 생성하고 계층 구조에 추가하는 대신, 개발자는 View 프로토콜을 준수하는 구조체를 통해 인터페이스를 설명합니다. SwiftUI는 현재 상태와 새 상태 간의 차이를 자동으로 계산하고 자체 렌더링 엔진을 사용하여 변경된 부분만 다시 그립니다.
프레임워크는 값 의미 체계(클래스가 아닌 구조체)를 사용하여 Swift로 작성되어 UI 구성 요소를 가볍고 스레드 안전하게 만듭니다. Objective-C 런타임으로 인해 UIViewController가 200+ 바이트가 될 수 있는 UIKit과 달리, SwiftUI View는 단지 몇 바이트 크기의 단순한 구조체입니다. 이는 메모리가 제한된 watchOS에서 특히 중요합니다.
동일한 View 설명이 iPhone, iPad, Mac, Apple Watch, Apple TV 및 Apple Vision Pro에서 작동합니다. SwiftUI는 플랫폼에 따라 인터페이스를 조정합니다: iOS에서는 터치 제스처, macOS에서는 키보드 조합, watchOS에서는 Digital Crown 스크롤. 이는 여러 Apple 플랫폼에서 앱을 출시하는 회사의 개발 시간을 단축하지만 각 플랫폼별 요소에 대한 추가 구성이 필요합니다.
SwiftUI에서 각 화면은 View 프로토콜을 구현하는 구조체로, 유일한 요구 사항은 some View 유형의 계산된 속성 body입니다. some 키워드(불투명 유형)는 구체적인 뷰 유형을 숨겨 SwiftUI가 렌더링을 최적화할 수 있게 합니다. body 내에서 개발자는 ViewBuilder를 사용하여 미리 만들어진 구성 요소(Text, Image, Button, List)를 결합하여 여러 뷰를 하나로 조립합니다.
struct GreetingView: View {
let name: String
var var body: some View {
VStack {
Text("안녕하세요, \(name)!")
.font(.title)
.foregroundColor(.blue)
Image(systemName: "hand.wave")
.imageScale(.large)
}
.padding()
}
}
예제에서 VStack(수직 스택)은 Text와 Image를 포함합니다. name 값은 구조체 초기화자를 통해 전달됩니다. 이것이 SwiftUI에서 외부 DI 컨테이너 없이 DI(의존성 주입)가 작동하는 방식입니다. 각 수정자는 원본을 변경하지 않고 변경 사항이 적용된 새 뷰를 반환합니다. 이는 값 유형의 불변성 덕분에 가능합니다.
ViewBuilder는 @resultBuilder로 주석이 달린 결과 빌더로, 최대 10개의 뷰를 하나로 조립합니다. body 내에서 추가 래퍼 없이 if/else, switch 및 ForEach를 사용할 수 있습니다. ForEach는 Identifiable 요소와 함께 작동하며, 삽입/삭제 시 올바른 애니메이션을 위해 각 뷰에 고유 ID가 할당됩니다.
SwiftUI에서 상태는 화면에 표시되는 내용을 결정합니다. 상태가 변경되면 SwiftUI는 의존하는 뷰의 body를 다시 만들고 diff 알고리즘을 사용하여 결과를 이전 결과와 비교합니다. 상태 저장을 위해 속성 래퍼가 사용되며, 각각 로컬 상태, 자식 뷰와의 연결 또는 외부 데이터 모델이라는 특정 작업을 해결합니다.
struct CounterView: View {
@State private var count = 0
var var body: some View {
VStack {
Text("카운터: \(count)")
Button("증가") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var age = 0
}
@State는 View 구조체 내에 로컬 단순 값(Int, String, Bool)을 저장합니다. SwiftUI는 구조체에서 메모리를 별도 저장소로 이동하므로 View가 값 유형이더라도 @State 속성을 변경할 수 있습니다. @ObservableObject는 @Published 속성이 있는 클래스를 위한 것으로, 해당 변경 사항이 SwiftUI에 다시 그리기 필요성을 자동으로 알립니다.
@Binding은 부모 뷰에 있는 데이터 소스와의 양방향 연결을 만듭니다. 부모는 $variable(투영 값)을 전달하고 자식은 바인딩을 통해 값을 읽고 씁니다. 이를 통해 부모에 상태를 유지하면서 텍스트 입력 또는 토글을 별도 구성 요소로 이동할 수 있습니다. @Binding이 없으면 각 변경마다 새 값을 위로 전달하는 콜백 클로저가 필요합니다.
iOS 16 이전에는 SwiftUI의 네비게이션이 NavigationView를 기반으로 구축되었습니다 — iPad에서 복잡한 동작(분할 보기, 이중 열)을 가진 레거시 API입니다. iOS 16부터 Apple은 NavigationStack을 권장합니다 — 타입 안전한 라우트를 갖춘 간소화된 대안입니다. 개발자가 가능한 라우트의 열거형을 정의하면 NavigationStack이 딥 링크 및 루트로 돌아가기를 지원하여 화면 스택을 자동으로 관리합니다.
enum Route: Hashable {
case detail(id: Int)
case settings
}
struct ContentView: View {
var var body: some View {
NavigationStack {
List {
NavigationLink("세부 화면",
value: Route.detail(id: 42))
NavigationLink("설정",
value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
}
}
}
Hashable을 준수하는 라우트는 매개변수 전달에 모든 데이터 유형을 사용할 수 있습니다. navigationDestination(for:destination:)은 라우트 유형을 대상 뷰와 연결합니다. UIKit 네비게이션에 비해 장점은 새 라우트를 추가할 때 다시 그리기가 필요하지 않다는 것입니다 — 열거형에 케이스를 추가하고 switch에 핸들러를 추가하기만 하면 됩니다. 딥 링크는 NavigationStack의 processDeepLink를 통해 처리됩니다.
프로그래매틱 네비게이션(로그인, 타이머 또는 서버 응답 후)을 위해 NavigationLink 초기화자와 함께 @State가 사용됩니다: NavigationLink(isActive: $isActive). isActive = true로 설정하면 사용자 터치 없이 전환이 발생합니다. 다른 방법은 NavigationStack에서 $path 배열을 바인딩하는 것입니다: $path.append(Route.detail(id: 1)).
Modifier는 뷰의 수정된 복사본을 반환하는 메서드입니다. 기존 뷰를 변경하여 속성을 구성하는 UIKit과 달리, SwiftUI는 변경 사항이 적용된 새 값을 만듭니다. 수정자 체이닝은 순차적 변환(글꼴 → 패딩 → 색상 → 그림자 → 제스처)을 통해 최종 인터페이스를 구축합니다.
Apple은 200개 이상의 내장 수정자를 제공합니다. 가장 일반적인 것: .font(), .foregroundColor(), .padding(), .background(), .cornerRadius(), .shadow(), .opacity(), .offset(). 수정자의 순서가 중요합니다: .padding() 전에 .background()를 사용하면 패딩이 포함된 영역이 채워지고, 이후에 사용하면 내부 영역만 채워집니다. 사용자 정의 수정자는 ViewModifier 프로토콜을 통해 생성됩니다.
수정자는 삼항 연산자를 통해 조건부로 적용할 수 있습니다: .foregroundColor(isError ? .red : .primary). 애니메이션을 위해 .animation(.easeInOut, value: state)가 사용됩니다 — 애니메이션 수정자는 특정 상태 속성에 바인딩됩니다. 이 속성이 변경되면 SwiftUI는 이전 값과 새 값 사이의 전환을 애니메이션화합니다. 애니메이션은 opacity, offset, scale, rotation, 크기 및 색상과 함께 작동하며 각 속성에는 해당하는 AnimatableParameter가 있습니다.
사용자 정의 애니메이션의 경우 .transition(나타나기/사라지기) 및 .matchedGeometryEffect(두 컨테이너 간 요소의 부드러운 전환)을 사용할 수 있습니다. 후자는 목록의 히어로 애니메이션에 사용됩니다: 목록 셀의 아이콘이 세부 화면에서 큰 이미지로 부드럽게 변환됩니다.
SwiftUI와 UIKit 사이의 선택은 iOS 개발자의 첫 번째 딜레마 중 하나입니다. 두 프레임워크 모두 Apple에서 지원하지만 인터페이스 구축 문제를 근본적으로 다른 방식으로 해결합니다: SwiftUI는 선언형으로, UIKit은 명령형으로. 그 차이는 상태 관리, 네비게이션, 성능 및 호환성에서 나타납니다.
| 측면 | SwiftUI | UIKit |
|---|---|---|
| 접근 방식 | 선언형: 무엇을 표시할지 | 명령형: 어떻게 구축할지 |
| 상태 | Property Wrappers, 자동 다시 그리기 | 수동: reloadData, setNeedsLayout |
| UI 코드 | 간결함, 수정자 체인 | 장황함, NSCoder/Storyboard/제약 조건 |
| 성능 | iOS 17+에서 높음, diff 알고리즘 | iOS 12–16에서 최고, 직접 제어 |
| 최소 버전 | iOS 15+ (완전 지원) | iOS 2+ (모든 버전) |
최소 iOS 17 버전의 새 프로젝트의 경우 Apple은 SwiftUI를 기본 프레임워크로 권장합니다. UIKit은 렌더링에 대한 세밀한 제어(사용자 정의 UICollectionViewLayout, 복잡한 CAAnimation 장면) 또는 iOS 12–14 지원이 필요한 인터페이스에 계속 필요합니다. 많은 프로젝트가 하이브리드 접근 방식을 사용합니다: UIHostingController를 통해 SwiftUI를 UIKit 앱에 임베드하고, UIViewRepresentable을 사용하여 SwiftUI 계층 내에서 UIKit 구성 요소를 사용할 수 있습니다.
자주 묻는 질문
네, UIHostingController(SwiftUI in UIKit)와 UIViewRepresentable(UIKit in SwiftUI)을 통해 가능합니다. 이는 마이그레이션 중에 인기 있는 하이브리드 접근 방식입니다.
iOS 17은 완전한 기능을 제공합니다: NavigationStack, Observation framework, Swift Charts. iOS 15는 프로덕션을 위한 최소 기준입니다.
가장 일반적인 원인은 백그라운드 스레드에서 @Published 속성을 변경하는 것입니다. ObservableObject는 main actor에서 변경 사항을 전송해야 합니다: @MainActor class ViewModel.
Combine을 통해 .debounce를 사용하세요: Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main).
네, Gesture 수정자를 통해 지원합니다: DragGesture, LongPressGesture, MagnificationGesture, RotationGesture. .simultaneousGesture()와 .sequenced()를 사용하여 결합하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.