SwiftUI Property Wrapper

2025. 1. 30. 16:40swift

  • @State
  • @Bingding
  • @StateObject
  • @ObservedObject
  • @EnvironmentObject
  • @Environment

@State

  • SwiftUI View 내부에서 사용되는 간단한 값 타입(Bool, String, Int 등)을 저장하고, 그 값이 변경될 때마다 뷰가 재렌더링되도록 하는 속성 래퍼.
  • 뷰 전체의 “상태(state)”라고 생각할 수 있으며, 해당 뷰에서만 유효합니다.
  • @State는 구조체 기반의 View에서 내부적으로 값을 감싸고 있습니다.
  • 값이 변경되면 자동으로 View가 새롭게 그려집니다.
  • 다른뷰에서는 관찰할 필요가 없는 경우에 사용합니다.
struct MyView: View {
    @State private var isSheetPresented: Bool = false

    var body: some View {
        VStack {
            Text("Hello, SwiftUI!")
                .onTapGesture {
                    isSheetPresented = true
                }
        }
        .sheet(isPresented: $isSheetPresented) {
            Text("Sheet View")
        }
    }
}

@Binding

  • 부모View가 소유한 상태(@State, 등)를 자식View에 참조 형태로 전달하기 위해 사용합니다.
  • 자식View에서 직접 상태를 소유하지않고, 부모View의 상태를 Binding 받아 동기화합니다.
  • 실제 값은 부모에 있고, 자식은 그 값에 대한 포인터를 갖습니다.
  • 자식 뷰에서 바인딩된 값을 변경하면, 부모의 상태도 자동으로 변경되어 상위 뷰가 업데이트 되고, 그에 따라 자식도 다시 랜더링 됩니다.
struct ParentView: View {
    @State private var count: Int = 0   
    var body: some View {
        ChildView(value: $count) // 자식 뷰로 바인딩 전달
    }
}
struct ChildView: View {
    @Binding var value: Int
    var body: some View {
        VStack {
            Text("Current value: \(value)")
            Button(action: {
                value += 1 // 부모 뷰의 count가 증가
            }) {
                Text("Increment")
            }
        }
    }
}

@StateObject

  • 클래스타입(ObservableOnject)을 뷰 내부에서 생성하고, SwiftUI가 해당 객체를 소유하며 라이프사이클을 관리할 수 있도록하는 래퍼
  • 뷰가 다시 그려져도 객체 자체는 다시 생성하지 않고 유지됩니다!
  • ObservableObject 프로토콜을 채택한 클래스를 View가 직접 소유할 때에 사용합니다(뷰모델의 경우!)
  • @StateObject 로 선언된 객체 @Published 프로퍼티가 바뀌면, 해당 객체를 구독(subscribe) 중인 뷰들이 자동으로 업데이트됩니다.
  • SwiftUI 초기 랜더링 시 객체를 초기화하고, 재렌더링 시에는 재사용합니다.
    • @ObservaedObject와 달리, 이 뷰 내부에서 새롭게 생성된 @ObservableObject한번만생성을 보장
class Counter: ObservableObject {
    @Published var value: Int = 0
}

struct CounterView: View {
    @StateObject private var counter = Counter()

    var body: some View {
        VStack {
            Text("Count: \(counter.value)")
            Button("Increment") {
                counter.value += 1
            }
        }
    }
}

@ObservedObject

  • ObservableObject 를 관찰하기 위한 래퍼이지만서도, 뷰 외부에서 전달된 객체를 추적할때에 사용합니다.
  • @Published 프로퍼티가 변경되면, 해당 뷰가 자동으로 업데이트 됩니다.
    • 즉, 부모 뷰(ParentView)가 @StateObject로 객체를 소유하고 있고, 자식 뷰(ChildView)는 @ObservedObject를 통해 이를 관찰하는 구조입니다.
    • ObservableObject 내부의 @Published 프로퍼티가 변경되면, 이를 관찰하는 모든 뷰(@ObservedObject를 가진 뷰)에서 다시 렌더링이 발생합니다.
  • 뷰가 새로 만들어질때 마다 @ObservedObject로 받는 객체가 새로 교체될 수 있습니다.
  • 뷰가 직접 생성하고 소유하는 것이 아니라, 보통 상위에서 주입받는 경우가 많습니다.
  • @StateObject와 달리, 뷰가 소유 하는 개념이 없으므로, 라이프사이클이 보장되지 않습니다.
class TimerModel: ObservableObject {
    @Published var time: Date = Date()
    // 타이머 동작 로직
}

struct ParentView: View {
    @StateObject private var timerModel = TimerModel()

    var body: some View {
        ChildView(timerModel: timerModel)
    }
}

struct ChildView: View {
    @ObservedObject var timerModel: TimerModel

