SwiftUI / @State, @Binding, @Environment

Minsang Kang·2023년 10월 24일
0

SwiftUI

목록 보기
10/12
post-custom-banner

이전 글에서 SwiftUI 에서 Data를 다루는 방법으로 @State, @Binding, @Environment 등의 프로퍼티래퍼를 사용한다고 했습니다.
따라서 이번에 @State, @Binding, @Environemnt 각각 Apple Developer 문서를 통해 알아보고자 합니다.

Apple Developer 문서에 표현된 State, Binding, Environment 정의 탐방
https://developer.apple.com/documentation/swiftui/state
https://developer.apple.com/documentation/swiftui/binding
https://developer.apple.com/documentation/swiftui/environment

UPDATE: 2023-10-26 15:28

@State

https://developer.apple.com/documentation/swiftui/state

SwiftUI로 관리되는 read/write 가능한 property wrapper 타입
State<Value>

Overview

  • App, Scene, View 등 view 계층구조 내에서 single source of truth로 값을 저장하기 위하여 기본값을 포함한 @State 속성을 적용한 property를 사용하세요.
  • SwiftUI에서 제공하는 스토리지 관리와 충돌할 수 있는 memberwise initializer에서 state를 설정하는 것을 방지하려면 private로 사용하세요.
struct PlayButton: View {
    @State private var isPlaying: Bool = false // Create the state.

    var body: some View {
        Button(isPlaying ? "Pause" : "Play") { // Read the state.
            isPlaying.toggle() // Write the state.
        }
    }
}

  • SwiftUI는 state property의 저장소를 관리합니다. 값이 변경되면 SwiftUI는 해당 property 값을통해 표시되는 view 계층구조의 일부분을 자동으로 업데이트합니다.
  • state property는 기본값으로 wrapped 된 값인 wrappedValue를 사용하지만, property를 직접 참조하면 Swift가 단축키 형태로 wrappedValue를 참조합니다.
  • 위의 예시에서 state property인 isPlaying property의 직접 참조하는 식으로 wrappedValue를 read/write 하고 있습니다.

  • state property 값에 접근해야 하는 view 계층구조의 가장 높은 view 내에서 private property로 선언하세요.
  • 해당 state property 값을 하위 view 내에서 read/write 할 수 있도록 binding을 통해 공유하세요.
  • class와 같은 reference type을 state property로 사용하고자 하는 경우 @StateObject를 사용하세요.

여기까지 Overview 내용을 살펴봤는데요, 이전에 Property Wrapper를 살펴본 것 처럼 wrappedValue를 SwiftUI View 내에서 사용하는 형태입니다!
여기서 @State 프로퍼티래퍼를 사용하면 SwiftUI가 값의 변화를 인지하여 자동으로 이 property를 사용하는 view를 업데이트해준다는 것이 큰 특징입니다!
(아마도 이 동작이 setter 내에서 publish 하고, getter 내에서 subscribe 할 것 같은 느낌이 드네요..!)

그러면 위에서 얘기한 것 처럼 하위 view로 전달하는 Binding 내용을 살펴보겠습니다!

Share state with subviews

  • state property를 하위 view로 전달하면 SwiftUI는 값이 변경될 때 마다 하위 view를 업데이트하지만, 하위 view는 값을 수정할 수 없습니다.
  • 하위 view에서 수정이 가능하도록 하려면 @Binding을 통해 전달하면 됩니다.
  • state property 에서 projectedValue를 통해 binding 값을 얻을 수 있으며, 이 값은 $property명 형식으로 사용이 가능합니다.

  • 예를 들어 하위 view 내 button에서 binding 값을 적용하여 isPlaying 값을 false로 수정할 수 있습니다.
  • 그리고 상위 view에서 하위 view로 @Binding 값으로 전달하기 위하여 상위 view에서 state property를 선언하고, $property명 형식으로 state property에 대한 binding 값을 전달합니다.
struct PlayButton: View {
    @Binding var isPlaying: Bool // Play button now receives a binding.

    var body: some View {
        Button(isPlaying ? "Pause" : "Play") {
            isPlaying.toggle()
        }
    }
}
struct PlayerView: View {
    @State private var isPlaying: Bool = false // Create the state here now.

