-
Notifications
You must be signed in to change notification settings - Fork 3
SwiftUI에서 View와 Model Binding (part2: @StateObject, @ObservedObject, @Published)
앞서 다룬 state와 binding은 view내에서 간단하게 상태관리를 할때는 적합하지만
우리가 실제로 쓰는 model을 이용할때는 적합하지 못합니다
이때는 @StateObject, @ObservedObject, @Published를 이용하면됩니다
그리고 이를 활용하기에 적합한 디자인패턴이 MVVM 패턴입니다
예시 앱으로 간단한 카운터 앱을 만들어보겠습니다.
먼저 Model과 ViewModel을 보여드리면
struct Counter {
let text: String
var count: Int
}
class CounterViewModel: ObservableObject {
@Published var counter = Counter(text: "카운터", count: 0)
func addCount() {
counter.count = counter.count.advanced(by: 1)
}
}
Counter 구조체는 여태껏 해왔듯 정말 단순한 모델입니다
CounterViewModel에서 combine을 이용하게되는데, ObservableObject 프로토콜을 체택합니다
@Published var counter
는 counter라는 변수가 바뀔때마다 알림을 보내겠다고 의미합니다
사실상
var counter = Counter(text: "카운터", count: 0) {
didSet {
objectWillChange.send()
}
}
와 똑같은 코드인데, 단순화시킨 것이 @Published property wrapper인것 같아요
이제는 해당 ViewModel을 가지는 View를 보겠습니다.
struct CountersView: View {
@State private var showModal = false
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("\(viewModel.counter.text)가 눌린 횟수 : \(viewModel.counter.count)")
Button("Show Modal") {
self.showModal.toggle()
}.sheet(isPresented: $showModal) {
ModalView(showModal: self.$showModal, viewModel: viewModel)
}
}
}
}
struct ModalView: View {
@Binding var showModal: Bool
@ObservedObject var viewModel: CounterViewModel
var body: some View {
VStack(spacing: 20) {
Text("\(viewModel.counter.text)가 눌린 횟수 : \(viewModel.counter.count)")
.padding()
Button("카운터 횟수 늘리기") {
viewModel.addCount()
}
Button("Dismiss") {
showModal.toggle()
}
}
}
}
@StateObject var viewModel = CounterViewModel()
부분은 View의 state가 viewModel에 의존적이다를 말해줍니다. viewModel
에서 업데이트가 나타나면 SwiftUI가 그것을 알아차리고 view를 다시 만듭니다.
마찬가지로 ModalView에도 viewModel을 갖고있습니다
흐름을 생각하면
-
ModalView에서 "카운터 횟수 늘리기"버튼을 누르면 view model에게 model을 업데이트하게 함
-
model인 @Published var counter가 업데이트가 되면 viewmodel이
-
CountersView는 @StateObject, ModalView는 @ObservedObject에게 알려줌
글면 @StateObject와 @ObservedObject는 뭐가 다를까요?
여러번 얘기 나온 "Source of truth"입니다. view에 @StateObject이 있으면 해당 객체의 주인이라는 뜻이고
@ObservedObject는 그 주인이 외부에있다 라는 뜻입니다.
@StateObject is used to store new instances of reference type data that conforms to the ObservableObject protocol. This owns its data. . @ObservedObject refers to an instance of an external class that conforms to the ObservableObject protocol. This does not own its data. .
잠깐... 그러면 showModal도 ViewModel에 넣어도 되지 않을까요?
class CounterViewModel: ObservableObject {
**@Published var showModal = false**
@Published var counter = Counter(text: "카운터", count: 0)
func addCount() {
counter.count = counter.count.advanced(by: 1)
}
}
struct CountersView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("\(viewModel.counter.text)가 눌린 횟수 : \(viewModel.counter.count)")
Button("Show Modal") {
viewModel.showModal.toggle()
}.sheet(isPresented: $viewModel.showModal) {
ModalView(viewModel: viewModel)
}
}
}
}
struct ModalView: View {
@ObservedObject var viewModel: CounterViewModel
var body: some View {
VStack(spacing: 20) {
Text("\(viewModel.counter.text)가 눌린 횟수 : \(viewModel.counter.count)")
.padding()
Button("카운터 횟수 늘리기") {
viewModel.addCount()
}
Button("Dismiss") {
viewModel.showModal.toggle()
}
}
}
}
네 됩니다.
사실 각 @State를 쓸지, @StateObject를 쓸지는 명확한 정답이 있는것 같지 않습니다.
제 생각에는 view와 관련되면 @State, 그리고 viewmodel/ model 과 관련되어있으면 @StateObject를 쓰는게 좋을 것 같네요
아직은 안 써봤는데, @EnvironmentObject
라는 친구도 있습니다.
For data that should be shared with all views in your entire app, SwiftUI gives us @EnvironmentObject
. This lets us share model data anywhere it’s needed, while also ensuring that our views automatically stay updated when that data changes.
Think of @EnvironmentObject
as a smarter, simpler way of using @ObservedObject
on lots of views. Rather than creating some data in view A, then passing it to view B, then view C, then view D before finally using it, you can create it in view and put it into the environment so that views B, C, and D will automatically have access to it.
https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-stateobject-property-wrapper
https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-observedobject-property-wrapper
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app