상태 프로퍼티, Observable 객체와 Environment 객체,
모두 사용자 인터페이스의 모양과 동작을 결정하는 "상태"를 제공
상태 프로퍼티를 이용한 바인딩
$variableName
→ 바인딩을 걸어줄 때 $
를 붙여줌variableName
→ 참조된 변수를 호출할 때는 변수명만 사용함struct ContentView: View {
@State private var userName: String = ""
var body: some View {
VStack {
TextField("이름을 입력하세요", text: $userName)
Text(userName)
}
}
}
기존 뷰 이외의 곳에서 상태 변수 사용
@Binding를 이용한 직접적인 상태 바인딩
struct ContentView: View {
@State private var userName: String = ""
@State private var wifiEnable: Bool = false
var body: some View {
VStack {
Toggle(isOn: $wifiEnable) {
WifiResultView(wifiEnable: $wifiEnable)
}
}
TextField("이름을 입력하세요", text: $userName)
. padding(5)
.border(Color.gray)
Text(userName)
.foregroundColor(.gray)
.font(.callout)
}
.padding()
.font(.largeTitle)
}
}
struct WifiResultView: View {
@Binding var wifiEnable: Bool
var body: some View {
Image(systemName: wifiEnable ? "wifi" : "wifi.slash")
Text(wifiEnable ? "Wi-Fi 켜짐" : "Wi-Fi 꺼짐")
}
}
(변수명: $변수명)
넣어서 바인딩 해주기@State private
는 해당 뷰 안에서만 사용되고, 프로퍼티의 값이 바뀌면 화면이 다시 그려진다$
가 붙은 바인딩된 매개변수를 사용하는 것 → 바인딩 프로토콜로 인해 값이 연동됨 (갖다 쓰고 변경 값도 전해준다)struct ContentView: View {
@State private var wifiEnable: Bool = true
@State private var username: String = ""
var body: some View {
VStack {
Toggle(isOn: $wifiEnable) {
Text("Enable Wifi")
}
TextField("Enter Username", text: $username)
Text("username: \(username)")
WifiImageView(wifiEnable: $wifiEnable)
}
.padding()
}
}
struct WifiImageView: View {
@Binding var wifiEnable: Bool
var body: some View {
VStack {
Image(systemName: wifiEnable ? "wifi" : "wifi.slash")
...
하지만... 상태 뷰는 일시적이라 부모 뷰가 사라지면 해당 상태도 사라진다는 것..🥲
그래서 나왔다, Observable.
게시자와 구독자의 개념
Observable 객체 - 프로퍼티를 선언할 때 @Published
프로퍼티 래퍼를 사용해 게시된 프로퍼티 구현하면 래퍼 프로퍼티 값이 변경될 때마다 모든 구독자에게 업데이트를 알려준다
Observer 객체 - 구독자는 observable 객체를 구독하기 위해 @ObservedObject
프로퍼티 래퍼를 사용한다 / 구독하게 되면 그 뷰 및 모든 하위 뷰가 상태 프로퍼티에서 사용했던 것과 같은 방식으로 게시된 프로퍼티에 접근하게 된다
Observable 객체 구현
// 1. 내부 내용들이 바뀔 예정!
class DemoData: ObservableObject {
// Published의 의미는 "다음과 값이 바뀌면 알려주겠다"는 뜻
// 2. 구체적으로 이런 내용이 바뀔 예정!
@Published var userCount: Int = 0
@Published var currentUser: String = ""
init() {
updateData()
}
func updateData() {
userCount += 1
currentUser = "ned"
}
}
struct ContentView: View {
@ObservedObject var demoData: DemoData = DemoData()
var body: some View {
NavigationView {
VStack {
Text("userCount: \(demoData.userCount)")
Text("currentUser: \(demoData.currentUser)")
.padding()
Button(action: {
demoData.updateData()
}) {
Text("Update Data")
}
...
각 프로퍼티 래퍼의 역할:
@ObservableObject
= “이거 좀 지켜봐줘!”
@Published
= “얘네가 바뀔 예정이니까 얘네 값을 전달줄게”
@ObserverObject
= “오케이 바뀐 값 좀 줘봐”
구독자 선언 시 초기화 생략
@ObservedObject var demoData: DemoData = DemoData()
← 이 초기화 부분을 생략해도 됨struct ContentView: View {
@ObservedObject var demoData: DemoData
var body: some View {
NavigationView {
VStack {
// 프리뷰 뷰
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(demoData: DemoData())
}
}
// 앱 파일
@main
struct StateWrapUpApp: App {
var body: some Scene {
WindowGroup {
ContentView(demoData: DemoData())
}
}
}
struct ContentView: View {
@EnvironmentObject var demoData: DemoData
var body: some View {
NavigationView {
VStack {
Text("userCount: \(demoData.userCount)")
Text("currentUser: \(demoData.currentUser)")
.padding()
Button(action: {
demoData.updateData()
}) {
Text("Update Data")
}
NavigationLink(destination: SecondView()) {
Text("Push")
.padding()
}
}
...
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(DemoData())
}
}
// 앱 파일
@main
struct StateWrapUpApp: App {
let demoData: DemoData = DemoData()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(demoData)
}
}
}
Reference:
핵심만 골라 배우는 SwiftUI 기반의 iOS 프로그래밍