본문 바로가기

Apple/SwiftUI

SwiftUI 04 - @State, @StateObject

반응형

SwiftUI에서는 @State, @StateObject와 같은 프로퍼티 래퍼로 뷰 내부, 뷰 사이에 데이터 공유를 할 수 있다.

@State

현재 뷰에서 비교적 간단한 데이터 공유(실시간 데이터 변화 감지 등)를 위해 사용한다. 뷰들 사이에 데이터 공유는 할 수 없다. 또 Struct 형태만 변수로 사용 가능하다.

아래코드를 보면 Score struct를 만들어서 뷰 내부에서 공유할 데이터 구조를 만들었고, Content 뷰 안에 score변수에서 Score struct의 인스턴스를 @State 프로퍼티 래퍼로 선언하였다. 텍스트 필드에서 내용을 수정하면 실시간으로 텍스트에 반영이 되고, 버튼을 클릭하면 isScore 값이 변경되어 텍스트 메시지도 바뀐다.

import SwiftUI

struct Score {
    var playerName: String = "sonny"
    var isScore: Bool = true
}

struct ContentView: View {
    @State private var score = Score()

    var body: some View {
        VStack {
            if score.isScore {
                Text("\(score.playerName)가 골을 넣었다.")
                    .font(.title)
                    .fontWeight(.bold)
            } else {
                Text("\(score.playerName)가 골을 못 넣었다.")
                    .font(.title)
                    .fontWeight(.bold)
            }
            
            TextField("선수 이름을 입력하세요", text: $score.playerName)
            Button("슛") {
                score.isScore.toggle()
            }
            .padding(.horizontal, 30)
            .padding(.vertical, 10)
            .foregroundColor(Color.white)
            .background(Color.mint)
            .cornerRadius(20)
        }
        .padding(.horizontal, 30)
    }
}

@State를 이용하면 struct를 이용해서 간단하게 뷰 내부의 값을 공유할 수 있다.

하지만 @State 프로퍼티 래퍼로는 클래스 데이터 구조를 사용할 수 없다. 위 예제코드에서 Score struct를 class로 바꾸면 아래와 같이 텍스트 필드의 내용을 바꾸거나 버튼을 눌러도 텍스트의 내용이 바뀌지 않는 것을 볼 수 있다.

@State로는 class 인스턴스를 사용하지 못한다.

 

class를 사용해야 할 때는 @StateObject, @Published 프로퍼티래퍼, Observable 프로토콜을 사용해야 한다.

@StateObject

맨 위의 코드에서 Score struct를 class로만 변경한 코드이다. 이대로는 위에서 말한 것처럼 제대로 동작하지 않는다. 

class Score {
    var playerName: String = "sonny"
    var isScore: Bool = true
}

struct ContentView: View {
    @State private var score = Score()

    var body: some View {
        VStack {
            if score.isScore {
                Text("\(score.playerName)가 골을 넣었다.")
                    .font(.title)
                    .fontWeight(.bold)
            } else {
                Text("\(score.playerName)가 골을 못 넣었다.")
                    .font(.title)
                    .fontWeight(.bold)
            }
            
            TextField("선수 이름을 입력하세요", text: $score.playerName)
            Button("슛") {
                score.isScore.toggle()
            }
            .padding(.horizontal, 30)
            .padding(.vertical, 10)
            .foregroundColor(Color.white)
            .background(Color.mint)
            .cornerRadius(20)
        }
        .padding(.horizontal, 30)
    }
}

다른 뷰들에게 score class의 프로퍼티의 값이 변경되었다고 알려주기 위해서  @Published 프로퍼티 옵저버를 사용해야 한다. 프로퍼티 옵저버는 프로퍼티 값의 변화를 관찰하는 것으로 새 값이 설정되면 호출된다. 

// Score 클래스 프로퍼티의 변화를 알려주기 위한 @Published 프로퍼티 옵저버 작성
class Score {
    @Published var playerName: String = "sonny"
    @Published var isScore: Bool = true
}

이 방법만 사용하는 것으로 class의 프로퍼티를 사용할 수 있으면 좋겠지만 몇 가지 세팅이 더 필요하다. @State 역할(SwiftUI의 뷰를 리로드하라고 알려주는)을 대신 하는 프로퍼티 래퍼를 작성해주어야 하는데 그것이 바로 @StateObject 프로퍼티 래퍼이다. private은 있어도 작동하고 없어도 작동하지만, 다른 뷰와 데이터를 공유하려고 할 때는 private을 없애야 한다.

// @StateObject 설정
@StateObject private var score = Score()

마지막으로 할 작업은 class에 Obeservable 프로토콜을 준수하라고 작성하는 것이다. @StateObject 프로퍼티 래퍼는 Observable 프로토콜을 준수하는 class에 대해서만 사용할 수 있다. 하지만 일반적인 다른 프로토콜과는 다르게 Observable 프로토콜을 작성한다고 해서 추가적으로 내용을 작성해야 하는 것은 아니며, 변화를 모니터링하기를 원한는 class라는 의미라고 생각하면 된다.

그래서 최종적으로 뷰 들 사이에 class 형식의 데이터를 공유하기 위해서 최종 코드 형태는 아래와 같다.

import SwiftUI

class Score: ObservableObject {
    @Published var playerName: String = "sonny"
    @Published var isScore: Bool = true
}

struct ContentView: View {
    @StateObject private var score = Score()

    var body: some View {
        VStack {
            if score.isScore {
                Text("\(score.playerName)가 골을 넣었다.")
                    .font(.title)
                    .fontWeight(.bold)
            } else {
                Text("\(score.playerName)가 골을 못 넣었다.")
                    .font(.title)
                    .fontWeight(.bold)
            }
            
            TextField("선수 이름을 입력하세요", text: $score.playerName)
            Button("슛") {
                score.isScore.toggle()
            }
            .padding(.horizontal, 30)
            .padding(.vertical, 10)
            .foregroundColor(Color.white)
            .background(Color.mint)
            .cornerRadius(20)
        }
        .padding(.horizontal, 30)
    }
}

잘 동작한다.

class 를 통해 우리의 상태(데이터 등)를 외부객체에 저장할 수 있고, 이를 통해 뷰들 간에 같은 값을 가리키게 할 수 있다는 것이다. 

반응형

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

SwiftUI 05 - @Binding 사용법  (1) 2022.03.26
SwiftUI03 - Stepper  (0) 2022.03.14
SwiftUI02 - Button  (0) 2022.03.14
SwiftUI01 - Stack(HStack, VStack, ZStack)  (0) 2022.03.14