아요 개발 일기

[SwiftUI] Property Wrapper - @Published, @ObservedObject, @StateObject, @EnvironmentObjec 본문

UI/SwiftUI

[SwiftUI] Property Wrapper - @Published, @ObservedObject, @StateObject, @EnvironmentObjec

소진이 2023. 1. 3. 15:43

안녕하세요! :)

오늘은 저번 글에 이어서 Property Wrapper에대해 알아보도록 하겠습니다!


 

@ObservedObject

 

https://developer.apple.com/documentation/swiftui/observedobject

 

" 관찰 가능한 객체를 구독하고 관찰 가능한 객체가 변경될 때마다 view를 무효화하는 속성 래퍼 유형입니다"

 

앗,,

ObservedObject는 OverView가 없네요..

구글링 합시당!

 

- SwiftUI는 @ObservedObject를 통해 view가 외부 객체를 감지하게 해줌

 

- @State는 특정 view에만 사용하는 프로퍼티였다면 ObservedObject는 

더 복잡한 프로퍼티(여러 메소드 & 여러 view에서 공유할 수 있는 커스텀 타입이 있는경우)에 사용

 

- String이나 integer 같은 간단한 로컬 property대신 외부 참조 타입(external reference type)을 사용한다는 점을 제외하면

@State와 매우 유사

 

- @ObservedObject와 함께 사용하는 타입은 ObservableObject protocol 따라야함

 

- Observed object가 데이터가 변경되었음을 view에 알리는 방법은 여러가지가 있지만 

가장 쉬운 방법은 @Published property wrapper를 사용하는 것 = SwiftUI에 view reload를 트리거

 

- @Published property wrapper는 SwiftUI에게 변경 사항이 view에 reload를 트리거해야한다고 알려주는 역할을 합니다.

 

만약,

Observable과 @Published에대해 모른다면

[SwiftUI] ObservableObject, @Published 글을 참고해주세요!

 

 

어웅 복잡해

예시를 봅시다!

 

View는 위 이미지처럼 만들꺼에요!

 

우선, ObservableObject를 프로토콜을 준수하는 UserProgress 클래스를 만들어주도록 하겠습니다!

 

 

🙋🏻‍♀️ 왜 ObservableObject 프로토콜을 준수해요? 🙋🏻‍♀️

ObservableObject프로토콜을 준수하면

UserProgress 클래스의 인스턴스를 view 내부에서 사용할 수 있고,

중요한 변경이 발생되면 view가 다시 load됩니다.

 

🙋🏻‍♀️ 왜 class에요? 🙋🏻‍♀️

ObservableObject가 class-bound 프로토콜이기때문에 Class로 선언해주어야합니다!

  

 

 

class UserProgress: ObservableObject {
    @Published var score = 0
}

 

UserProgress의 score 앞에 @Publisthed가 붙었죠??

즉, score가 변경되면 view를 reload하게 됩니다.

 

🙋🏻‍♀️ 왜 progress property가 private 하지 않아요? 🙋🏻‍♀️

binding된 개체는 둘 이상의 보기에서 사용할 수 있으므로 공개적으로 공유하는 것이 일반적이기 때문입니다.

 

struct InnerView: View {
    @ObservedObject var progress: UserProgress
    
    var body: some View {
        Button("Increase Score") {
            progress.score += 1
        }.buttonStyle(.bordered)
    }
}

 

Button부분을 설정하는 View를 보겠습니다!

 

UserProgress의 인스턴스인 score을 사용하기위해 @ObservedObject Property를 사용해줍니다.

body 쪽은 버튼을 누르면 +1이되도록 구성하였습니다 ㅎㅎ

 

 

이제 끝나갑니다!

마지막 View를 구성해봅시당

 

struct ProgressView: View {
    @StateObject var progress = UserProgress()
    
    var body: some View {
        VStack {
            Text("Your score is \(progress.score)")
            InnerView(progress: progress)
        }
    }
}

 

어? 여기 코드에서는@ObservedObject가 아니라 @StateObject를 사용하고 있죠?

객체의 인스턴스를 만들때는 @ObservedObject가 아니라 

@StateObject를 대신해서 사용해야한다고 합니다.

 

잠깐 @StateObject를 알아보고갑시다!

 


@StateObject

 

@ObservedObject와 거의 같은 방식으로 작동을 합니다.

SwiftUI가 View를 다시 랜더링할 때, 실수로 취소되는 것을 방지해줍니다.

 

 

 


 

body 부분 코드는 간단한 출력부분이니 넘어가겠습니다!

 

 

짠~

잘 실행되네요!

 


 

EnvironmentObject

 

