SwiftUI를 공부하면 가장 헷갈리는 부분이 데이터 관리/흐름이었다. 사실 제대로 이해만 한다면 쉬운 개념인 것 같다. 문제는 사탕 먹듯 까먹어버리기 때문... 이건 State였나 Binding이었나.. 이 두 가지라면 쉽겠지만.. StateObject, ObservedObject, EnvironmnetObject... 뭐가 이렇게 많은지ㅎㅎ 처음 공부하면서 아이패드에 정리했던 부분을 다시 정리해서 "나"도 보고 여러분도 보시라고 글을 올리기로 했다..^^
(Apple Developer에 가면 개념들을 잘 설명해놨다. 굿굿)
Overview
Framework는 User Interface와 App Data를 연결하기 위해 State, Binding 과 같은 도구를 제공한다.
이런 도구들은 App에 여러 조각 데이터를 위한 singe source of truth를 유지시킨다.
- 일시적으로 UI state를 view 내에 locally 관리하고 싶다면, State
- State / Oberservable Object 같은 source of truth에 대한 참조를 공유하고 싶다면, Binding
- 외부 참조 model data에 연결하고 싶다면, ObservedObject (ObservableObject protocol)
- environment에 저장된 Observable Object에 접근하고 싶다면, EnvironmentObject
- view에서 Observable Object를 직접 인스턴스화(객체화)하고 싶다면, StateObject
- child view에서 view hierarchy를 통해 data를 전달하려면, PreferenceKey
- core data에 저장된 영구/지속 data를 관리하려면, FetchRequest
1. State
: SwiftUI에 의해 관리되는 value를 읽고 쓸 수 있는 property wrapper type
view 내에서 수정 가능한 데이터를 저장하려고 한다면, State로 선언하자.
State로 선언한 property의 storage를 관리한다. 이 value가 변경되었을 때, 그 value에 의존하는 view hierarchy를 update.
State로 마킹한다는 것은, underlying storage를 관리하기 위해 framework에게 말하는 것이다.
이 value가 바뀌면 영향받는 view들이 업데이트된다.
view hierarchy 내 저장된 value를 위한 하나의 source of truth로써 State를 사용하자.
State는 private으로 선언해 그 view 내에서만 사용 가능하다. 주의할 것은 State != value itself. → State는 value 그 자체를 말하는 것이 아니라, value를 읽고 쓰는 수단일 뿐 [State의 기본 value에 접근하기 위해, WrappedValue property value를 리턴하는 property name으로 참조한다]
State의 life cycle은 view life cycle을 따라가기 때문에 지속/영구적인 storage를 위해 사용하지 말고, UI에 영향을 주기만 하는 일시적인 데이터를 관리하기 위해서 사용하자. (Button의 highlight state, filter setting, 현재 선택된 list item과 같은)
2. Binding
: source of truth에 있는 value를 읽고 쓸 수 있는 property wrapper type
Binding을 사용하면 2-way connection 생성
(데이터 저장 property와 데이터 display & change 하는 view 사이에 connection)
예를 들어, PlayButton view에서 isPlaying state property를 읽고 업데이트 할 수 있다. (이 property를 참조함으로써)
struct PlayButton: View{
@State private var isPlaying: Bool = false
var body: some View{
Button(isPlaying? "Pause" : "Play")
isPlaying.toggle()
}
}
만약 child view로 state property를 넘긴다면, parent view에서 일어나는 value change는 child view에서도 update!
✨대신, 이 value를 child view에서 수정할 순 없다.✨
저장된 value를 child view에서 수정하려면, Binding으로 넘겨줘야 한다.
→ State의 projectdValue에 접근함으로써 State value에서 Binding을 얻을 수 있다. ➿ $ 붙이기
struct PlayButton: View{
@Binding var isPlaying: Bool
var body: some View{
Button(isPlaying? "Pause" : "Play")
isPlaying.toggle()
}
}
//parent view(PlayView)에서 isPlaying을 Binding으로 받았다
struct PlayView: View{
var episode: Episode
@State private var isPlaying: Bool = false
var body: some View{
VStack{
Text(episode.title)
.foregroundStyle(isPlaying? .primary: .secondary)
PlayButton(isPlaying: $isPlaying)
}
}
}
//child view(PlayButton)에서 state property인 isPlaying을 Binding으로 넘겨준다.
즉, PlayView에서 isPlaying이라는 source of truth(단 하나의 데이터 저장소)를 만들어서 연결된 view과 데이터를 공유하도록 데이터를 Binding으로 넘겨준다. 앞서 말했듯이, State는 해당 view에서만 사용할 수 있고 State를 Binding($)으로 넘겨준다면 해당 view에서도 수정 가능
위 코드를 보면 parent view에서는 isPlaying을 State로, child view에서는 Binding으로 선언했다.
→ Binding으로 State에 접근
만약 view에서 child view와 state의 control을 공유할 필요가 있다면, child view 내에 Binding property wrapper를 선언하자. Binding은 존재하는 storage를 참조하고, underlying data를 위한 single source of truth를 보존한다.
💡 Binding은 State와 달리 own storage가 없다.
어딘가에 저장된 state property를 참조하고 그 storage에 2-way connection을 제공한다.
▶︎ source of truth를 왜 사용할까?
여러 view에 동일한 데이터를 선언해주면 데이터가 일치하지 않게 된다. 그러므로 단 하나의 저장소를 생성해서 그 저장소에서 유일한 데이터를 가져와서 사용하도록 해야 한다.
▶︎ source of truth를 어디에 만들면 좋을까?
view 사이 공유되는 하나의 source of truth를 만들기 위해 가장 공통점이 적은 조상 view 내에 state data를 저장하자.
▶︎ wrappedValue, projectedValue?
- wrappedValue: State 변수에 의해 참조되는 기본 값(underlying value). 데이터에 대한 기본 access를 제공한다. wrapping 된 값에 직접 접근하는 것은 아니다. 대신 State로 생성된 속성 변수를 참조한다.
- projectedValue: binding을 반환하는 projection으로 view hierarchy 아래로 binding 값을 전달하려면 사용해야 한다. projectedValue를 가져오려면 $를 붙인다.
▶︎state 변수 내의 scoped value도 binding 할 수 있을까? 네
struct PodCaster: View{
@State private var episode = Episode(
title: "Some Episode",
showTitle: "Great Show",
isFavorite: false)
var body: some View{
VStack{
Toggle("Favorite", isOn: $episode.isFavorite)
PlayerView(episode: episode)
}
}
}
💡공부 및 기록용 블로그이므로 오류가 있을 수 있습니다.💡
만약 문제에 오류나 오타가 있다면 댓글로 알려주세요➿
언제나 환영합니다. 감사합니다. 화이팅!
'iOS' 카테고리의 다른 글
[TextField] 키보드 내리기, 버튼 활성화, 자동으로 커서 올리기 (0) | 2022.07.01 |
---|---|
[iOS] Navigation Bar 없애기 (0) | 2022.07.01 |
🌱 What is MVC Design Pattern? (Stanford iOS Lecture) (0) | 2022.05.15 |
🌱 What's in iOS? (Standford iOS Lecture) (0) | 2022.05.15 |
[iOS] MapKit으로 지도 앱을 만들어보자! (1) (0) | 2022.03.04 |