[Swift] @State @Observable @Environment
[Swift] @State @Observable @Environment
SwiftUI 의 상태 프로퍼티를 사용하면 기본 데이터의 변경에 따른 처리 코드를 작성하지 않아도 뷰가 업데이트 된다.
@State
상태에 대한 가장 기본적인 형태, 다음과같은 뷰 레이아웃의 상태를 저장하기 위해서만 사용
- 사례 : 토글 버튼 활성 여부
- 텍스트 필드 입력값
- 피커 뷰의 현재 선택
String이나 Int 값처럼 간단한 데이터 저장하기 위해 사용
- 상태 값은 해당 뷰에 속한것이라서 private 로 선언
- 상태 프로퍼티에 대한 바인딩 사용해서 조작
1
2
3
4
5
6
7
8
9
struct ContentView: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
TextField("Enter user name", text: $userName)
}
}
userName에 변화가 생길때마다 뷰 계층 구조는 SwiftUI에 의해 다시 랜더링 됨
@Observable
- 상태 프로퍼티는 일시적이라 부모 뷰가 사라지면 그 상태도 사라짐
- 반면 observable은 다른 외부 뷰에서 접근 가능한 지속적인 데이터
- 사례 : 여러 뷰에서 상태를 동기화(공유데이터)
Enviroment
- 앱 전체에서의 전역상태를 참조할때 사용
- 사례 : 앱 전체 테마, 설정
- 구조 : @Environment(어떤 클래스의 모델인지참조해줘여됨.self)
예제
1
2
3
4
@Observable
class ThemeModel {
var isDarkMode = false
}
1
2
3
4
@Observable
class CounterModel {
var count = 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct CounterView: View {
@Environment(ThemeModel.self) var theme // 전역 상태 참조
@Environment(CounterModel.self) var counter // 공통 데이터 참조
@State private var showSettings = false // 로컬 상태 관리
var body: some View {
VStack {
// 전역 테마 적용
Text("Dark Mode: \(theme.isDarkMode ? "On" : "Off")")
.padding()
.background(theme.isDarkMode ? Color.black : Color.white)
.foregroundColor(theme.isDarkMode ? Color.white : Color.black)
// 공통 데이터 사용
Text("Count: \(counter.count)")
.font(.largeTitle)
.padding()
// 로컬 상태 사용
Toggle(isOn: $showSettings) {
Text("Show Settings")
}
.padding()
// 공통 데이터 업데이트
Button("Increment") {
counter.count += 1
}
.padding()
.background(theme.isDarkMode ? Color.gray : Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.padding()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@main
struct MyApp: App {
@StateObject private var themeModel = ThemeModel()
@StateObject private var counterModel = CounterModel()
var body: some Scene {
WindowGroup {
CounterView()
.environment(themeModel) // 전역 테마 상태 주입
.environment(counterModel) // 공통 데이터 주입
}
}
}
내가이해한부분
- @State는 그 뷰에서만 사용. 즉 showSettings가 바뀌면 counterView가 새로고침됨
- @Enviromnet 변수인 theme,counter는 값이 변하면 그 부분의 view만 새로고침됨. 예를 들면 Text(background(theme.isDarkMod)) 부분만.
근데 이 값이 변하는걸 어떻게 아냐? class 선언한 부분을 보면 @Observable을 이용하여 값을 추적하는거임.
즉, @Environment가 참조하는 클래스는 번드시 @Observable를 가지고있어야 된다
오류 해결 깨닳은 점
@Environment는 부모뷰에서 하위뷰로 전달됨.
1
2
3
4
@Observable
class ModelData {
var landmarks: [Landmark] = load("landmarkData.json")
}
1
2
3
4
5
6
7
8
9
10
struct ContentView: View {
var body: some View {
LandmarkList()
}
}
#Preview {
ContentView()
.environment(ModelData()) // <- 부모에서 환경변수 전해줘야 자식에서도 사용가능
}
1
2
3
4
5
6
7
8
9
10
11
12
struct LandmarkList: View {
@Environment(ModelData.self) var modelData
var body: some View {
}
}
#Preview {
LandmarkList()
.environment(ModelData())
}
- 마지막 LandmarkList 에서 modelData는 class modelData에서 @Environment, @Observable 로 연결되어있으니까 바로 데이터를 가져온다고 생각했음.
- 오류나는 이유는 “부모 : ContentView -> 자식 : LandmarkList” 이기 때문에 ContentView에서 .environment(ModelData())를 받아와야 자식 뷰들도 ModelData 전부 사용가능임
This post is licensed under CC BY 4.0 by the author.