- SwiftUI는 데이터 주도 방식으로 앱 개발을 강조
- 사용자 인터페이스 내의 뷰들은 기본 데이터의 변경에 따른 처리 코드를 작성하지 않아도 뷰가 업데이트 됨
- 데이터와 사용자 인터페이스 내의 뷰 사이에 게시자(publisher)와 구독자(subscriber)를 구축하여 가능
- 이를 위해 SwiftUI는 상태 프로퍼티, Observable 객체, Environment 객체를 제공
- 이들은 사용자 인터페이스의 모양과 동작을 결정하는 상태를 제공함
- SwiftUI에서 레이아웃을 구성하는 뷰는 코드 내에서 직접 업데이트 하지 않음 → 뷰와 바인딩된 상태 객체가 시간이 지남에 따라 변하면 그 상태에 따라 자동으로 뷰 업데이트 됨
상태 프로퍼티 (State Property)
- 상태에 대한 가장 기본적인 형태
- 뷰 레이아웃의 현재 상태(토글 버튼 활성화 여부, 텍스트 필드에 입력된 텍스트, 피커뷰의 현재 선택)을 저장하기 위해 사용됨
- String, Int 값 처럼 간단한 데이터 타입을 저장하기 위해 사용됨
- @State 프로퍼티 래퍼를 사용하여 선언
struct Chap21: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
Text("Hello")
}
}
→ 해당 값은 뷰에 속한 것이기 때문에 private 프로퍼티로 선언됨
- 상태 프로퍼티 값이 변경된 것은 프로퍼티가 선언된 뷰 계층구조를 다시 렌더링해야 한다는 신호!
- 즉, 계층 구조 내 모든 뷰를 빠르게 재생성하고 표시해야 함
- 그 프로퍼티에 의존하는 모든 뷰는 어떤 식으로든 최신 값이 반영되도록 업데이트 됨
- 프로퍼티 선언 후 레이아웃에 있는 뷰와 바인딩 가능함
- 바인딩 되어 있는 뷰에서 어떤 변경이 일어나면 해당 상태 프로퍼티에 자동으로 반영됨
- ex) wifiEnabled 프로퍼티가 바인딩되어 사용자가 토글 뷰를 조작하면 새로운 토글 설정 값으로 상태 프로퍼티를 자동 업데이트 해야 됨
- 상태 프로퍼티와의 바인딩은 프로퍼티 이름 앞에 ‘$’ 를 붙이면 됨
- ex) TextField 뷰는 사용자가 입력한 텍스트를 저장하는 데 사용하기 위해 userName이라는 상태 프로퍼티와 바인딩 됨
struct Chap21: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
VStack{
TextField("Enter user name", text: $userName)
}
}
}
→ TextField에 입력하게 되면 바인딩은 현재의 텍스트를 userName이라는 프로퍼티에 저장함
- 이 상태 프로퍼티에 변화가 생길 때마다 뷰 계층구조는 SwiftUI에 의해 다시 렌더링 됨
- 상태 프로퍼티에 값을 저장하는 것은 단방향 프로세스
- ex) 입력된 사용자의 이름을 반영하는 Text 뷰 생성
struct Chap21: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
VStack{
TextField("Enter user name", text: $userName)
Text(userName)
}
}
}
→ 텍스트를 입력하면 Text 뷰는 입력을 반영하기 위해 자동으로 업데이트 됨
- 중요! : userName 프로퍼티는 ‘$’ 표시 없이 사용되어야 함
- 상태 프로퍼티에 할당된 값(즉, 입력된 String 값)을 참조하려고 하기 때문
ex) Toggle 뷰와 wifiEnabled 상태 프로퍼티 간의 바인딩
struct Chap21: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
VStack{
Toggle(isOn: $wifiEnabled){
Text("Enable Wi-Fi")
}
}
TextField("Enter user name", text: $userName)
Text(userName)
Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
}
}
→ Toggle 뷰와 상태 프로퍼티 간의 바인딩 구현
- Image 뷰 : systemName 이미지 참조체를 이용함
상태 바인딩
- 상태 프로퍼티 : 선언된 뷰와 그 하위 뷰에 대한 현재 값
- 어떤 뷰가 하나 이상의 하위 뷰를 가지고 있고, 동일한 상태 프로퍼티에 접근하는 경우가 발생
- ex) 와이파이 뷰가 하위 뷰로 분리되는 상황
struct Chap21: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
VStack{
Toggle(isOn: $wifiEnabled){
Text("Enable Wi-Fi")
}
}
TextField("Enter user name", text: $userName)
Text(userName)
WifiImageView()
}
}
struct WifiImageView : View {
var body: some View{
// 오류 : Cannot find 'wifiEnabled' in scope
Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
}
}
→ 분리된 하위 뷰의 요소인 Image 뷰는 메인 뷰 범위 밖이므로 wifiEnabled는 정의되지 않은 변수가 됨
- @Binding 프로퍼티 래퍼를 사용하여 해결 가능
struct Chap21: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
VStack{
Toggle(isOn: $wifiEnabled){
Text("Enable Wi-Fi")
}
}
TextField("Enter user name", text: $userName)
Text(userName)
WifiImageView(wifiEnabled: $wifiEnabled)
}
}
struct WifiImageView : View {
@Binding var wifiEnabled : Bool
var body: some View{
Image(systemName: wifiEnabled ? "wifi" : "wifi.slash")
}
}
→ 하위 뷰가 호출될 때 상태 프로퍼티에 대한 바인딩을 전달해야 함
Observable 객체
- 상태 프로퍼티는 하위 뷰가 아니거나 상태 바인딩이 구현되어 있지 않은 다른 뷰는 접근할 수 없음
- 상태 프로퍼티는 일시적인 것이라 부모 뷰가 사라지면 그 상태도 사라짐
- Observable 객체는 여러 다른 뷰들이 외부에서 접근 할 수 있는 영구적인 데이터를 표현하기 위해 사용됨
- Observable 객체는 ObservableObject 프로토콜을 따르는 클래스나 구조체 형태를 취함
- Observable 객체는 일반적으로 시간에 따라 변경되는 하나 이상의 데이터 값을 모으고 관리하는 역할
- 또한, 타이머나 알림(notification)과 같은 이벤트 처리를 위해 사용됨
- Observable 객체는 게시된 프로퍼티 (published property)로서 데이터 값을 게시(publish) 한 후 Observer 객체는 게시자를 구독(subscribe)하여 게시된 프로퍼티가 변경될 때마다 업데이트 받음
- 상태프로퍼티와 마찬가지로 게시된 프로퍼티와 바인딩을 통해 데이터가 변경됨을 반영함
- Combine 프레임워크
- Combine 프레임워크에 포함되어 있는 Observable 객체는 게시자와 구독자 간의 관계를 쉽게 구축 가능
- 여러 게시자를 하나의 스트림으로 병합하는 것부터 게시된 데이터를 구독자 요구에 맞게 변형하는 것까지 다양한 작업을 수행하는 커스텀 게시자 구축 플랫폼을 제공함
- 최초 게시자와 최종 구독자 간에 복잡한 수준의 연쇄 데이터 처리 작업도 구현 가능
- Observable 객체의 게시된 프로퍼티를 구현하는 가장 쉬운 방법
- 프로퍼티 선언 시 @published 프로퍼티 래퍼 사용하는 것
- 이 래퍼는 프로퍼티 값이 변경될 때마다 모든 구독자에게 업데이트를 알림
ex) 두 개의 게시된 프로퍼티를 가진 간단한 observable 객체(DemoData) 선언
import Foundation
import Combine
// Observable 객체
class DemoData : ObservableObject {
// 게시
@Published var userCount = 0
@Published var currentUser = ""
init(){
// 데이터 초기화 코드
updateData()
}
func updateData(){
// 데이터를 최신 상태로 유지하기 위한 코드
}
}
- 구독자는 observable 객체를 구독하기 위해 @ObservedObejct 프로퍼티 래퍼를 사용함
- 구독하게 되면 상태프로퍼티와 같은 방법으로 게시된 프로퍼티에 접근함
ex) DemoData 클래스의 인스턴스를 구독하도록 설계된 예제
import SwiftUI
struct Chap21: View {
@ObservedObject var demoData : DemoData
var body: some View {
Text("\(demoData.currentUser), you are user number \(demoData.userCount)")
}
}
#Preview {
Chap21(demoData: DemoData())
}
Environment 객체
- 구독 객체는 특정 상태가 앱 내의 몇 몇 뷰에 의해 사용될 경우 적합함
- 다른 뷰로 이동(navigation)할 때 이동될 뷰에서도 동일한 구독 객체를 접근해야 한다면, 이동할 대상 뷰로 구독 객체에 대한 참조체를 전달해야 함
...
@ObservedObject var demoData : DemoData = DemoData()
...
NavigationLink(destination: SecondView(DemoData)){
Text("Next Screen")
}
→ SecondeView 라는 이름의 다른 뷰로 이동하면서 demoData 객체에 대한 참조체 전달
- 앱 내에 여러 뷰가 동일한 구독 객체를 접근하는 경우 복잡함 → Environment 객체 사용하여 해결
- Environment 객체는 Observable 객체와 같은 방식으로 선언
- 즉, ObservableObject 프로토콜을 따르며, 적절한 프로퍼티가 게시되어야 함
- 차이점 : Environment 객체는 SwiftUI 환경에 저장되며, 모든 뷰에서 접근 가능
- Environment 객체를 구독해야 하는 객체는 @ObservedObject 래퍼 대신 @EnvironmentObject 프로퍼티 래퍼 를 사용하여 참조
@EnvironmentObejct var demoData : DemoData
- Environment 객체는 옵저버 내에서 초기화 불가능하므로 접근하는 뷰가 화면 설정하는 동안 구성해야 함
→ SceneDelegate.swift 파일의 willConnectTo 메서드를 수정하는 작업이 필요함
let contentView = ContentView()
if let windowScene = scne as? UIWindowScene{
let window = UIWindow(windowScene : windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
// -> default code
// 아래와 같이 수정
let contentView = ContentView()
let demoData = DemoData()
if let windowScene = scne as? UIWindowScene{
let window = UIWindow(windowScene : windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
- 캔버스 수정
#Preview {
Chap21().environment(DemoData())
}
'SwiftUI' 카테고리의 다른 글
SwiftUI - Observable 객체와 Environment 객체 튜토리얼 (2) | 2024.01.27 |
---|---|
SwiftUI - 슬라이더 뷰(Slider View) 사용 (1) | 2024.01.22 |
SwiftUI - 스택, 프레임 (1) | 2024.01.13 |
SwiftUI - 커스텀 뷰 생성 (1) | 2024.01.10 |
SwiftUI - 프로젝트 분석 (0) | 2024.01.07 |