@EnvironmentObject는 보통 앱 전반에 걸쳐 공유되는 데이터에 사용 되며

 .environmentObject()를 통해 값을 전달할 수 있습니다

전달하는 object는 위의 Observed와 동일하게 ObservableObject 프로토콜을 준수해야 합니다

라고 구글링에서 설명해주는데..

 

 

공식 문서에서는 어떻게 설명하는지 봅시다1!!!

 

 

"부모 또는 조상 view에서 제공하는 observable object에 대한 property wrapper type입니다."

 

ㅎㅎ.. Overview를 봐야겠군요..

 

 

environment object는 obervable object가 변경될 때마다 현재 view를 무효화(invalidates)합니다. 

속성을 environment object로 선언하는 경우

environmentObject(_:) 수정자를 호출하여 조상 보기에 해당 모델 개체를 설정해야 합니다 .

 

라고 설명하고 있는데,

너무 길죠..?

 

쉽게 설명해보면 아래와 같은 뜻입니다!

"It's shared data that every view can read if they want to."

모든 view가 읽을 수 있는 shared data.

 

 

이번에도 예시를 봅시다!

 

위의 이미지처럼 구성할 것이라

ObservableObject에서 사용한 코드를 가져다쓰도록 하겠습니다! 

점수를 다음 View에서 출력되도록 할꺼에요!

 

우선, SceneDelegate 파일을 수정해야하는데요!

 

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
	...
}

 

위의 함수 내부에 코드를 추가해주어야합니다!!

위에서 모든 view가 읽을 수 있는 shared data라고 했죠?

그래서 여기에 설정해주는거에요!

 

// SceneDelegate.swift

let progress = UserProgress()
let contentViews = ContentViews().environmentObject(progress)

 

위처럼 코드를 추가해주면 됩니다!

ObervableObject 예시때 만들어 놓았던 UserProgress()를 progress 상수로 설정해주고,

저희가 만들어 놓은 ContentViews()에 progress의 데이터를 넘겨줍니다

 

이제 environomentObject 예제하기위해 만들어 놓은 파일로 갑시다!

 

 

저는 이렇게 한 파일에 다 예제를 넣어놨어요!

EnvironmentObjectProperty파일로 갑시다!

 

저희가 점수를 새로운 View에 넘겨주고 띄워주기로 했죠??

 

그래서 이렇게 간단하게 View를 구성해놨습니다!

 

struct ScoreView: View {
    @EnvironmentObject var progress: UserProgress
    
    var body: some View {
        Text("\(progress.score)")
    }
}

 

@EnvironmentObject로 progress 변수를 설정해주고 이를 출력해주네요!

 

자 이제 본격적으로 구성해봅시당

 

struct ContentViews: View {
    @EnvironmentObject var progress: UserProgress
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Your score is \(progress.score)")
                InnerView(progress: progress)
                NavigationLink(destination: ScoreView()) {
                    Text("다음 View")
                }.buttonStyle(PlainButtonStyle())
            }
        }
    }
}

 

위 ScoreView와 동일하게 @EnvironmentObject 해서 데이터를 받아주고

body 내부에 ObservableObject에서 작성했던 부분을 그대로 가져 옵니다

겉에 NavigationView가 추가 되었는데, 이는 NavigationLink해서 가져온 ScoreView를 보여지게해줍니다!

NavigationLink로 Text를 누르면 ScoreView로 넘어가도록 하고,

해당 Link를 ButtonStyle로 만들어줍니다.

 

struct EnvironmentObjectProPerty_Previews: PreviewProvider {
    static var previews: some View {
        ContentViews().environmentObject(UserProgress())
    }
}

 

마지막입니다!!

Previews 부분에 environmentObject를 해주어야 데이터를 Userprogress()를 받아올 수 있습니다!

이 부분을 적지 않으면 데이터를 읽어 올 수 없슴니당

 

 

짠!

완성~~!!

 

SwiftUI 너무 재밌죠?!!

처음에는 어려울 것 같아서 겁 먹었는데,

공부해보면서 너무 재미있었던 시간이었던 것 같아요!

SwiftUI를 더 많이 활용하고 사용하는 계기가 되었으면 좋겠습니다!

부족한 글 읽어주셔서 감사합니다 :)

 

ObservedObject 예제 사이트 

 

'UI > SwiftUI' 카테고리의 다른 글

[SwiftUI] Table View 만들기  (0) 2023.01.02
[SwiftUI] Property Wrapper - @State, @Binding  (4) 2023.01.02
[SwiftUI] Container View  (0) 2023.01.02
[SwiftUI] Life Cycle  (0) 2023.01.02
[UI] Storyborad(UIKit) vs SwiftUI  (0) 2023.01.02