YunDaeHyeon
← 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으로 받음)