@Binding은 다른 컴포넌트가 소유한 데이터에 대한 참조를 생성하는 SwiftUI의 Property Wrapper입니다. Binding은 값 자체를 저장하지 않으며 $ 프로젝션을 통해 기존 데이터 소스에 대한 액세스만 제공합니다. Apple Developer Documentation (2025)에 따르면, Binding은 데이터를 직접 소유하지 않고 부모 뷰와 자식 뷰 간의 반응형 양방향 통신을 제공합니다. @Binding은 계층 구조에서 가변 상태를 아래로 전달하는 핵심 메커니즘입니다.
핵심 사항
@Binding은 부모 뷰에 저장된 속성과 자식 컴포넌트 간의 양방향 연결을 생성하는 Property Wrapper입니다. Binding과 @State의 주요 차이점: Binding은 데이터를 소유하지 않습니다. 실제 데이터 소스(@State, @StateObject 또는 부모의 다른 Binding)를 통해서만 값을 읽고 씁니다. Binding이 없으면 자식 뷰는 콜백이나 델리게이트 없이 조상의 상태를 수정할 수 없습니다.
Binding은 두 개의 속성을 가진 구조체로 구현됩니다: wrappedValue(현재 값)와 projectedValue($를 통해 접근 가능한 Binding 자체). 자식 뷰가 Binding을 통해 wrappedValue를 변경하면 SwiftUI가 데이터 소스에 변경 사항을 전달하고 모든 종속 뷰를 다시 그립니다. 이는 현재 업데이트 주기 내에서 동기적으로 발생합니다.
중요한 특징: @Binding은 단일 수준 전달에 국한되지 않습니다. Binding은 여러 계층 수준을 통해 전달될 수 있으며, 각 자식 컴포넌트는 동일한 데이터 소스에 대한 참조를 받습니다. 모든 수준의 변경은 바인딩된 모든 뷰의 단일 업데이트를 트리거합니다.
@Binding을 통한 양방향 바인딩 메커니즘은 Property Wrapper 프로젝션을 기반으로 합니다. 부모가 @State var value: T를 선언하면 SwiftUI가 자동으로 Binding<T> 타입의 $value 프로젝션을 생성합니다. $value를 @Binding var value: T가 있는 자식 컴포넌트에 전달하면 두 뷰를 동일한 메모리 셀에 연결합니다. 자식 뷰에서 Binding을 통한 모든 쓰기는 두 컴포넌트의 다시 그리기를 트리거합니다.
struct SliderContainer: View {
@State private var value: Double = 0.5
var body: some View {
VStack {
Text("Value: \(value)")
SliderView(value: $value)
}
}
}
struct SliderView: View {
@Binding var value: Double
var body: some View {
Slider(value: $value, in: 0...1)
}
}
예제에서 SliderContainer는 @State value를 소유하고 SliderView는 $value를 통해 Binding을 받습니다. SliderView 내부의 Slider는 이 Binding에 바인딩됩니다. 슬라이더를 드래그하면 Slider가 Binding을 통해 값을 변경하고 SliderContainer의 @State를 자동으로 업데이트하여 두 뷰가 현재 숫자를 표시합니다. 전체 체인이 단일 콜백이나 알림 없이 작동합니다.
@StateObject 또는 @ObservedObject에서 Binding을 생성하려면 동일한 프로젝션을 사용합니다: $object.property는 Binding<PropertyType>을 제공합니다. 이를 통해 전체 객체를 전달하지 않고 ObservableObject의 개별 속성을 자식 뷰에 전달할 수 있습니다. 이 접근 방식은 더 좁은 결합을 제공하고 불필요한 다시 그리기를 방지합니다.
SwiftUI 이전에는 계층 구조에서 위로 변경 사항을 전달하는 표준 방법이 콜백과 델리게이트였습니다: 부모가 클로저를 전달하고 자식 컴포넌트가 변경 시 이를 호출했습니다. @Binding은 더 적은 코드와 더 선언적인 구문으로 대안을 제공합니다. 완료 클로저를 전달하는 대신 $stateValue를 전달하기만 하면 됩니다.
| 기준 | @Binding | 콜백 |
|---|---|---|
| 코드 | 하나의 어노테이션 + $ | 클로저 + 호출 |
| 다중 수준 | 자동 | 클로저 체인 |
| 테스트 | Binding(value:constant) | 모의 클로저 |
| 가독성 | 높음 | 중간 |
| 유연성 | 데이터만 | 모든 로직 |
자식 뷰가 값을 읽고 수정만 하면 되는 경우 @Binding을 사용하세요. 변경 시 부작용(유효성 검사, 로깅, 네트워크 요청)이 필요한 경우 Binding을 콜백과 결합하세요: 데이터에는 Binding을, 이벤트에는 클로저를 전달합니다. 예를 들어 TextField는 Binding에 바인딩되고 onChange는 유효성 검사를 트리거합니다.
@Binding은 여러 일반적인 시나리오에서 사용됩니다. 첫 번째 — 커스텀 컨트롤: 스위치, 슬라이더, 컬러 피커 및 기타 인터랙티브 요소가 양방향 동기화를 위해 Binding을 수락합니다. 두 번째 — 모달 창: 시트 표시 플래그가 Binding으로 전달되어 자식 뷰가 presentationMode 또는 직접 설정을 통해 자체적으로 닫힐 수 있습니다.
세 번째 패턴 — 분리된 폼. 폼이 많은 필드로 구성된 경우 각 필드를 별도의 컴포넌트로 추출하여 해당 값에 대한 Binding을 수락할 수 있습니다. 이는 여러 폼 간의 필드 테스트 및 재사용을 간소화합니다. 부모 컴포넌트는 전체 폼 모델의 유일한 소유자로 유지됩니다.
struct FormField: View {
let title: String
@Binding var text: String
var body: some View {
VStack(alignment: .leading) {
Text(title).font(.caption)
TextField("Enter \(title.lowercased())", text: $text)
.textFieldStyle(.roundedBorder)
}
}
}
FormField 컴포넌트는 제목과 문자열에 대한 Binding을 수락합니다. 레이블과 전달된 Binding에 바인딩된 TextField를 표시합니다. 모든 폼은 각 필드에 $property를 전달하여 FormField를 여러 번 사용할 수 있습니다. 이는 마크업 중복을 줄이고 텍스트 필드 스타일링을 중앙 집중화합니다.
SwiftUI는 Binding(get:set:) 초기화자를 통해 수동으로 Binding을 생성할 수 있습니다. 이는 값을 읽거나 쓸 때 로직을 추가해야 할 때 유용합니다. 예를 들어 저장 전에 숫자의 형식을 지정하는 Binding이나 변경 시마다 값을 원격 서버와 동기화하는 Binding을 만들 수 있습니다.
struct ValidatedField: View {
@State private var email: String = ""
var emailBinding: Binding<String> {
.init(
get: { email },
set: { email = $0.lowercased().trimmingCharacters(in: .whitespaces) }
)
}
var body: some View {
TextField("Email", text: emailBinding)
}
}
목록에서 커스텀 emailBinding은 변경 시마다 자동으로 텍스트를 소문자로 변환하고 공백을 제거합니다. TextField는 $email에 직접 바인딩하는 대신 이 Binding을 사용합니다. 이 접근 방식은 onChange 핸들러로 코드를 복잡하게 만들지 않고 Binding 내에서 유효성 검사 및 데이터 변환을 중앙 집중화합니다.
첫 번째이자 가장 흔한 오류는 Binding 대신 값 전달입니다. 자식 컴포넌트가 @Binding var text: String을 선언하고 부모가 text($ 없이)를 전달하면 컴파일러가 오류를 표시합니다: Cannot convert value of type 'String' to expected argument type 'Binding<String>'. 해결책 — 전달 시 항상 $ 접두사를 사용하세요: $text.
두 번째 오류 — 읽기 전용 데이터에 Binding. 자식 뷰가 값을 읽기만 하면 되는 경우 @Binding을 사용하지 마세요 — 부모의 단순한 let 또는 @State로 충분합니다. Binding은 쓰기 가능성을 의미하며 과도한 수정 권한은 디버깅을 복잡하게 하고 최소 권한 원칙을 위반합니다.
세 번째 문제 — 프로덕션에서 Binding.constant. Binding.constant(value)는 피드백 없는 더미 바인딩을 생성하며 변경 사항이 무시됩니다. constant는 프로토타이핑 및 미리보기(Xcode Previews)에만 사용하고 실제 코드에서는 절대 사용하지 마세요. 테스트에는 제어된 동작으로 Binding(get:set:)을 사용하세요.
자주 묻는 질문
@State는 데이터를 소유하고 힙에서 저장을 관리합니다. @Binding은 소유권 없이 기존 상태만 참조합니다. @State는 항상 비공개이며 @Binding은 자식 뷰의 입력 매개변수입니다.
네, Binding(get:set:) 초기화자 또는 Binding.constant(value)를 통해 가능합니다. Binding은 @StateObject에서 $object.$property 프로젝션을 통해, 그리고 Publisher에서 Subscribe 내부의 Binding(get:set:)을 통해 얻을 수도 있습니다.
@Binding은 체인을 통해 전달됩니다: 각 중간 컴포넌트가 @Binding을 선언하고 $를 통해 더 전달합니다. 모든 수준은 루트 뷰의 동일한 데이터 소스를 참조합니다.
Binding.constant는 뮤트 래퍼를 생성하며 세터가 새 값을 무시합니다. 자식 컴포넌트의 피드백이 필요하지 않은 프로토타이핑 및 SwiftUI Previews 전용입니다.
네, Binding<T?>이 지원됩니다. Binding<String?>을 전달하면 자식 뷰가 nil을 설정할 수 있습니다. 이는 옵셔널 폼 필드 또는 재설정 옵션이 있는 상태에 편리합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.