원문 : https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views

@EnvironmentObject를 사용해서 뷰 간에 데이터를 공유하는 방법(How to use @EnvironmentObject to share data between views)

전체 앱에서 모든 뷰간에 데이터를 공유해야 하는 경우, SwiftUI는 @EnvironmentObject를 제공합니다. 필요한 경우에 모든곳에서 모델 데이터를 공유할 수 있으며, 데이터가 변경할때 자동으로 모든 뷰가 업데이트 되는 것을 보장할수 있습니다.

@EnvironmentObject가 많은 뷰에서 @ObjectBinding을 사용하는 것보다 더 똑똑하고 간단한 방법이라 생각합니다. 뷰 A에서 데이터를 만들고나서, 뷰 B에 전달하고, 뷰 C에 전달하고, 마지막으로 사용하기 전에 뷰 D로 전달하는 것 대신에, 뷰에서 만들고 environment에 넣고 뷰 B, C, D에서 자동으로 그것을 사용할 수 있을 것입니다.

주의: Environment 객체는 반드시 조상(ancestor) 뷰에서 제공되어야 합니다 - SwiftUI가 현재 타입에서 environment 객체를 찾지 못하는 경우 크래쉬(crash)가 날것입니다. 미리보기에서도 마찬가지이므로 주의해야 합니다.

예들들어, 사용자 설정을 저장하는 bindable 객체가 있습니다.

class UserSettings: BindableObject {
    var didChange = PassthroughSubject<Void, Never>()

    var score = 0 {
        didSet {
            didChange.send(())
        }
    }
}

그렇습니다. 하나의 값만 저장하지만, 괜찮습니다 - 중요한 것은 값이 변경될 때 PassthroughSubject가 모든 뷰를 새로고침하도록 알려주는 것입니다.

사용자 설정(User settings)은 앱에서 어디서든 공유할 수 있는 합리적인 데이터이므로, 더 이상 직접 동기화할 필요가 없습니다.

그래서, 앱을 처음 실행할때 우리는 UserSettings의 인스턴스를 만들것이며 그 공유된 인스턴스는 앱 어디서든 사용할 수 있습니다.

SceneDelegate.swift를 열어보면 scene(_:willConnectTo:options:) 메소드 안쪽에 있는 다음과 같은 코드 2줄을 찾을수 있을것입니다.

let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = UIHostingController(rootView: ContentView())

코드 2번째 줄은 초기 컨텐츠 뷰를 만들고 화면에 보여줍니다. 이곳은 우리가 만들었던 모든 environment 객체를 전달해야 하는 곳이므로, SwiftUI는 ContentView 안에서 사용할수 있도록 만들수 있지만 다른 모든 뷰도 사용할 수 있습니다.

우선, SceneDelegate의 프로퍼티를 추가합니다.

var settings = UserSettings() 

settings 인스턴스를 한번 만들고 안전하게 저장합니다. 이제 내가 보여줬던 그 두번째 줄에 있는 코드로 돌아가고 두번째 줄을 변경해서 settings 프로퍼티를 다음과 같이 ContentView 안의 environment 객체로 전달합니다. 

window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(settings))

한번 완료하고나서, 그 공유된 UserSettings 인스턴스는 컨텐츠 뷰에서 사용가능하고 모든 다른 뷰에서 가져오거나 표시할 수 있습니다. 다음과 같이 @EnvironmentObject 프로퍼티를 사용해서 프로퍼티를 만들기만 하면 됩니다.

@EnvironmentObject var settings: UserSettings

environment로 부터 자동으로 읽게 될것이기에, 기본 값으로 초기화할 필요는 없습니다.

그래서, 우리는 ContentView 구조체를 score setting을 증가하도록 만들수 있고, 심지어는 UserSettings의 모든 지역 인스턴스를 만들거나 전단할 필요없이, DetailView에서 score setting을 보여줄수도 있습니다 - 항상 environment만 사용 합니다.

그렇게 만들수 있는 코드는 다음과 같습니다.

struct ContentView : View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        NavigationView {
            VStack {
                // A button that writes to the environment settings
                Button(action: {
                    self.settings.score += 1
                }) {
                    Text("Increase Score")
                }

                NavigationLink(destination: DetailView()) {
                    Text("Show Detail View")
                }
            }
        }
    }
}

struct DetailView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        // A text view that reads from the environment settings
        Text("Score: \(settings.score)")
    }
}

따라서 객체를 environment에 넣은 후에는 상위레벨 뷰 또는 10레벨 아래에 있는 뷰에서도 즉시 사용할 수 있습니다 - 중요하지 않습니다. 그리고 가장 중요한 것은, 어떤 뷰가 environment를 바꿀때마다, 그것을 의존(relying)하는 모든 뷰는 자동으로 새로고침되서 동기화 상태를 유지한다는 것입니다. 

보시다시피(As you can see), 우리는 scene delegate에 있는UserSettings 인스턴스를 두 뷰의 settings 프로퍼티와 명시적으로 연결할 필요가 없었습니다 - SwiftUI는 environment안에 UserSettings 인스턴스가 있는 것을 자동으로 파악하므로, 그것을 사용하면 됩니다.

경고: 우리 뷰들이 현재 environment 객체를 의존(rely)하기에, settings를 사용한 예제를 제공하기 위해 여러분의 미리보기 코드 또한 업데이트하는 것이 중요합니다. 예를들어, 미리보기를 위해 ContentView().environmentObject(UserSettings())같은 것을 사용하면 됩니다.

Posted by 까칠코더