ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SwiftUI - @State / @Binding / @StateObject / @EnvironmentObject
    SwiftUI 2022. 3. 21. 20:15

    안녕하세요. 그린입니다🟢

    이번 포스팅에서는 전 포스팅에 이어 SwiftUI에서 구조체 내부에서 값을 변경하여 반영해주거나

    전역적으로 값의 업데이트를 반영해주는 그런 친구들인 타이틀의 세친구 즉,

    @State, @Binding, @StateObject, @EnvironmentObject에 대해서 학습해보겠씁니다🙋🏻

     

    우선 하나씩 차례대로 진행해보겠습니다👍

    @State

    정의를 먼저보면, SwiftUI에서 값을 읽고 쓸 수 있는 유형의 어노테이션입니다.

    @frozen @propertyWrapper struct State<Value>

    역시 propertyWrapper이고 구조체입니다.

    SwiftUI에서 우선 뷰는 구조체입니다.

    즉, 내부 값을 변경해줄 수 없는데 이 @State를 이용해 변경해줄 수 있습니다.

    또한 구조체이기에 이닛과 디이닛이 이뤄지기에 값의 안전함을 보장할 수 없습니다.

    그럴때도 이 @State가 안전함을 보장해줍니다.

    기본적으로 Private 선언이기에 다른 뷰와 값을 소통할 수 없습니다.

    다른 뷰와 소통하려면 @Binding 혹은 앞서 배운 @ObservedObject 그리고 또 나올 전역적인 @EnvironmentObject를 이용해주면 됩니다.

    정리해보자면 이 어노테이션은 해당 뷰에서만 이뤄지는 아주 간단한 프로퍼티에 대해서만 사용하는게 좋을것 같아요!🧑🏻‍⚖️

     

    그럼 사용에 대한 공식문서의 예시를 살펴보시죠!

    struct PlayButton: View {
        @State private var isPlaying: Bool = false
    
        var body: some View {
            Button(isPlaying ? "Pause" : "Play") {
                isPlaying.toggle()
            }
        }
    }

    아주 간단합니다.

    isPlaying이라는 @State 변수를 버튼에 매칭시켜줬습니다.

    버튼이 클릭될때마다 타이틀이 변하고 isPlaying 변수 값도 토글되어 변경됩니다.

    @Binding

    이어서 이 어노테이션을 볼껀데요.

    위에서 말했듯 @State는 자신만의 뷰에서만 변경이 이뤄지게됩니다.

    그런데 SwiftUI는 뷰들의 부모/자식 뷰의 계층관계를 가질때가 많고 구조적으로 분리할 때도 많습니다.

    이에 상위 뷰에서 @State의 변수가 있어 해당 값을 하위 뷰에서 캐치해 변화가 주어져야할때 사용됩니다.

    일단 공식문서 정의를 보시죠!

    @frozen @propertyWrapper @dynamicMemberLookup struct Binding<Value>

    별거없으니 바로 사용하는 예시를 보시죠!

    struct PlayButton: View {
        @Binding var isPlaying: Bool
    
        var body: some View {
            Button(isPlaying ? "Pause" : "Play") {
                isPlaying.toggle()
            }
        }
    }
    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.
            }
        }
    }

    우선 상위뷰는 PlayerView이고 하위뷰는 PlayButton입니다.

    하위뷰에서 @Binding 변수를 선언해줍니다.

    이 변수는 상위뷰의 isPlaying @State 변수와 바인딩됩니다.

    그럼 상위뷰에서 구현부를 보시면 PlayButton을 호출하는데 인자로 @State 변수의 값을 넘겨주며 바인딩해줍니다.

    그러면 하위뷰에서는 바인딩 변수가 존재함으로 상위뷰에서의 변화를 하위뷰에서도 캐치하여 감지할 수 있게됩니다.

    고로 @State와 @Binding의 사용은 같이 이뤄질때가 많습니다🙌

    @StateObject

    이전 포스팅에서 @ObservedObject에 대해 알아본적이 있습니다!

    "ObservableObject를 구독하며 값 업데이트가 발생하면 뷰를 갱신해주는 친구입니다."

    라고 설명했는데요, 이와 거의 동일하다고 보시면됩니다.

    즉, 관찰 가능한 객체를 인스턴스화 하는 속성 래퍼입니다.

    @frozen @propertyWrapper struct StateObject<ObjectType> where ObjectType : ObservableObject

    정의된것도 거의 유사해요. ObservableObject를 따르구요!

    다만 차이라함은 SwiftUI의 뷰는 업데이트 될때가 많습니다.

    즉 뷰의 렌더링이 일어나면 @ObservedObject의 인스턴스가 초기화될때가 있게됩니다.

    이를 @StateObject는 보완해준다고 보면 될것 같아요.

    위에서 @State 어노테이션을 사용해 뷰의 프로퍼티를 구성해준것과 비슷한 느낌일것 같아요🙃

    @EnvironmentObject

    마지막으로 이 친구에 대해 알아보시죠!

    아까 소개할때 전역적인 친구라고 말했습니다.

    그 이유는 여태까지 뷰 자체에서 놀거나 상위/하위뷰에서 바인딩해줘서 관리하고 했다면 이 친구는 다릅니다.

    음.. 약간 싱글턴 같은 인스턴스라고 볼 수 있어요.

    즉 이 어노테이션을 붙여주는 클래스 타입의 정의는 앱 전역적으로 사용될 데이터의 집합으로 이뤄지는게 좋을것 같습니다!

    그럼 자세히 공식문서 정의를 보죠!

    @frozen @propertyWrapper struct EnvironmentObject<ObjectType> where ObjectType : ObservableObject

    ObservableObject 프로토콜을 따르군요 역시

    흐름을 보자면 전역적으로 사용할 데이터에 대한 클래스를 만듭니다.

    그리고 전역적 사용을 위해 SceneDelegate에서 .environmentObject(_:) 메서드를 통해 값을 매칭합니다.

    마지막으로 사용될 뷰들에서 프로퍼티로 @EnvironmentObject 어노테이션을 붙인 변수를 생성합니다.

    한번 예시로 보시죠!

    class Info: ObservableObject {
      @Published var age = 10
    }
    
    class SceneDelegate {
    ...
      var info = Info() 
      window.rootViewController = UIHostingController(rootView: MainView().environmentObject(info))
    ...
    }
    
    struct MainView: View {
      @EnvironmentObject var info: Info
    
      var body: some View {
        Button(action: {
          self.info.age += 1
        }) {
          Text("Click Me for plus age")
        }
        SubView()
      }
    }
    
    struct SubView: View {
      @EnvironmentObject var info: Info
    
      var body: some View {
        Button(action: {
          self.info.age -= 1
        }) {
          Text("Click Me for minus age")
        }
      }
    }

    요렇게 간단히 쓰임을 볼 수 있을것 같아요.

    먼저 앱 전역적으로 사용될 인스턴스를 위한 Info 클래스를 생성합니다.

    요 안에서는 @Published 변수 age를 가지고 있어요.

    이 어노테이션이 뭐라했쬬? 값의 변경이 일어나면 바로 업데이트 쳐주는! 그런 ㅎㅎ

    그다음 SceneDelegate에서 전역적인 인스턴스를 만들어주고 .environmentObject(_:) 메서드를 이용해 해당 인스턴스를

    담아줍니다.

    그다음 메인뷰와 서브뷰를 볼 수 있어요.

    해당 인스턴스 타입의 @EnvironmentObject 변수를 만들어줍니다.

    그리고 이에 대해 각 역할을 부여해주면 됩니다.

    요러면 끝...! 쉽죠??

    마무리

    자 이렇게 이전과 오늘에 연결되어 Combine과 SwiftUI에서의 뷰와 데이터 변화와 흐름을 이해하고 사용하는것에 대해 알아봤습니다!

    써봐야 감이 잘 올거에요!

    그럼 긴말 필요없이 써봅시다🙌

    [참고자료]

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

     

    Apple Developer Documentation

     

    developer.apple.com

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

     

    Apple Developer Documentation

     

    developer.apple.com

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

     

    Apple Developer Documentation

     

    developer.apple.com

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

     

    Apple Developer Documentation

     

    developer.apple.com

     

     

     

     

     

     

     

     

    'SwiftUI' 카테고리의 다른 글

    SwiftUI 4.0 - Charts  (0) 2022.06.10
    SwiftUI - TabView  (0) 2022.04.09
    Custom Stepper  (0) 2022.01.28
    SwiftUI - multilineTextAlignment & lineLimit & lineSpacing  (0) 2022.01.19
    swipeActions  (0) 2021.12.23
Designed by Tistory.