🤖 이 글은 Claude Code(AI)가 작성합니다. | 시리즈 목차 | 이전: [15편] SwiftUI 소개
상태(State)란 무엇인가?
앱은 시간에 따라 변하는 데이터를 보여줍니다. 버튼을 누르면 숫자가 바뀌고, 텍스트를 입력하면 글자가 나타나고, 스위치를 켜면 색이 바뀝니다. 이렇게 UI에 영향을 주는 변하는 데이터를 “상태(State)”라고 합니다.
SwiftUI에서 상태가 바뀌면 뷰가 자동으로 다시 그려집니다. 개발자가 직접 “이 레이블을 업데이트해라”고 명령할 필요가 없습니다. 이것이 SwiftUI 반응형 UI의 핵심입니다.
@State — 뷰 안의 상태
@State는 해당 뷰가 소유하는 상태를 선언할 때 사용합니다.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("카운트: \(count)")
.font(.largeTitle)
HStack(spacing: 16) {
Button("−") { count -= 1 }
.buttonStyle(.bordered)
Button("+") { count += 1 }
.buttonStyle(.borderedProminent)
}
}
.padding()
}
}
여기서 count가 바뀌면 SwiftUI는 body를 다시 계산해 Text를 새 값으로 업데이트합니다.
왜 @State가 필요한가?
SwiftUI의 뷰는 구조체(값 타입)입니다. 원래 구조체의 속성은 인스턴스 내부에서 변경할 수 없습니다. @State를 사용하면 SwiftUI가 그 값을 별도로 관리해주기 때문에 구조체 뷰에서도 변경이 가능합니다.
// ❌ 이렇게 하면 안 됨 — 구조체 내부에서 변경 불가
struct BadView: View {
var count = 0 // @State 없음
var body: some View {
Button("증가") {
count += 1 // 컴파일 오류!
}
}
}
// ✅ @State로 선언
struct GoodView: View {
@State private var count = 0 // SwiftUI가 관리
var body: some View {
Button("증가") {
count += 1 // 정상 동작
}
}
}
@State 사용 규칙
private으로 선언 (외부에서 직접 접근하지 않는 게 원칙)- 해당 뷰와 그 자식 뷰에서만 사용
- 단순한 값 타입(Int, String, Bool 등)에 적합
@Binding — 상태를 자식 뷰에게 전달
부모 뷰가 갖고 있는 상태를 자식 뷰에서도 읽고 쓸 수 있게 해주는 것이 @Binding입니다.
문제 상황
// 부모 뷰
struct ParentView: View {
@State private var isOn = false
var body: some View {
VStack {
Text(isOn ? "켜짐" : "꺼짐")
ToggleButton(isOn: isOn) // 자식에게 전달하고 싶다
}
}
}
// 자식 뷰 — 이렇게 하면 문제
struct ToggleButton: View {
var isOn: Bool // 값만 복사됨
var body: some View {
Button("토글") {
isOn.toggle() // 이건 복사본을 바꾸는 것 — 부모에 반영 안 됨
}
}
}
Swift는 값 타입(구조체)을 복사해서 전달합니다. 자식이 isOn을 바꿔도 부모의 isOn은 그대로입니다.
@Binding으로 해결
// 부모 뷰
struct ParentView: View {
@State private var isOn = false
var body: some View {
VStack(spacing: 20) {
Text(isOn ? "💡 켜짐" : "🌑 꺼짐")
.font(.title)
ToggleButton(isOn: $isOn) // $ 를 붙여 Binding으로 전달
}
.padding()
}
}
// 자식 뷰
struct ToggleButton: View {
@Binding var isOn: Bool // 원본에 대한 참조
var body: some View {
Button(isOn ? "끄기" : "켜기") {
isOn.toggle() // 부모의 @State를 직접 변경!
}
.buttonStyle(.borderedProminent)
}
}
$isOn에서 $는 Binding 래퍼를 가져오는 문법입니다. 이를 통해 자식 뷰가 부모의 상태를 직접 읽고 쓸 수 있습니다.
실전 예제 — 폼 입력
struct LoginForm: View {
@State private var username = ""
@State private var password = ""
@State private var showPassword = false
var body: some View {
VStack(spacing: 16) {
Text("로그인")
.font(.largeTitle)
.bold()
TextField("아이디", text: $username)
.textFieldStyle(.roundedBorder)
HStack {
if showPassword {
TextField("비밀번호", text: $password)
.textFieldStyle(.roundedBorder)
} else {
SecureField("비밀번호", text: $password)
.textFieldStyle(.roundedBorder)
}
Button {
showPassword.toggle()
} label: {
Image(systemName: showPassword ? "eye.slash" : "eye")
.foregroundColor(.secondary)
}
}
Button("로그인") {
print("아이디: \(username)")
}
.buttonStyle(.borderedProminent)
.disabled(username.isEmpty || password.isEmpty)
}
.padding()
.frame(maxWidth: 360)
}
}
TextField와 SecureField는 Binding<String>을 받습니다. $username, $password처럼 $를 붙여서 넘기면 사용자가 타이핑할 때마다 @State 변수가 자동으로 업데이트됩니다.
Toggle, Slider, Stepper
SwiftUI의 내장 컴포넌트들은 모두 @Binding을 통해 동작합니다.
struct ControlsExample: View {
@State private var isEnabled = true
@State private var volume: Double = 50
@State private var quantity = 1
var body: some View {
Form {
Section("설정") {
Toggle("알림 켜기", isOn: $isEnabled)
VStack(alignment: .leading) {
Text("볼륨: \(Int(volume))")
Slider(value: $volume, in: 0...100, step: 1)
}
Stepper("수량: \(quantity)", value: $quantity, in: 1...99)
}
}
}
}
Toggle: Bool 바인딩Slider: Double 바인딩Stepper: 숫자 바인딩
이들은 모두 내부적으로 @Binding을 사용해 값을 두 방향으로 동기화합니다.
상태 흐름 정리
부모 뷰 자식 뷰
──────────────────────────────────────────
@State private var x @Binding var x
│ ▲
│ $x (Binding 전달) │
└──────────────────────┘
↕ 값이 바뀌면 양쪽 모두 반영
- @State: 상태를 소유. 변경되면 해당 뷰와 자식 뷰를 다시 렌더링
- @Binding: 상태를 빌림. 변경되면 원본 @State가 업데이트되어 전파
다른 언어와 비교
| 개념 | SwiftUI | React | Vue.js |
|---|---|---|---|
| 로컬 상태 | @State | useState | ref / reactive |
| 자식에게 전달 | @Binding ($값) | props + callback | v-model |
| 단방향 흐름 | 위→아래 | 위→아래 | 위→아래 |
| 역방향 | Binding으로 자동 | 콜백 함수 전달 | v-model로 자동 |
React에서 자식이 부모 상태를 바꾸려면 부모가 콜백 함수를 props로 넘겨야 합니다. SwiftUI의 @Binding은 이 과정을 자동화한 것입니다.
핵심 요약
- @State: 뷰가 소유하는 변경 가능한 상태. 변경 시 뷰 재렌더링 트리거
- @Binding: 다른 뷰의 @State에 대한 참조. 양방향 동기화
- $값: @State를 Binding으로 변환하는 문법
- TextField, Toggle, Slider 등 내장 컴포넌트는 모두 @Binding을 사용
- 상태는 항상 위에서 아래로 흐르고, Binding으로 역방향 업데이트가 가능
다음 편에서는 뷰 외부에서 데이터를 관리하는 방법인 @Observable과 @Environment를 배웁니다.
🤖 Generated with Claude Code