    var body: some View {
        VStack {
            PlayButton(isPlaying: $isPlaying) // Pass a binding.

            // ...
        }
    }
}

  • state property를 선언시 항상 기본값을 할당하여 초기값을 설정하세요.
  • state propertyview와 하위 view 내에서만 저장소로 사용하세요.

여기까지 내용을 통해 @State 값을 하위 view로 @Binding으로 전달하기 위한 내용을 살펴봤습니다.
Prooperty Wrapper 에서 projectedValue를 사용할 수 있었는데, 여기서 @State 프로퍼티래퍼의 경우 projectedValue를 통해 binding값을 반환한답니다!

그리고 이런 binding 값을 통해 하위 view 내에서도 read와 더불어 write까지 가능한 바인딩을 제공하고 있었어요!

정리

  • @StateSwiftUI 내 view 계층구조 내에서 값을 저장하는 용도의 property wrapper
  • wrappedValue: view를 표시하기 위한 데이터값
    • 이 값의 변화를 SwiftUI가 인지하여 해당 property를 사용하는 view를 자동으로 업데이트합니다.
  • projectedValue: binding 값을 반환
    • binding 값을 통해 하위 view 내에서도 read와 더불어 write까지 가능하도록 전달 가능
    • $property명 형식으로 사용

이렇게 @State와 @Binding을 통해 정보를 공유하면 thread-safe하게, 그리고 SwiftUI가 자동으로 변화를 감지하여 표시해준다는 점이 너무나 매력적인 것 같아요!

@Binding

그러면 다음으로 @Binding 프로퍼티래퍼를 살펴보겠습니다
https://developer.apple.com/documentation/swiftui/binding

source of truth 정보를 read/write 가능한 property wrapper 타입
Binding<Value>

Overview

  • binding을 통해 데이터를 저장하고 있는 property인 source of truth 정보데이터를 표시하고 변경하는 view 간의 양방향 연결을 만듭니다.
struct PlayButton: View {
    @Binding var isPlaying: Bool

    var body: some View {
        Button(isPlaying ? "Pause" : "Play") {
            isPlaying.toggle()
        }
    }
}
  • 예를 들어, play와 pause를 전환할 수 있는 button은 @Binding 프로퍼티래퍼를 사용하여 상위 view의 property에 대한 바인딩을 통해 만들 수 있습니다.
  • 그리고 상위 view는 @State 프로퍼티래퍼를 사용하여 playing 상태를 지니는 property를 선언하여 source of truth 값을 지닙니다.
struct PlayerView: View {
    var episode: Episode
    @State private var isPlaying: Bool = false


    var body: some View {
        VStack {
            Text(episode.title)
                .foregroundStyle(isPlaying ? .primary : .secondary)
            PlayButton(isPlaying: $isPlaying) // Pass a binding.
        }
    }
}

  • 상위 view인 PlayerView는 하위 view인 PlayButton을 초기화할 때 @State 프로퍼티래퍼의 binding 값인 Binding<Bool> 값을 전달합니다.
  • @State 프로퍼티래퍼 변수명 앞에 $를 붙이면 projectedValue 값인 binding 값이 반환됩니다.
  • 그리고 binding으로 연결된 양방향 연결을 통해 PlayButton을 탭할 때 마다 isPlaying 상태가 변경됩니다.
  • Observavle 프로토콜을 준수하는 type에 대한 바인딩을 만드려면 @Bindable 프로퍼티래퍼를 사용하세요.(iOS17+ 이상 가능)

여기까지가 @Binding 프로퍼티래퍼 내용이였습니다.

정리

  • 상위 view에서 @State 프로퍼티래퍼로 source of truth 정보를 지니고 있음
  • 하위 view에서 @Binding 프로퍼티래퍼로 양방향 바인딩을 받아 값을 read/write 할 수 있음
  • 상위 view에서 하위 view로 전달시 @State 프로퍼티래퍼의 projectedValue 값이 binding 값이므로 $를 통해 하위view로 전달

