SwiftUI @State, @ObservedObject, @EnvironmentObject 익히기

 앱을 개발하려고 보면, 사용자의 입력을 받든, 서버에서 값을 가져오든, 변수에 값을 저장하고, 그 값의 변화에 따라 화면의 UI 가 바뀐다. objective-c와 이전의 swift 에서는 어떻게 했는지 까먹었으나, SwiftUI 는 새로 공부해보면서 정리하려고한다.


There are several concepts regarding the binding between data and view of the data in SwiftUI. Below is what I understood from other developer's blogs.


@State

The most basic annotation that we can use. The important thing is it can be only used in a specific view.  So usually I'd use with `private` keyword.

With @State basically,  struct View 밖의 special memory 에 저장되고, it is managed by SwiftUI. 이 값이 바뀔 때마다 SwiftUI 는 해당 view 를  rebuild 한다.


@State private var showFavorited: Bool = false

var body: some View {

    VStack{
        Button(
            action: { self.showFavorited.toggle() },
            label: { Text("Change filter") }
        )

        if !self.showFavorited{
            Text("asdf")
        }else{
            Text("zxcv")

        }
    }
}


@ObservedObject

This keyword means that the data can be observed in many independent views.  In general, class should extends ObservableObject. And the property inside the class can annotated with @Published. Then struct view which uses this class can observe this value and the view will rebuild when change is made.

다만, view 에서 접근 하기 위해서는 ObservableObject 를 init  할때 매개변수로 전달 해주어야 한다.


let pod = PodcastPlayer();
let contentView = ContentView(player:pod)


final class PodcastPlayer: ObservableObject {
    @Published private(set) var isPlaying: Bool = false

    func play() {
        isPlaying = true
    }

    func pause() {
        isPlaying = false
    }
}


struct ContentView: View {
    

    @ObservedObject var player: PodcastPlayer

    var body: some View {
        List {
            Button(
                action: {
                    if self.player.isPlaying {
                        self.player.pause()
                    } else {
                        self.player.play()
                    }
            }, label: {
                    Text(player.isPlaying ? "you can pause": "you can play")
                }
            )
        }
    }
}


@EnvirnmentObject

The class also should inherit from ObservableObject. And It should have some variables with @Published annotation that will be observed.

But, ObservedObject 와는 달리, EnvinronmentObject can be set at first, in the SceneDelegate class. And in the SceneDelegate, we can create the class and then put in the view with .environmentObject(). By doing this, 모든 child views 들은 이 object 의 변화를 감지할 수 있다.

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

    let store = ReposStore(service: .init())
    let contentView = ContentView().environmentObject(store)

    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView)
        self.window = window
        window.makeKeyAndVisible()
    }
}

class ReposStore: ObservableObject{
    @Published private(set) var repos: [Repo] = []
    
    private let serviceGithubService
    init(service: GithubService) {
        self.service = service
    }
    
    func fetch(matching query:String){
        service.search(matching: query){[weak self] result in
            DispatchQueue.main.async {
                switch result {
                case .success(let repos): self?.repos = repos
                case .failure: self?.repos = []
                }
            }
        }
    }
}

struct ContentView: View {
    @State private var query: String = "Swift"
    @EnvironmentObject var repoStore: ReposStore

    var body: some View {
        NavigationView{
            List{
                TextField("Type someting...", text: $query, onCommit: fetch)
                ForEach(repoStore.repos){ repo in
                    RepoRow(repo: repo)
                }
            }.navigationBarTitle(Text("Search"))
        }.onAppear(perform: fetch)
    }

    private func fetch() {
        repoStore.fetch(matching: query)
    }
}


Conclusion

 간단하게 이해한 바대로 정리하자면,  state 는 그 상태가 바뀔 때마다 view 를 다시 그린다.  ObservableObject 를 implement 한 class 는 Published 변수를 가질 수 있고,  published  값들에 관심이 있는 각 객체들은 이것을 observe 하면서 값이 변할 때마다 화면을 다시 그리는데, environment 단에서 detect 가 가능하면 @EnvironmentObject 가 되고,  init 을 통해 접근 가능한 여러 view  들에서 접근하면 @ObservedObject 가 된다. Observable 한 객체가 Published  한 것을 envObj  나 observedObj 를 통해 관찰하고, 변경시 UI 역시 다시 그리는 것이다.

@Binding 이라는 것도 있는데, 이것은 @State  객체를 child View 에게 reference 하도록 넘김으로써, child view 가 이 값을 read, write 할 수 있게 한다. 하지만 그렇다고 해서 child view 가 이값의 변화를 observe 할 수 있는것은 아니다.

Comments

Popular posts from this blog

삼성전자 무선사업부 퇴사 후기

개발자 커리어로 해외 취업, 독일 이직 프로세스

코드리뷰에 대하여