@StateObject는 ObservableObject를 준수하는 클래스를 유지함.
SwiftUI를 사용하다 보면 뷰를 처음부터 다시 그려야 할 때가 있다. 이 말은 @StateObject를 사용하는 객체를 제외한 모든 객체가 재설정되는 것을 의미한다.
그 후 뷰가 다시 생성될 때, 해당 ObservableObject클래스는 유지된다.
import SwiftUI
class Idol: ObservableObject {
@Published var group: String
@Published var member: String
init(group: String, member: String) {
self.group = group
self.member = member
}
}
struct AboutStateObject: View {
@StateObject private var idol = Idol(group: "aespa", member: "Karina")
var body: some View {
VStack(spacing: 15) {
CommonTextView(title: "@StateObject", desc: "클래스와 UI 간의 양방향 바인딩에 @StateObject 속성 래퍼 사용")
Text("\(idol.group) \(idol.member)")
.font(.largeTitle)
.fontWeight(.black)
VStack {
TextField("그룹", text: $idol.group)
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("멤버", text: $idol.member)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
}
}
}
위 코드를 실행하고 TextField의 텍스트를 수정하면, Text뷰의 텍스트 역시 실시간으로 변경되는 것을 볼 수 있음.
즉, 뷰를 다시 그리거나 새로 고친 경우에도 @StateObject인 idol변수는 현재 값을 잃지 않고 보존하는 것을 알 수 있다.
import SwiftUI
class Idol: ObservableObject {
@Published var group: String
@Published var member: String
init(group: String, member: String) {
self.group = group
self.member = member
}
}
struct AboutStateObject: View {
@State private var idol = Idol(group: "aespa", member: "Karina")
var body: some View {
VStack(spacing: 15) {
CommonTextView(title: "@StateObject와 @State비교", desc: "@State를 class와 함께 사용시, 양방향 바인딩 불가능. @StateObject는 오직 클래스와 바인딩되며, 구조체는 해당되지 않는데.")
Text("\(idol.group) \(idol.member)")
.font(.largeTitle)
.fontWeight(.black)
VStack {
TextField("그룹", text: $idol.group)
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("멤버", text: $idol.member)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
}
}
}
이제 idol을 @StateObject에서 @State로 변경하고 다시 실행해보자.
TextField의 텍스트를 수정해도 Text뷰에 아무런 변화가 없음을 볼 수 있다.
class MyMember: ObservableObject {
@Published var member: [String] = [""]
@Published var myPick = ""
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [self] in
member = ["Karina", "Winter", "NingNing", "Giselle"]
myPick = member[1]
}
}
}
struct MyMemberWithPicker: View {
@StateObject private var myMember = MyMember()
var body: some View {
VStack(spacing: 15) {
CommonTextView(title: "@StateObject", desc: "@StateObject와 피커 바인딩하기")
Text("멤버 선택")
Picker(selection: $myMember.myPick) { // 피커와 myMember 양방향 바인딩
ForEach(myMember.member, id: \.self) { member in
Text(member).tag(member)
}
} label: {
Text("Picker")
}
.pickerStyle(.wheel)
.background(Color.orange.opacity(0.4))
Text("현재 선택한 멤버 : ")
+ Text(myMember.myPick)
.bold()
.foregroundColor(.orange)
}
}
}
위 코드를 실행하면, 2초 후 member속성이 업데이트되고 UI로 푸쉬된다. 그리고 UI가 업데이트되는 것을 볼 수 있다.
class WithBinding: ObservableObject {
@Published var color = Color.blue
@Published var date = Date()
@Published var slider = 0.75
@Published var stepper = 50
@Published var text = "단방향 바인딩"
@Published var textField = "양방향 바인딩"
@Published var textEditor = "TextEditor 데이터"
@Published var toggle = true
}
struct StateObjectWithBinding: View {
@StateObject private var examples = WithBinding()
var body: some View {
VStack(spacing: 20) {
CommonTextView(title: "@StateObject", desc: "@StateObject를 사용해 다른 뷰들과 바인딩하기")
Form {
ColorPicker("Color Picker", selection: $examples.color)
DatePicker(selection: $examples.date) {
Text("Date")
}
Slider(value: $examples.slider)
Stepper(value: $examples.stepper, in: 0...100) {
Text("Value: \(examples.stepper)")
}
Text(examples.text)
TextField("Placeholder", text: $examples.textField)
.textFieldStyle(RoundedBorderTextFieldStyle())
TextEditor(text: $examples.textEditor)
.border(Color.green)
Toggle("Toggle", isOn: $examples.toggle)
}
}
}
}
이렇게 상호작용시 ObservableObject객체가 업데이트되고, ObservableObject객체가 업데이트되면 UI도 함께 업데이트된다.
@StateObject는 ObservableObject를 준수하는 클래스와 함께 사용해야 한다.
그렇지 않으면 @StateObject를 이용할 수 없다 (Xcode에서 오류 발생함).
import SwiftUI
class Idol: ObservableObject {
@Published var group: String
@Published var member: String
init(group: String, member: String) {
self.group = group
self.member = member
}
}
struct AboutStateObject: View {
@StateObject var idol: Idol = Idol(group: "", member: "")
var body: some View {
VStack(spacing: 15) {
CommonTextView(title: "@StateObject", desc: "프리뷰에서 @StateObject 사용하기")
Text("\(idol.group) \(idol.member)")
.font(.largeTitle)
.fontWeight(.black)
VStack {
TextField("그룹", text: $idol.group)
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("멤버", text: $idol.member)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
}
}
}
/// 프리뷰
struct AboutStateObject_Previews: PreviewProvider {
static var karina = Idol(group: "aespa", member: "카리나")
static var winter = Idol(group: "aespa", member: "윈터")
static var previews: some View {
Group {
AboutStateObject(idol: karina)
.previewLayout(.sizeThatFits)
AboutStateObject(idol: winter)
.previewLayout(.sizeThatFits)
.preferredColorScheme(.dark)
}
}
}
즉, 양방향 바인딩을 제공하는 앱의 화면에 대해서만 인스턴스화하고 유지해야 하는 ObservableObject 클래스(레퍼런스 타입)가 있을 때 @StateObject를 사용함.
Struct(밸류 타입)의 유지를 원한다면 @State를 사용할 것