Skip to content

SwiftUI에서 View와 Model Binding (part1: @State와 @Binding)

강병민 (Byungmin Kang) edited this page Dec 13, 2020 · 2 revisions

SwiftUI와 Combine을 이용하면서 가장 많이 보게될 propertyWrapper인

@State, @Binding @ObservedObject @Published에대해서 정리해보았습니다

예시를 들기 위해서 똑같은 View를 조금씩 다르게 구현해봤습니다

완성된 결과물은 정말 간단한 스위치입니다 ( toggle 이 on일 때만 "Hello World"를 표시합니다)

@State

가장 간단한 방법으로는 하나의 view 내에 모든 코드를 갖고있을떄입니다

struct SampleView: View {
    @State private var isOn = true

    var body: some View {
        VStack {
            Text("This is an Example")
            VStack {
                Toggle(isOn: $isOn) {
                    Text("Show welcome message")
                }.padding()

                if isOn {
                    Text("Hello World!")
                }
            }
        }
    }
}

이 View는 true/false가 매우 중요한 view인데 이 정보를

@State private var isOn = true에 담고 있습니다

Apple 공식 자료와 영상을 보면 이러한 값을 "source of truth"라고합니다

When that state changes, SwiftUI knows to automatically reload the view with the latest changes so it can reflect its new information.

즉 Toggle이 isOn의 값을 바꾸게되면 SwiftUI는 View의 "State"(상태)가 바뀌었다는것을 알아채서 View를 업데이트하게됩니다.

그러면 이번에는

이부분만 SubView로 따로 빼놓겠습니다

이때 Extract SubView를 쓰면 편하겠죠?

따로 SubView로 빼놨더니 이렇게 오류를 뜹니다

해당 View의 state를 표현해줄 정보가 없는것이죠

그러면 방금과 마찬가지로 @State private var isOn = true를 추가하면 될까요?

🙅‍♂️안됩니다🙅‍♂️

이렇게하면 parent View와 child View가 각각의 "source of truth"를 가지게 되어 따로 행동하게 됩니다.

이러한 상황에 쓰이는것이 @Binding입니다

This is exactly what @Binding is for: it lets us create a property in the add user view that says “this value will be provided from elsewhere, and will be shared between us and that other place.” That property literally means “I have a Boolean value called isOn, but it’s being stored elsewhere.” -HackingWithSwift

struct SampleSubView: View {
    @Binding var isOn: Bool
    var body: some View {
        VStack {
            Toggle(isOn: $isOn) {
                Text("Show welcome message")
            }.padding()
            
            if isOn {
                Text("Hello World!")
            }
        }
    }
}

그럼 이제 부모 View에서는 해당값을 넘겨줘야겠죠?

이때 주의해야할 점은 isOn이 아니라 $isOn을 넘기는것입니다.

struct SampleView: View {
    @State private var isOn = true

    var body: some View {
        VStack {
            Text("This is an Example")
            SampleSubView(isOn: $isOn)
        }
    }
}

물론 이렇게 간단한 View일때는 @State를 써도 지장없습니다

하지만 조금더 복잡한 View, 혹은 복잡한 외부의 Model을 쓰게될때는 어떻게 해야할까요?

@ObservedObject @Published 맛보기...!

이때 쓰이는것이 @ObservedObject입니다

여전히 View가 해당 data에 대한 dependency가 존재하는데, 이제는 그 data를 우리가 직접 만들어주게 됩니다.

(part 2에 더 자세히 설명을 하겠지만

MVVM패턴에서는 ViewModel이 해당역할을 하게됩니다.

이해를 돕고자 코드를 보여주자면...

struct SampleModel {
    var isOn: Bool
    let text: String
}

class SampleViewModel: ObservableObject{
    @Published var model = SampleModel(isOn: true, text: "Hello World!")
    
    func toggleIsOn() {
        model.isOn.toggle()
    }
}

View는 새롭게 만든 Model과 ViewModel을 반영하기 위해

import SwiftUI

struct SampleModel {
    var isOn: Bool
    let text: String
}

class SampleViewModel: ObservableObject{
    @Published var model = SampleModel(isOn: true, text: "Hello World!")
    
    func toggleIsOn() {
        model.isOn.toggle()
    }
}

struct SampleView: View {
   @StateObject var viewModel = SampleViewModel()
    
    var body: some View {
        VStack {
            Text("This is an Example")
            SampleSubView(isOn: $viewModel.model.isOn, sampleText: viewModel.model.text)
        }
    }
}

struct SwitchView_Previews: PreviewProvider {
    static var previews: some View {
        SampleView()
    }
}

struct SampleSubView: View {
    @Binding var isOn: Bool
    let sampleText: String
    
    var body: some View {
        VStack {
            Toggle(isOn: $isOn) {
                Text("Show welcome message")
            }.padding()
            
            if isOn {
                Text(sampleText)
            }
        }
    }
}
Clone this wiki locally