Swift UI 상태 프로퍼티 - @State
Swift UI 에서 뷰의 업데이트는 뷰와 결합된 데이터를 업데이트 할 때 일어난다. 이를 위해선 데이터와 뷰 사이에 게시자 - 구독자 관계를 구축하여 할 수 있다. 이를 위해서 Swift UI 는 상태 프로퍼티, Observable 객체, 그리고 Environment 객체를 제공하며, 이들 모두는 사용자 인터페이스의 모양과 동작을 결정하는 상태를 제공한다. Swift UI 에서 UI 는 코드 내에서 직접 업데이트하지 않고, 결합된 데이터가 변함에 따라 자동으로 뷰가 업데이튿 된다.
struct ContentView: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
}
}
상태 프로퍼티
상태 프로퍼티는 상태에 대한 가장 기본적인 형태이며, 뷰 레이아웃의 현재 상태를 저장하기 위해서만 사용된다. 상태 프로퍼티는 String 이나 Int 처럼 간단한 데이터 타입을 저장하기 위해 주로 사용하며, @State 프로퍼티 래퍼를 사용하여 선언된다. 프로퍼티 래퍼에 대해서 모른다면 이전 게시글을 보고오자.
struct ContentView: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
}
}
상태 값은 해당 뷰에 속한 것이기 때문에, private 접근 지정자를 사용해야 한다. @State 프로퍼티 래퍼가 붙은 값이 변경되면 해당 프로퍼티가 선언된 뷰 계층구조를 다시 렌더링한다. 즉 그 프로퍼티의 데이터에 의존하는 뷰들은 모두 업데이트되어 최신 데이터를 반영하게 된다.
예를 들어 위 코드에서 wifiEnabled 라는 Boolean 프로퍼티와 결합된 토글 뷰가 있다면, 사용자가 토글 뷰를 조작할 때마다 wifiEnabled 프로퍼티가 변경되고, 뷰가 업데이트된다.
쉬운 이해를 위해 TextField 를 예로 들어보자.

TextField 는 사용자 텍스트 입력 이벤트를 받는다. 사용자가 텍스트를 입력할 때마다 그에 맞춰 뷰를 업데이트 해야한다. (입력한 텍스트를 보여주기 위해). 따라서 텍스트 필드도 @State 프로퍼티 래퍼로 감싸진 String 프로퍼티를 받아야 하며, 정확한 사용 방식은 다음과 같다.
struct ContentView: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
VStack{
TextField("PlaceHolder Text",text: $userName)
.padding()
Text("사용자가 입력한 텍스트 : \(userName)")
}
}
}
userName 프로퍼티를 TextField 와 결합함으로써, 사용자의 입력 이벤트는 전부 userName 에 저장된다. 이 때 TextField 와 userName 을 결합하기 위해 userName 앞에 '$' 를 사용했다. 이제 사용자가 입력하는 텍스트는 전부 두 번째 텍스트 뷰에 반영되고, 이 때 userName은 '$' 이 없는 것을 알 수 있는데, 왜냐하면 단순히 프로퍼티의 값만을 참조하기 때문이다.

상태 바인딩
struct ContentView: View {
@State private var wifiEnabled = true
var body: some View {
VStack{
HStack{
Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
Toggle(isOn: $wifiEnabled, label: {Text("와이파이")})
.frame(width:120)
}
}
}
}
와이파이 연결 여부를 나타내는 wifiEnable 프로퍼티와, 해당 데이터와 결합해 설정 상태를 보여줄 Toggle View 를 선언했다. 또한 설정 상태에 따라 알맞은 이미지를 나타내는 Image View 도 선언했다.

물론 지금은 간단한 이미지와 토글 버튼 뿐이라서 같은 뷰 안에서 모든 동작을 구현할 수 있다. 하지만 코드의 재사용성과 가독성을 위해서 공통적으로 나타나는 뷰를 하위 뷰로 만드는 경우를 생각해보자. 지금의 경우에선 와이파이 이미지를 하위 뷰로 추출한다고 생각해보자. 단순히 와이파이 이미지 뷰는 다음과 같이 구현할 수 있다.
struct WifiImageView: View {
var body: some View {
Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
}
}
여기서 중요한 것은, 위 와이파이 이미지 뷰가 상위 뷰인 ContentView 의 wifiEnable 프로퍼티를 참조해야 한다는 점이다. 하지만 wifiEnabled 는 엄연히 다른 구조체의 프로퍼티이기 때문에 WifiImageView 구조체에서 접근할 수 있는 방법이 없다. 이 경우는 @Binding 프로퍼티 래퍼를 사용해서 해결할 수 있으며, 다음과 같이 사용할 수 있다.
struct ContentView: View {
@State private var wifiEnabled = true
var body: some View {
VStack{
HStack{
WifiImageView(wifiEnabled: $wifiEnabled)
Toggle(isOn: $wifiEnabled, label: {Text("와이파이")})
.frame(width:120)
}
}
}
}
struct WifiImageView: View {
@Binding var wifiEnabled : Bool
var body: some View {
Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
}
}
@Binding 프로퍼티 래퍼를 선언하고, 해당 하위 뷰를 사용할 때 '$'를 사용해 프로퍼티에 대한 결합을 제공해주면 된다.