구독되는 객체는 특정 상태가 앱 내의 몇몇 SwiftUI 뷰에 의해 사용되어야 할 경우에 가장 적합하다. 그런데, 어떤 뷰에서 다른 뷰로 이동(navigation)하는 데 이동될 뷰에서도 동일한 구독 객체에 접근해야 한다면, 이동할 때 대상 뷰로 구독되는 객체에 대한 참조를 전달해야 할 것이다.
@StateObject var demoData : DemoData = DemoData() NavigationLink(destination: SecondView(demoData)) { Text("Next Screen") } - 앞의 코드 선언부에서 NavigationLink는 SecondView라는 이름의 다른 뷰로 이동하기 위해 사용되며, demoData 객체에 대한 참조를 전달한다.
- 이 방법은 여러 상황에 사용될 수 있지만, 앱 내의 여러 뷰가 동일한 구독 객체에 접근해야 하는 경우에는 복잡해질 수 있다.
- 이런 상황에서는 Environment 객체를 사용하는 것이 더 합리적일 수 있다.
Environment 객체는 Observable 객체와 같은 방식으로 선언된다. 즉, 반드시 ObservableObject 프로토콜을 따라야 하며, 적절한 프로퍼티가 게시되어야 한다. 하지만, 중요한 차이점은 이 객체는 SwiftUI 환경에 저장되며, 뷰에서 뷰로 전달할 필요 없이 모든 자식 뷰가 접근할 수 있다는 것이다. 다음의 Observable 객체 선언 예제를 살펴보자.
class SpeedSetting: ObservableObject { @Published var speed = 0.0 } - Environment 객체를 구독해야 하는 뷰는 @StateObject 또는 @ObservedObject 래퍼 대신 @EnvironmentObject 프로퍼티 래퍼를 사용하여 객체를 참조하기만 하면 된다.
- 예를 들어, 다음의 뷰는 모두 동일한 SpeedSetting 데이터에 접근해야 한다.
struct SpeedControlView: View { @EnvironmentObject var speedSetting: SpeedSetting var body: some View { Slider(value: @speedSetting.speed, in: 0...100) } } struct SpeedDisplayView: View { @EnvironmentObject var speedSetting: SpeedSetting var body: some View { Text("Speed = \(speedSetting.speed)") } } - 이 시점에서 우리에게는 SpeedSetting이라는 Observable 객체와 해당 타입의 Environment 객체를 참조하는 두 개의 뷰가 있다.
- 하지만 아직 Observable 객체의 인스턴스를 초기화하지 않았다.
- 이 작업을 수행하는 논리적 위치는 앞에서 본 하위 뷰의 상위 뷰 안이다.
struct ContentView: View { let speedsetting = SpeedSetting() var body: some View { VStack { SpeedControlView() SpeedDisplayView() } } } - 하지만 이 시점에서 앱을 실행하면 실행 직후 충돌이 발생한다.
- 여기서 문제는 ContentView 선언 내에서 관찰 가능한 객체의 인스턴스를 생성했지만 뷰 계층 구조에 아직 삽입하지 않았다는 것이다.
- 이것은 Observable 객체 인스턴스를 통해 전달하는 environmentObject() 수정자를 사용하여 다음과 같이 한다.
struct ContentView: View { let speedsetting = SpeedSetting() var body: some View { VStack { SpeedControlView() SpeedDisplayView() } .environmentObject(speedsetting) } } - 이러한 단계가 수행되면 객체는 관찰되는 객체와 동일한 방식으로 작동하지만, 뷰 계층 구조를 통해 전달되지 않고 콘텐트 뷰의 모든 하위 뷰에 접근할 수 있다는 점만 다르다.
- SpeedControlView의 슬라이더를 움직이면 현재 속도 설정을 반영하도록 SpeedDisplayView의 Text 뷰가 업데이트 된다.
- 이로써 두 개의 뷰가 동일한 Environment 객체를 접근하고 있음을 보여준다.
요약
SwiftUI는 사용자 인터페이스와 앱의 로직에 데이터를 바인딩하는 방법 세 가지를 제공한다. 상태 프로퍼티는 사용자 인터페이스 레이아웃 내의 뷰 상태를 저장하는 데 사용되며, 현재 콘텐트 뷰에 관한 것이다. 이 값은 임시적이어서 해당 뷰가 사라지면 값도 없어진다.
사용자 인터페이스 밖에 있으며 앱 내의 SwiftUI 뷰 구조체의 하위 뷰에만 필요한 데이터는 Observable 객체 프로퍼티를 사용해야 한다. 이 방법을 사용하면 데이터를 표시하는 클래스나 구조체는 ObservableObject 프로토콜을 따라야 하며, 뷰와 바인딩될 프로퍼티는 @Published 프로퍼티 래퍼를 사용하여 선언되어야 한다.
뷰 선언부에 Observable 객체 프로퍼티와 바인딩하려면 프로퍼티는 @ObservedObject 또는 @StateObject 프로퍼티를 사용해야 한다.(대부분의 경우 @StateObject가 선호)
사용자 인터페이스 밖에 있으며 여러 뷰가 접근해야 하는 데이터일 경우에는 Environment 객체가 최고의 해결책이 된다. Observable 객체와 동일한 방법으로 선언되지만, Environment 객체 바인딩은 @EnvironmentObject 프로퍼티 래퍼를 사용하여 SwiftUI 뷰 파일 내에 선언된다. 자식 뷰에 접근할 수 있게 되기 전에 environmentObject() 수정자를 사용하여 뷰 계층 구조에 삽입하기 전에 Environment 객체도 초기화해야 한다.
[!note] 굉장히 중요한 부분인데 흐름이 자꾸 끊기다보니 이해가 잘 안된다. 이해가 되다가 끊긴 느낌? 확실히 짚고 이해하고 넘어가야 할 것 같다.