State
: 값이 변하면 View를 새로 그려주는 역할을 수행
State 란 무엇인가?
1. State Property store values that the view depends on
⇒ 뷰가 의존하는 상태 속성 저장값
2. State Property represent values that can change
⇒ State Property는 변경할 수 있는 값을 나타냅니다.
3. State Property typically declared in the view that nedds them
⇒ State Property는 일반적으로 이를 추가하는 뷰에서 선언됩니다.
예제 코드
- State 속성을 사용한 toggleValue 의 변화에 따라 Text가 변하는걸 볼수 있다
Binding
: State의 값을 받아오거나 변경할 때 사용 ( View간 양방향 소통 )
$ 기호를 사용하여 속성을 정의한다
Binding이란 무엇인가?
1. Put a ‘$’ infront of a State Property reference in your view code (Not in the declaration) to turn it into a two way, Read/Write relationship
⇒ view 코드(선언 아님)에서 State Property 참조 앞에 '$'를 넣어 양방향 읽기/쓰기 관계로 전환합니다.
2. @Binding is a property wrapper to indicate that it expects a binding to be passed to the view when an instance of the view is created
⇒ @Binding은 뷰의 인스턴스가 생성될 때 뷰에 바인딩이 전달될 것으로 예상함을 나타내는 속성 래퍼입니다.
예제 코드
- State를 선언한 View
struct ParentView: View {
@State private var toggleValue = true
var body: some View {
SubView(toggleValue: $toggleValue)
}
}
- Binding으로 State변수를 받는 SubView
struct SubView: View {
@Binding var toggleValue: Bool
var body: some View {
if toggleValue == false {
Text("Toggle is False")
} else {
Text("Toggle is True")
}
Button(action: {
self.toggleValue.toggle()
}) {
Text("토글 버튼")
}
}
}
- @Binding 을 명시함 으로써, State변수를 받아옴을 명시
- 부모 View 에서 Binding으로 값을 넘겨 줄때는 $ 기호를 사용
Observable Object
: 여러 View에서 의존하기를 원할때 사용하는 Object
Observable Object를 사용하기 위한 절차
- Class가 Observable Object 프로토콜을 채택해야함
- 프로토콜을 채택함으로써 변경하상에 대해 Class의 객체를 관찰하거나 수신
- @Published를 선엄하여 변경사항을 저장할 속성을 명확하게 명시 ( Class 내의 개체에 명시 )
- @Observed Object 속성을 사용하여 관찰가능한 개체에 대한 참조를 저장
예제 코드
- 여러 View에서 의존 할 Class 생성
class Profile: ObservableObject {
@Published var isLogined = false
}
⇒ Class에 Observable Object를 채택
⇒ @Published 를 사용함 으로써 변하는 값을 감시할 변수 명시
struct LoginView: View {
@ObservedObject var profile = Profile()
var body: some View {
if profile.isLogined {
Text("로그인 완료")
} else {
Text("버튼을 눌러 로그인 해주세요")
}
Button(action: {
profile.isLogined.toggle()
}){
Text("버튼")
}
}
}
⇒ @ObservedObject 를 사용함 으로써, 변경 사항을 감시할 개체 인스턴스 생성
하지만 이러한 방식을 사용하면 하위 View 모두에게 동일 인스턴스를 전달 할 수 없다 다음 예를 보자
버튼을 클릭하여 로그인을 하면, 로그인 사용자의 닉네임을 보여줄 것이다.
- LoginView
struct LoginView: View {
@ObservedObject var profile = Profile()
var body: some View {
if profile.isLogined {
Text("로그인 완료")
} else {
Text("버튼을 눌러 로그인 해주세요")
}
LoginCheckView()
Button(action: {
profile.isLogined.toggle()
}){
Text("버튼")
}
}
}
- LoginCheckView
struct LoginCheckView: View {
@ObservedObject var profile = Profile()
var body: some View {
if profile.isLogined {
Text("로그인한 사용자 : Chikong.")
} else {
Text("로그인 정보가 없습니다.")
}
}
}
코드를 작성하고 앱을 실행하면
- 하위 View의 값이 변하지 않는것을 확인 할 수 있다
이러한 현상이 나타나는 이유는 다음 그림을 살펴보자
- 다음과 같이 각 View에 새로운 Class 인스턴스를 생성 했기 때문에 각각 다른 인스턴스를 참조 하고 있다.
- 그렇기 때문에 두개의 인스턴스는 다른 것으로 인식하게 되고, 연관된 값을 변경 하거나 감시할 수 없다
EnvironmentObject
: 하위 View 모두에게 동일한 객체 인스턴스를 사용
위의 예시를 해결할 수 있는 방법이 EnvironmentObject 이다. EnvironmentObject를 사용하면 동일 인스턴스를 하위 View 모두에게 전달 할 수 있다.
위의 문제를 해결할 예제 코드를 보자
- MainView
import SwiftUI
@main
struct DataFlowApp: App {
var body: some Scene {
WindowGroup {
LoginView()
.environmentObject(Profile())
}
}
}
⇒ 제일 상위 Main View에서 .environmentObject(Profile()) 를 이용해 객체 인스턴스를 넘겨주었다. 이제 MainView의 하위 View에서 동일한 인스턴스를 참조할 수 있다.
- LoginView
struct LoginView: View {
@EnvironmentObject var profile: Profile
var body: some View {
if profile.isLogined {
Text("로그인 완료")
} else {
Text("버튼을 눌러 로그인 해주세요")
}
LoginCheckView()
Button(action: {
profile.isLogined.toggle()
}){
Text("버튼")
}
}
}
- LoginCheckView
struct LoginCheckView: View {
@EnvironmentObject var profile: Profile
var body: some View {
if profile.isLogined {
Text("로그인한 사용자 : Chikong.")
} else {
Text("로그인 정보가 없습니다.")
}
}
}
⇒ LoginView 와 LoginCheckView에 @EnvironmentObject var profile: Profile 부분을 보자.
@EnvironmentObject 속성을 명시 했고 같은 인스턴스를 참조함을 구체적으로 적어 주었다
⇒ 같은 인스턴스를 참조하기 때문에 로그인상태에 따라 값이 변함을 확인 할 수 있다.
이처럼 하위 View에 동일 인스턴스를 참조해야 할때, 필요한 View에 명시해주면 사용할 수 있다.