Environment Object

SteadySlower·2022년 6월 6일
0

SwiftUI

목록 보기
13/64
post-custom-banner

EnvironmentObject 개념

앱 전체에 걸쳐 단 1개만 존재하는 오브젝트를 싱글톤 오브젝트라고 불렀습니다. EnvironmentObject도 비슷한 개념으로 보면 좋습니다. 상위 View에서 한번 인스턴스를 만들면 하위 View에서 동일한 인스턴스를 사용할 수 있습니다.

User를 관리하는 객체를 EnvironmentObject로 사용하면 좋습니다. (앱의 어디서든 로그인 여부, 사용자 이름을 참조할 수 있습니다.)

구현

EnvironmentObject로 사용할 ObservableObject 정의하기

import SwiftUI

//✅ 선언하는 부분은 일반적인 ObservableObject와 동일합니다.
class MyInfo: ObservableObject {
    @Published var name = "Moon"
    @Published var photo = "person"
    var token = ""
}

사용할 View에 @EnvironmentObject로 선언하기

struct HomeView: View {
    @EnvironmentObject var myInfo: MyInfo
    //👉 싱글톤이기 때문에 인스턴스를 만들지 않고 타입만 선언합니다.! (뷰의 메모리에는 올리지 않습니다.)
    
    var body: some View {
        VStack {
            Text("name: \(myInfo.name)")
                .padding()
            Image(systemName: myInfo.photo)
        }
    }
}

상위 View에서 environmentObject의 인스턴스 주입하기

struct ContentView: View {
    var body: some View {
        HomeView()
            .environmentObject(MyInfo()) //👉 여기서 처음 인스턴스를 만든다.
    }
}

중간 뷰에서 EnvironmentObject를 사용하지 않더라도 그 하위 View에서도 사용이 가능하다

아래 예시는 ContentView > HomeView > DetailView의 구조를 가집니다. ContentView에서 주입한 EnvironmentObject는 중간에 위치한 View인 HomeView에서 사용하지 않더라도 그 하위 View인 DetailView에서 사용할 수 있습니다.

마치 어디서나 자유롭게 접근할 수 있는 싱글톤 오브젝트와 마찬가지로 EnvironmentObject를 주입한 View의 하위 View에서는 어디서든 자유롭게 접근할 수 있습니다.

struct ContentView: View {
    //✅ 가장 상위 View에서 일단 주입만 하면!
    var body: some View {
        HomeView()
            .environmentObject(MyInfo())
    }
}
struct HomeView: View {
    //🚫 중간 View에서 EnvironmentObject 사용하지 않지 않더라도
    var body: some View {
        NavigationView(content: {
            NavigationLink(destination: {
                DetailView()
            }, label: {
                Text("To Info Detail")
            })
        })
    }
}
struct DetailView: View {
    //👉 하위 View 어디서든 선언하고 이용할 수 있음.
    @EnvironmentObject var myInfo: MyInfo
    
    var body: some View {
        VStack {
            Text(myInfo.name)
            Image(systemName: myInfo.photo)
        }
    }
}

EnvironmentObject의 장점

뷰 위계가 A → B → C → D 일 때 A, D에서만 필요한 경우에는 B, C는 데이터를 가지고 있을 필요가 없습니다. 이 경우 A에 EnvironmentObject를 주입하면 D에서는 상위 뷰인 B, C에 없는 데이터라도 중간에 언제든지 접근할 수 있습니다.

EnvironmentObject를 사용할 때 주의할 점

최상위 View에서 인스턴스를 한번만 생성하면 그 아래에서는 그냥 쓰면 됩니다. 새로운 인스턴스를 또 만들면 안됩니다!

@EnvironmentObject 변수를 인스턴스로 만드는 경우 = 🚫 에러

만약에 ObservedObject로 만든다면?

어거지로 MyInfo의 인스턴스를 하나 더 만들어 봅시다. 이 경우에는 두 개의 별도의 인스턴스가 생기는 셈입니다. 따라서 HomeView에 있는 myInfo와 DetailView에 있는 myInfo는 완전히 별도의 인스턴스입니다. 이름을 바꾸는 버튼을 만들어서 이름을 바꾸어도 상위 View에 전혀 반영되지 않습니다.

struct DetailView: View {
    @ObservedObject var myInfo = MyInfo()
    
    var body: some View {
        VStack {
            Text(myInfo.name)
            Image(systemName: myInfo.photo)
            Button {
                myInfo.name = "Lee"
            } label: {
                Text("Change Name")
            }

        }
    }
}

기타 팁

앱 전체에서 쓰이는 EnvironmentObject를 주입하기

App의 body에서 주입하면 앱의 모든 View에서 사용할 수 있습니다.

import SwiftUI

@main
struct swiftUI_practiceApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
            	.environmentObject(MyInfo())
        }
    }
}

environmentObject 2개 이상 주입하기

EnvironmentObject는 여러개 주입할 수 있습니다. 필요한 하위 View에서 같은 타입으로 @Environment 변수를 달아서 선언하면 됩니다.

import SwiftUI

@main
struct swiftUI_practiceApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
            .environmentObject(MyInfo())
            .environmentObject(MyLocation())
        }
    }
}

⭐️ 실전 활용 예시

아래 예시는 로그인이 되어있지 않다면 LogInView를 보여주고 로그인이 되어 있다면 MainTabView를 보여주는 아주 단순한 코드입니다. 이 화면 만을 위해서는 굳이 EnvironmentObject를 쓸 필요가 없어 보이기도 합니다.

⭐️ 하지만 이 코드가 진가를 발휘하는 순간은 “로그아웃”을 하는 순간입니다.

사용자가 로그아웃 시키는 버튼은 MainTabView의 하위 View 어딘가에 있을 것입니다. 아주 깊숙히 있을 수도 있지요. 하지만 어떤 View에서든지 간에 로그아웃을 버튼을 누르는 순간 viewModel.user는 nil이 될 것이고 그 순간에 @Publish로 선언된 변수이기 때문에 연결된 View를 Re-render합니다.

따라서 ContentView에서는 viewModel.user를 사용한 분기문이 다시 실행되고 LogInView로 돌아오게 되는 것이지요.

만약에 EnvironmentObject가 없었다면 로그아웃을 실행한 이후에 뷰 계층을 거슬러 올라가서 다시 LogInView를 보여주는 코드를 작성해야할지도 모릅니다. 그런 수고를 Environment Object는 아래와 같은 간단한 방식으로 덜어줍니다.

class AuthViewModel: ObservableObject {
    @Published var user: User

		// ... 중략 ...
    
    func logout() {
        self.user = nil
    }
}
struct ContentView: View {
    @EnvironmentObject var viewModel: AuthViewModel 

    var body: some View {
        Group {
            if viewModel.user == nil { //👉 로그인이 안되어 있다면
                LogInView()
            } else { //👉 로그인이 되어 있다면
                MainTabView()
            }
        }
    }
}
profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.
post-custom-banner

0개의 댓글