[SwiftUI 3.0] State/ Binding / EnvironmentObject
State/ Binding / EnvironmentObject
✅ 오늘은 이 세가지에 대해서 알아보자.
@State: 값이 변경되었을 때, 화면을 다시 보여주어야 할 때.
@Binding: state같은 친구들을 view - view 간에 공유해야 하는 경우.
@EnvironmentObject: parent로 child(하위) view에 공유해야 하는 경우.
우선 State와 Binding에 대해서 이해해보자.
✅ 예시 코드
struct FirstView: View {
@State var appTitle = tabIndex.first.rawValue
@State var count = 0
enum tabIndex: String {
case first = "1번뷰 입니다."
case second = "2번뷰 입니다."
case third = "3번뷰 입니다"
}
var body: some View {
TabView {
VStack {
Text("스위프트 유아이를 정복해봅시다. count: \(count)")
.padding()
Button { // action
count += 1
} label: {
Text("카운트 업")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
.tabItem { Label("1번 뷰", systemImage: "pencil.circle") }
SecondView(count: $count)
.tabItem { Label("2번 뷰", systemImage: "pencil.circle") }
ThirdView(count: $count)
.tabItem { Label("3번 뷰", systemImage: "pencil.circle") }
}
}
}
✅ SecondView (ThirdView도 코드가 같습니다.)
import SwiftUI
struct SecondView: View {
@Binding var count: Int
init(count: Binding<Int> = .constant(0)) {
_count = count
}
var body: some View {
VStack {
Text("2번뷰입니다. \(count)")
Button { // action
count += 1
} label: {
Text("카운트 업")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
}
}
🟠 1번뷰(@State를 갖고 있음)에서 카운트업을하고 2번뷰로 이동하면 1,2번 뷰 모두 카운트가 달라집니다.
🟠 그런데 @Binding으로 처리되어 있는 2번뷰에서 카운트 업을하고 1번뷰로 간다면?
-> 이 경우에도 카운트가 달라집니다.
🟠 그렇다면 2번뷰에서 카운트를하고 3번뷰로 이동을 바로 한다면?
-> 이 경우에도 카운트가 달라지게 됩니다.
State와 Binding은 뷰 간에 데이터를 공유하게 해줍니다.
State에 걸린 동일한 Binding에 전부 반영된다고 이해할 수 있습니다.
다음으로 EnvironmentObject에 대해서 알아봅시다.
✅ @State의 경우에는 뷰 전체를 다시 그리는 동작을 합니다.
예를 들어서 row가 10인 List를 그렸다고 가정합시다. row를 클릭하면 해당 row의 데이터를 변경하여 UI를 갱신한다고 생각합시다.
그렇다면 다시 다 그려야하니까 비용이 넘 큽니다... 해당 row만 바꿔주면 훨씬 더 효율적이겠죠?
또한, Combine이라는 개념을 도입해야 하는데, 이 경우에는 RxSwift를 공부하던 기억을 살리면 쉽게 접근할 수 있습니다.
이렇게 되면은 하위에서 구독의 개념이 생겨서 하위뷰 전체를 동시에 컨트롤 할 수 있게 된다.
잘 이해가 안간다면 코드를 보자!
🟠 사용법 정리
1. ObservableObject를 ViewModel에서 상속받기
2. 해당 View에 environment를 사용하여 ViewModel을 넣어주기
✅ SwiftUI_PracticeApp
@main
struct SwiftUI_PracticeApp: App {
var body: some Scene {
WindowGroup {
// viewModel을 넣어준다.
FirstView().environmentObject(ViewModel())
}
}
}
✅ ViewModel
import Foundation
import Combine
// ObservableObject는 이벤트 처리에 대한 것을 상속
class ViewModel: ObservableObject {
@Published var appTiltle = ""
}
✅ FirstView
import SwiftUI
struct FirstView: View {
@EnvironmentObject var viewModel: ViewModel
@State var appTitle = tabIndex.first.rawValue
@State var count = 0
enum tabIndex: String {
case first = "1번뷰 입니다."
case second = "2번뷰 입니다."
case third = "3번뷰 입니다"
}
var body: some View {
TabView {
VStack {
Text(appTitle)
Text("스위프트 유아이를 정복해봅시다. count: \(count)")
.padding()
Button { // action
count += 1
viewModel.appTiltle = "\(appTitle)\(count)"
} label: {
Text("카운트 업")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
.tabItem { Label("1번 뷰", systemImage: "pencil.circle") }
SecondView(count: $count)
.tabItem { Label("2번 뷰", systemImage: "pencil.circle") }
ThirdView(count: $count)
.tabItem { Label("3번 뷰", systemImage: "pencil.circle") }
}
.onReceive(viewModel.$appTiltle) { appTitle in
print("Receieved", appTitle)
self.appTitle = appTitle
}
}
}
✅ SecondView
import SwiftUI
struct SecondView: View {
@EnvironmentObject var viewModel: ViewModel
@Binding var count: Int
@State var appTitle: String = ""
init(count: Binding<Int> = .constant(0)) {
_count = count
}
var body: some View {
VStack {
Text(viewModel.appTiltle)
Text("2번뷰입니다. \(count)")
Button { // action
count += 1
viewModel.appTiltle = "\(appTitle)\(count)"
} label: {
Text("카운트 업")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}.onReceive(viewModel.$appTiltle) {
self.appTitle = $0
}
}
}
}
onReceive하고 있기 때문에 12, 1213, 121314 ... 이런식으로 UI에 반영된다.
✅ ThirdView
import SwiftUI
struct ThirdView: View {
@EnvironmentObject var viewModel: ViewModel
@Binding var count: Int
@State var appTitle: String = ""
init(count: Binding<Int> = .constant(0)) {
_count = count
}
var body: some View {
VStack {
Text(viewModel.appTiltle)
Text("3번뷰입니다. \(count)")
.padding()
Button { // action
count += 1
viewModel.appTiltle = "\(appTitle)\(count)"
} label: {
Text("카운트 업")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
}
}
여기서는 onReceive가 없어서 이전 값을 처음에 한번만 가져오고 이후에 지속해서 값을 받아들이지 못한다.
따라서 카운트업을 눌러도 appTitle이 빈 상태라서 12, 13, 14 .. 이런식으로 UI에 반영된다.