← Lab
SwiftUI 상태 관리 프로퍼티 래퍼
스터디2025.08.18
- SwiftUI
- Swift
SwiftUI 상태 관리 프로퍼티 래퍼 가이드
@State
특징
- 뷰 내부에서 관리하는 단순한 상태값
- 값 타입(Struct, Enum, Int, String 등)에 적합
- 뷰가 데이터를 소유하고 관리
- private으로 선언하는 것이 권장됨
사용 시기
- 뷰 내부에서만 사용하는 간단한 UI 상태
- 토글, 텍스트 입력, 선택 상태 등
- 일시적이고 재사용이 필요 없는 데이터
예시
struct ContentView: View {
@State private var isOn = false
@State private var text = ""
@State private var count = 0
var body: some View {
VStack {
Toggle("스위치", isOn: $isOn)
TextField("입력", text: $text)
Button("카운트: \(count)") {
count += 1
}
}
}
}
@Binding
특징
- 다른 곳의 상태에 대한 양방향 참조
- 값 타입, 참조 타입 모두 가능
- 데이터를 소유하지 않고 참조만 함
- $ 접두사로 전달
사용 시기
- 부모 뷰의 @State를 자식 뷰로 전달
- 자식 뷰에서 부모의 상태를 변경해야 할 때
- 커스텀 컴포넌트 제작 시
예시
struct ParentView: View {
@State private var isOn = false
var body: some View {
ChildView(isOn: $isOn)
}
}
struct ChildView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("토글", isOn: $isOn)
}
}
@ObservedObject
특징
- 참조 타입(Class)에만 사용
- ObservableObject 프로토콜 준수 필요
- 뷰가 객체를 소유하지 않음
- 뷰가 재생성되면 객체도 새로 할당될 수 있음
사용 시기
- 외부에서 생성된 ObservableObject를 관찰
- 부모로부터 전달받은 뷰모델
- 여러 뷰에서 공유하지만 뷰가 소유하지 않는 데이터
예시
class ViewModel: ObservableObject {
@Published var count = 0
@Published var name = ""
}
struct ParentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
ChildView(viewModel: viewModel)
}
}
struct ChildView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
Text("Count: \(viewModel.count)")
}
}
@StateObject
특징
- 참조 타입(Class)에만 사용
- ObservableObject 프로토콜 준수 필요
- 뷰가 객체를 소유하고 생명주기 관리
- 뷰가 재생성되어도 객체는 유지됨
- iOS 14+
사용 시기
- 뷰가 직접 뷰모델을 생성하고 소유
- 뷰의 생명주기 동안 데이터를 유지해야 할 때
- 복잡한 비즈니스 로직이 포함된 상태
예시
class UserViewModel: ObservableObject {
@Published var username = ""
@Published var email = ""
func saveUser() {
// 저장 로직
}
}
struct ProfileView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
VStack {
TextField("Username", text: $viewModel.username)
TextField("Email", text: $viewModel.email)
Button("저장", action: viewModel.saveUser)
}
}
}
@EnvironmentObject
특징
- 참조 타입(Class)에만 사용
- ObservableObject 프로토콜 준수 필요
- 앱 전역 또는 뷰 계층 상위에서 주입
- 의존성 주입 패턴
사용 시기
- 여러 뷰에서 공유하는 전역 상태
- 사용자 인증 정보, 앱 설정 등
- 깊은 뷰 계층 구조에서 데이터 전달
예시
class AppSettings: ObservableObject {
@Published var isDarkMode = false
@Published var fontSize: Double = 16
}
@main
struct MyApp: App {
@StateObject private var settings = AppSettings()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(settings)
}
}
}
struct ContentView: View {
@EnvironmentObject var settings: AppSettings
var body: some View {
VStack {
Text("Font Size: \(settings.fontSize)")
Toggle("Dark Mode", isOn: $settings.isDarkMode)
}
}
}
struct NestedView: View {
@EnvironmentObject var settings: AppSettings
// 중간 뷰를 거치지 않고 바로 접근 가능
var body: some View {
Text("Settings available here too!")
}
}
비교 표
| 프로퍼티 래퍼 | 데이터 타입 | 소유권 | 생명주기 | 주요 사용처 | 전달 방식 |
|---|---|---|---|---|---|
| @State | 값 타입 (Struct, Enum, 기본형) | 뷰가 소유 | 뷰의 생명주기와 동일 | 뷰 내부의 단순 상태 | $ 접두사로 Binding 전달 |
| @Binding | 값/참조 타입 모두 | 소유 안함 (참조) | 원본 데이터의 생명주기 | 부모-자식 간 양방향 연결 | $ 접두사로 받음 |
| @ObservedObject | 참조 타입 (Class) | 소유 안함 | 외부에서 관리 | 외부에서 전달받은 뷰모델 | 일반 프로퍼티로 전달 |
| @StateObject | 참조 타입 (Class) | 뷰가 소유 | 뷰의 생명주기와 동일 | 뷰가 생성하는 뷰모델 | 일반 프로퍼티로 전달 |
| @EnvironmentObject | 참조 타입 (Class) | 상위 계층이 소유 | 주입한 범위 내 | 전역 공유 데이터 | .environmentObject() |
선택 가이드
값 타입 데이터라면
- 뷰 내부 상태 → @State
- 부모-자식 전달 → @Binding
참조 타입 데이터라면
- 뷰가 생성하고 소유 → @StateObject
- 외부에서 전달받음 → @ObservedObject
- 여러 뷰에서 공유 → @EnvironmentObject
일반적인 패턴
App (@StateObject 생성)
↓ .environmentObject()
ParentView (@EnvironmentObject 접근)
↓ 프로퍼티 전달
ChildView (@ObservedObject로 받음)
↓ $binding 전달
GrandchildView (@Binding으로 받음)