    var body: some View {
        Text("Current time: \(timerModel.time)")
    }
}
  • ParentViewtimerModel을 소유 (@StateObject)하고, ChildViewtimerModel을 관찰 (@ObservedObject).
  • 인스턴스 생성해서 가지고 쓰지만 관찰하는입장에서느 변경될때에 뷰가 변경.

ObservedObject정리

- ParentView가 @StateObject를 통해 TimerModel을 직접 소유
- ChildView가 @ObservedObject로 TimerModel을 관찰
- @Published 프로퍼티(time)가 변경될 때마다 관찰하는 뷰(ChildView)가 다시 렌더링됨
- 하지만 ChildView가 재생성되면 timerModel 인스턴스는 유지됨 (부모가 소유하고 있기 때문)

 

@StateObject vs @ObservedObject 차이점 정리

속성 래퍼 역할 사용 예시 라이프사이클
@StateObject 뷰가 직접 소유하는 ObservableObject 부모 뷰가 ObservableObject를 관리해야 할 때 뷰가 처음 생성될 때 한 번만 인스턴스화됨
@ObservedObject 외부에서 주입받아 관찰하는 ObservableObject 부모로부터 ObservableObject를 받아 사용하는 자식 뷰 뷰가 새로 그려질 때마다 인스턴스 변경 가능

 

@EnvironmentObject

  • SwiftUIEnvironmentObservableObject를 주입하면, 해당 뷰 트리 전체에서 공유할 수 있다.
  • 자식 뷰 어디서든 @EnvironmentObject로 같은 객체를 자동 DI받게 됩니다.
  • APP 전체에서 공통으로 필요한 데이터나 설정을 저장하고, 여러 하위 뷰가 이를 공유할 때 유용합니다.
  • @EvironmentObject도 내부적으로는 ObservableObject를 구독하고 있으므로, 해당 객체의 @Published 프로퍼티가 변경되면 UI가 자동 업데이트 됩니다.
  • 선언부에서 타입만 지정하면 되고, 실제 인스턴스는 최상위뷰에서 .environmentObject(??)로 주입애햐 합니다.(environmentObject(viewModel))
class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
}

@main
struct MyApp: App {
    @StateObject private var settings = UserSettings()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(settings) 여기서 주입
        }
    }
}

struct ContentView: View {
    var body: some View {
        ProfileView()
    }
}

struct ProfileView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        Text("Hello, \(settings.username)")
    }
}
  • ProfileView에서 @EnvironmentObject var settings만 선언하면, MyApp에서 주입된 UserSettings 인스턴스를 자동으로 참조하게 됩니다.

@Environment

  • SwiftUI가 공급하는 기본환경값을 읽기 위해 사용하는 속성래퍼.(색상, 레이아웃방향, 디스플레이크기)
  • 키를 통해 시스템이 제공하거나 커스텀한 값에 접근할 수 있습니다.
  • @Environment와 달리 객체가 아닌 단일 환경 값에서 가져옵니다.
  • @Environment(.\colorsScheme)처럼 EnvironmentKey를 통해 값을 가져올 수 있습니다.
struct MyView: View {
    @Environment(\.colorScheme) var colorScheme

    var body: some View {
        Text("Current color scheme: \(colorScheme == .dark ? "Dark" : "Light")")
    }
}
간단하게 환경에서 현재 다크모드 여부를 가져와 UI에서 적용할 수 있습니다.

간단한 요약
@State: 뷰 내부에서 간단한 상태(값 타입)를 소유. 값이 바뀌면 해당 뷰 재렌더링.
@Binding: 부모의 @State나 다른 상태 객체를 참조하여, 자식 뷰에서 읽고 쓸 수 있게 함.
@StateObject: 뷰가 직접 소유하는 ObservableObject. 뷰의 라이프사이클에 맞춰 한 번만 생성되며, @Published 변경 시 뷰 업데이트.
@ObservedObject: 외부(부모 등)에서 전달되는 ObservableObject를 관찰. 뷰 자체가 객체를 소유하지 않음.
@EnvironmentObject: 환경(Environment)에 등록된 ObservableObject를 뷰에서 자동 주입받아 사용. 여러 하위 뷰가 공통 객체를 공유할 때 유용.
@Environment: SwiftUI가 제공하는 환경 값(colorScheme, locale 등)을 읽어오는 래퍼.

 

'swift' 카테고리의 다른 글

가치와 원칙에 대해서  (0) 2024.04.15
Swift에서 크래시안나게 하는 습관  (0) 2024.04.15
ARC  (2) 2024.01.08
ISP: 인터페이스 분리 원칙  (1) 2023.12.03
LLVM 에 대해서 알아보자.  (0) 2023.09.18