상태, Observable 객체, 그리고 Environment 객체로 작업하기
상태, Observable 객체, Environment 객체
- 사용자 인터페이스의 모양과 동작을 결정하는 상태 제공
- 사용자 인터페이스 레아아웃을 구성하는 뷰는 뷰와 바인딩된 상태 객체가 시간이 지남에 따라 변하면 그 상태에 따라 자동으로 뷰가 업데이트 됨
상태 프로퍼티
상태 프로퍼티(state property)
- 현재 상태를 저장
- 간단한 데이터 타입을 저장
- @State 프로퍼티 래퍼 사용
- private 프로퍼티로 선언
struct ContentView: View {
@State private var wifiEnalbed = true
@State private var userName = ""
var body: some View {
.
.
}
바인딩
- 프로퍼티 이름앞에
$
키워드 사용
struct ContentView: View {
@State private var wifiEnalbed = true
@State private var userName = ""
var body: some View {
VStack {
TextField("Enter user name", text: $userName)
Text(userName)
}
}
}
상태 프로퍼티에 변화가 생길 때마다 뷰 계층구조는 SwiftUI에 의해 다시 렌더링
userName 프로퍼티에 ‘$’ 표시 없이 사용된 이유:
상태 프로퍼티에 할당된 값을 참조하였기 때문
토글 뷰 바인딩
var body: some View {
VStack {
Toggle(isOn: $wifiEnabled) {
Text("Enalbe Wi-Fi")
}
TextField("Enter user name", text: $userName)
Text(userName)
Image(systemName: wifiEnalbed ? "wifi" : "wifi.slash")
}
}
상태 바인딩
.
.
VStack {
Toggle(isOn: $wifiEnabled) {
Text("Enalbe Wi-Fi")
}
TextField("Enter user name", text: $userName)
WifiImageView()
}
}
.
.
struct WifiImageView: View {
var body: some View {
Image(systemName: wifiEnabled ? "wifi" : "wifi.slash") // 오류 발생
}
}
Image 뷰는 메인 뷰의 범위 밖
-> @Binding 프로퍼티 래퍼 이용
WifiImageView(wifiEnabled: $wifiEnabled)
}
}
.
.
struct WifiImageView: View {
@Binding var wifiEnabled : Bool
var body: some View {
Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
}
}
Observable 객체
상태 프로퍼티: 하위 뷰가 아니거나 상태 바인딩이 구현되어 있지 않은 다른 뷰는 접근x (일시적)
Observable 객체: 여러 다른 뷰들이 외부에서 접근할 수 잇는 영구적인 데이터를 표현
- ObservableObject 프로토콜을 따르는 클래스나 구조체 형태
- 시간에 따라 변경되는 하나 이상의 데이터 값을 모으고 관리하는 역할
- 타이머나 알림과 같은 이벤트 처리
- 게시된 프로퍼티(published property) -> 게시자를 구독(subscribe) 자동 업데이트
- Combine 프레임워크
- 게시자(publisher)와 구독자(subscriber) 간의 관계 구축
- 여러 게시자를 하나의 스트림으로 병합
- 게시된 데이터를 구독자 요구에 맞게 변형
- 복잡한 수준의 연쇄 데이터 처리 작업
- 게시된 프로퍼티 구현하는 가장 쉬운 방법: @Published 프로퍼티 래퍼
import Foundation import Combine class DemoData : ObservableObject { @Published var userCount = 0 @Published var currentUser = "" init() { updateData() } func updateData() {} }
- observable 객체를 구독하기 위해: @ObservedObject 프로퍼티 래퍼
import SwiftUI struct ContentView: View { @ObservedObject var demoData : DemoData var body: some View { Text("\(demoData.currentUser), you are user number \(demoData.userCount)") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(demoData: DemoData()) } }
구독하게 되면 게시된 프로퍼티에 접근
Environment 객체
어떤 뷰에서 다른 뷰로 이동(navigation)할때,
이동될 뷰에서도 동일한 구독 객체 에 접근해야 한다면 구독 객체에 대한 참조체 를 전달
.
.
@ObservedObject var demoData : DemoData = DemoData()
.
.
NavigationLink(destination: SecondView(demoData)) {
Text("Next Screen")
}
demoData 객체에 대한 참조체를 전달
여러 뷰가 동일한 구독 객체에 접근해야 하는 경우 복잡 -> Environmnet 객체
- ObservableObject 프로토콜을 따름
- SwiftUI 환경에 저장
- 뷰에서 뷰로 전달할 필요없이 모든 뷰가 접근
- @EnvironmentObject 프로퍼티 래퍼 이용
@EnvironmentObject var demoData: DemoData
- 옵저버(observer) 내에서 초기화x
let contentView = ContentView() let demoData = DemoData() // 객체의 인스턴스 저장 if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: contentView.environmentObject(demoData)) self.window = window window.makeKeyAndVisible() } . . struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView().environmentObject(DemoData()) // 선언부 수정 } }
구독 객체와 같은 동작, 모든 레이아웃 뷰가 접근가능