간단하죠?
그리고 projectedValue 내용을 간단하게 살펴보니, 아쉽게도 어떻게 양방향 바인딩이 구현되었는지까지는 내용이 없더라구요..ㅠㅠ

@Environment

마지막으로 Binding이 없이도 모든 하위 view에서 접근가능한 데이터 형식인 @Environment 프로퍼태래퍼에 대해 알아보겠습니다!
https://developer.apple.com/documentation/swiftui/environment

view의 environment 값을 읽는 property wrapper 타입
Environment<Value>

Overview

  • @Environment 프로퍼티래퍼를 사용하여 view의 environment 값을 읽을 수 있습니다.
  • 선언시 EnvironmentValues key path 값을 사용하여 읽을 값을 나타냅니다.
  • 예를 들어 \.colorScheme key path를 통해 현재 view의 color scheme 값을 읽을 수 있습니다.
@Environment(\.colorScheme) var colorScheme: ColorScheme

  • 선언된 @Environment property의 wrappedValue 값으로 얻을 수 있으며, 조건에 따른 컨텐츠를 표시하면 됩니다.
  • 다른 property wrapper와 같이 property를 직접 참조하여 wrappedValue 값을 접근합니다.
if colorScheme == .dark { // Checks the wrapped value.
    DarkContent()
} else {
    LightContent()
}

  • 값이 변경되면 SwiftUI는 해당 값으로 표시되는 view의 모든 부분을 자동으로 업데이트합니다.
  • 예를 들어, 사용자가 Appearance setting 값을 변경하면 \.colorScheme key path 값으로 선언한 @Environment property를 사용하는 모든 view는 자동으로 업데이트 됩니다.

  • environment 프로퍼티래퍼를 통해 read는 가능하지만 write는할 수 없습니다.
  • SwiftUI는 system setting에 따라 자동으로 업데이트 하고, 다른 environment 값에는 기본값을 제공합니다.
  • 그리고 .environment(_:_:) view 모디파이어를 통해 사용자 정의 environment를 정의하거나 기존 environment를 재정의할 수 있습니다.
  • 사용자 정의 environment를 생성시 EnvironmentKey 프로토콜을 참고하여 생성합니다.

여기까지가 @Environment 프로퍼티 내용이였습니다.
쉽게 말해 dark mode & light mode와 같은 system setting 값들을 EnvironmentValues 형식으로 제공하고 있으며 이러한 환경값을 읽고 변화를 감지해야 하는 경우 @Environment 프로퍼티래퍼를 통해 값을 얻을 수 있답니다!

그리고 네트워킹과 같은 사용자 정의 environment 또한 정의가 가능하며, 이때는 EnvironmentKey 프로토콜을 사용하여 정의할 수 있습니다!

정리

SwiftUI 에서 Model data를 전달하는데 사용되는 @State, @Binding, @Environment 프로퍼티래퍼를 살펴봤습니다

  • 셋 모두 Property Wrapper 형식으로 제공되어 SwiftUI View를 표시하기위한 값은 wrappedValue를 사용합니다.
  • @State 프로퍼티래퍼의 경우 write가 가능한 양방향 바인딩이 필요할 때 $를 사용하여 projectedValue 값인 Binding을 사용합니다.
  • 하위 view에서 @Binding 프로퍼티래퍼를 통해 Binding값을 받아 사용합니다.
  • @Environment 프로퍼티래퍼를 통해 view 내에서 system setting과 같은 환경값을 읽고 변화를 감지할 수 있습니다.
  • 사용자 정의 환경도 설정 가능합니다.

음.. 그런데 이런생각은 혹시 안드실까요?

  • 그러면 상위 -> 하위는 모두 $를 통해 @Binding 식으로 전달해야 하나?
  • viewModel 식으로 한번에 데이터들을 전달할수는 없나?

따라서 이와 관련하여 다음 글에서 @StateObject, @ObservedObject, @EnvironmentObject 프로퍼티래퍼를 살펴보겠습니다!

다음글: SwiftUI / @StateObject, @ObservedObject, @EnvironmentObject

profile
 iOS Developer
post-custom-banner

0개의 댓글