SwiftUI Property Wrappers
≡ 목차 (Table of Contents)
SwiftUI에서는 Property Wrapper를 이용해서 Swift 언어로 양방향 Model 혹은 View Model 구현을 할 수 있도록 지원한다. 이렇게 SwiftUI에서 유용하게 사용되는 Property Wrapper 들을 정리해보자.
@State
@State
는 특정 프로퍼티를 뷰의 상태(state)로 만들어준다. 즉 이 프로퍼티가 변경되면 자동으로 뷰의 데이터도 변경되고, 뷰의 데이터를 바꿔도 이 프로퍼티의 데이터도 자동으로 변경된다.
struct ContentView: View { @State private var name = "World" var body: some View { VStack { Text("Hello, \(name)!") .padding() Button( action: { self.switchName() }, label: { Text("Switch") } ) } } func switchName() { if name == "World" { name = "Universe" } else { name = "World" } } }
위의 예제는 버튼을 누르면 프로퍼티의 내용이 바뀌는데 이때 텍스트 뷰의 내용도 자동으로 바뀌는 것을 볼 수 있다.
참고로 예제에서도 볼 수 있다시피 @State
는 private
프로퍼티에만 사용할 수 있다.
@Binding
@Binding
은 다른 인스턴스 소유의 @State
프로퍼티를 빌려올 때 사용한다.
struct MyToggleButton: View { @Binding var value: Bool var body: some View { Button(action: { self.value.toggle() }, label: { Text(self.value ? "Hello" : "World") }) } } struct ContentView: View { @State private var value = false var body: some View { VStack { MyToggleButton(value: $value) } } }
위의 예제에서 MyToggleButton
의 value
프로퍼티가 @Binding
으로 선언되어 있다. 그리고 이 프로퍼티는 나중에 ContentView
에서 뷰를 생성할 때 value
프로퍼티와 연결된다.
따라서 이 두 값은 연결되기 때문에 어느 한 쪽의 값이 바뀌면 다른 한 쪽도 값이 동일하게 바뀐다. 또한 뷰도 이 데이터의 변경을 알아채고 역시 알아서 업데이트된다.
@ObservedObject
@State
의 대표적인 단점은 Value 타입에서만 사용이 가능하다는 점이 다. 즉 클래스 오브젝트의 경우는 @State
나 @Binding
이 불가능하다. 대신 이 경우 @ObservableObject
를 상속받은 클래스의 프로퍼티에 @ObservedObject
라는 Property Wrapper 를 적용해 비슷하게 뷰와 프로퍼티를 연결할 수 있다.
class MyData: ObservableObject { @Published var name = "World" @Published var buttonTitle = "Switch to Universe" func switchName() { if name == "World" { name = "Universe" buttonTitle = "Switch to World" } else { name = "World" buttonTitle = "Switch to Universe" } } } struct ContentView: View { @ObservedObject var data = MyData() var body: some View { VStack { Text("Hello, \(data.name)!") .padding() Button( action: { self.data.switchName() }, label: { Text(self.data.buttonTitle) } ) } } }
다만 클래스의 모든 프로퍼티의 변화를 추적하지는 않는다. 위의 예에서 볼 수 있다시피 추적을 원하는 프로퍼티는 @Published
라는 Property Wrapper를 적용해야 한다.
@EnvironmentObject
@EnvironmentObject
의 경우 오브젝트라는 이름이 붙은 것처럼 클래스 오브젝트를 추적하기 위한 용도의 Property Wrapper다. 다만 차이가 있다면 공유 인스턴스 형태에 적합하게 사용할 수 있다는 점이 있다.
class SharedData: ObservableObject { @Published var configName = "default" ... } struct ContentView: View { @EnvironmentObject var sharedData: SharedData ... } struct FooView: View { @EnvironmentObject var sharedData: SharedData ... }
위의 경우 ObservableObject
를 상속받은 클래스를 여러 뷰에서 @EnvironmentObject
형식으로 참조하는 것을 볼 수 있다. 따라서 이름처럼 환경설정 등 여러 곳에서 공유될 만한 데이터를 관리하는 모델로 사용하기 좋다.
다만 최초 생성을 참조가 시작되기 전에 되어야만 할 것이다. 보통은 해당 뷰를 만들기 전에 오브젝트를 생성하고 이걸 environmentObject()
로 알려주어야 한다.
var sharedData = SharedData() ... window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(sharedData))
위 코드가 SharedData
오브젝트를 생성해서 공유를 시작하는 시점이다. 이 코드를 어디에 만들어야 하나 궁금할 수 있는데, SceneDelegate.swift
라는 파일이 보인다면 이 파일 안에서 찾아보자. 아마도 비슷한 곳을 찾을 수 있을 것이다.