SwiftUI - transformEnvironment
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 SwiftUI의 transformEnvironment에 대해 학습해보겠습니다 🙋🏻
우선 저는 사실 이 개념에 대해 사용해본적이 없어서 몰랐었긴해요!
그런데, WWDC를 통해 SwiftUI의 기초 영상부터 스터디 도중 아래와 같이 처음보는 메서드가 보였습니다!
transformEnvironment? 이게 뭐지..?
해당 WWDC 영상은 2021년에 공개된 "Demystify SwiftUI"라는 영상으로 후반부에 구조적인 식별자에 대해 설명하며 나옵니다.
느낌상 전역적인 환경변수인 Environment 같은거인거 같은데 처음 접했기에 한번 이렇게 학습하며 기록해보게 되었어요 😃
인트로가 너무 길었는데, 그럼 바로 알아보시죠 🏃🏻
transformEnvironment
우선 공식문서에서는 아주 심플하게 SwiftUI의 메서드로 해당 메서드를 사용해 지정된 키 경로의 환경 값을 변환한다고 나와 있습니다.
func transformEnvironment<V>(
_ keyPath: WritableKeyPath<EnvironmentValues, V>,
transform: @escaping (inout V) -> Void
) -> some View
정의는 요러한데, 정의를 보니 이해가 가네요.
저 keyPath가 환경값에 대한 키패스가 될것이고 transform이 실제 클로저 역할로 동작시켜줄 로직이 될것 같아요.
주로 그래서 키패스에서 지정된 환경 값을 가져와서 해당 환경 값을 transform에서 변경시켜주는 역할을 하는 메서드로 볼 수 있습니다.
반환 타입은 View이니 뷰 모디파이어로 사용할 수 있겠네요!
그럼 한번 간단히 예시 코드를 구현해보면서 사용해볼까요?
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text("Parent")
ChildView()
.transformEnvironment(\.font) { font in
font = Font.system(size: 30)
}
}
}
}
struct ChildView: View {
var body: some View {
Text("Child")
}
}
ContentView에서는 두개의 텍스트를 배치하려 합니다.
하나는 Parent, 그리고 다른 하나는 ChildView에서 배치하고 있는 Child 텍스트죠.
그런데, 여기서 ChildView에 transformEnvironment 모디파이어를 이용하고 있어요.
키패스에서 font 변경을 위해 font 값을 가져오고 이스케이핑 클로저에서 font의 사이즈를 변경해주고 있죠!
즉, 이제 ChildView 컴포넌트 하위 구조에서 만약 font의 크기를 지정해주지 않는다면 이제 하위 종속된 뷰들의 텍스트 크기는 모두 30을 가지게 될것입니다.
기본 폰트 사이즈가 변한것을 확인할 수 있죠!
정리하자면 해당 키패스로 가져온 환경 값이 font였고 클로저에서 사이즈를 변경시켜줬기에 이제 메서드를 이용한 하위 종속된 뷰들에서는 환경 설정 값은 변경된 사이즈의 폰트가 되는것입니다.
폰트 외에도 디자인 테마라던지, 접근성이라던지 정말 다양하게 컨텍스트에서 유용히 활용할 수 있습니다.
이를 통해서 뷰 계층 전체에 걸쳐 일관된 설정을 적용하거나 특정 뷰에 대해 세밀한 조정 등을 할 수 있습니다!
그럼 이쯤에서 하나 의문이 들거에요 🤔
@Environment와는 뭐가 다른거지?! 난 얘밖에 몰랐는데...!
이거에 대한 아주 명확한 차이가 있습니다.
@Environment vs transformEnvironment
우선, @Environment를 모를 수 있어 간단히 설명까지 곁들여볼께요.
@Environment는 뷰 내에서 SwiftUI의 환경 값을 읽기 위해 사용됩니다.
즉, 여기서 핵심은 읽기 전용! 이라는것이에요.
해당 환경 값에 직접 접근할 수 있게 되며, 해당 값을 통해서 UI를 커스텀하게 동적으로 변형할 수 있습니다.
주로, 다크/라이트 모드에 대한 colorScheme 값을 파악하여 뷰들을 그에 맞게 색상을 조절하여 보여줄 수 도 있고, 디바이스의 크기 자체인 size class의 값을 사용할 수도 있죠.
그럼 한번 예시로 볼까요?
import SwiftUI
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
Text("green")
.foregroundColor(colorScheme == .dark ? .green : .blue)
}
}
아주 단순히 요런식으로 사용할 수 있습니다.
@Environment에서 동일하게 키패스로 colorScheme 환경 값을 가져와 변수로 선언하죠.
해당 값은 접근은 가능하나 변경은 되지 않습니다.
그리고, Text에서 현재 다크/라이트 모드에 대해 판단해 텍스트 컬러를 변경해줄 수 있죠.
반대로, 이 값을 변경하고 싶다면 위에서 우리가 같이 알아봤던 transformEnvironment를 이용하면 되는겁니다.
다시 정리하면 transformEnvironment는 특정한 환경 값을 변경할때 사용하니까 이런 경우에 활용할 수 있겠죠!
다만, 유의할 부분은 transformEnvironment으로 특정 환경 값을 변경하는것은 직접적으로 해당 환경 값을 수정하는게 아니라 모디파이어를 적용한 뷰 하위 종속된 계층에서만 변경된 값을 전달하는 역할을 가집니다.
즉, transformEnvironment를 사용해도 계층 구조 상 하위에 있지 않은 부분까지 적용되진 않는것이죠.
아예 고유의 값을 직접 변경하는 느낌이 아닌, 복사된 값을 전달한다 느낌으로 볼 수 있습니다.
마지막으로 공통점으로는 @Environment와 transformEnvironment 모두 환경 값을 이용하는것에 있고 SwiftUI의 데이터 전달 및 상태 관리 매커니즘의 일부로 볼 수 있습니다.
찐막으로 transformEnvironment는 environment 메서드로도 단순히 사용할 수도 있습니다.
아니면 그냥 변경할 환경 값의 모디파이어를 이용해버려도 됩니다 😇
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text("Parent")
ChildView()
.environment(\.font, .title)
}
}
}
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text("Parent")
ChildView()
.font(.title)
}
}
}
위 폰트를 변경하는것과 모두 동일한 결과를 가집니다.
마무리
자 이렇게 transformEnvironment를 알아보면서 환경 설정 값을 읽는것과 변경하는것에 차이에 대해 알아봤어요!
사실, 해당 환경 값을 직접 변경하는 경우가 드물었는데, 하위 뷰들을 구현할때 항상 구체적으로 설정 값들을 내부에서 줬기에 크게 사용할 일은 없었긴했죠.
그것도 아니면 아예 상위에서 environment, 그것도 귀찮기에 font, color 요런식으로 상위에서 컨트롤 해주기에 사실상 쓰는 일이 거의 없었네요 😃
그래도 어떤 차이가 있고 어떻게 사용할 수 있는지 알고 있으면 좋겠죠 ☺️