이 게시글 내용은 공부하면서 점차 수정해 나갈 예정입니당
2023.6.26 13:31 작성
-----------
- SwiftUI → 데이터 주도 방식으로 앱 개발을 강조
- publlisher와 subscriber 구축
- 데이터와 UI 뷰 사이 데이터 변경에 따른 처리 코드 없이 뷰가 업데이트 가능.
이를 위해 SwiftUI는 상태 프로퍼티, Observable 객체, Environment 객체를 제공
→ UI의 모양과 동작을 결정하는 상태 제공
뷰와 바인딩된 상태 객체가 상태에 따라 자동으로 뷰 업데이트
상태 프로퍼티, State property
- 상태에 대한 가장 기본적인 형태
- 뷰 레이아웃의 현재 상태를 저장하기 위해 사용
- 토글 버튼 활성화 여부, 텍스트 필드의 텍스트, 피커 뷰의 현재 선택 … 등
- 간단한 데이터 타입을 저장하기 위해 사용
- @State 프로퍼티 래퍼를 사용해 선언
struct ContentView: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
...
}
}
- 상태 값은 해당 뷰에 속한것이므로 private 프로퍼티로 선언
- — private에 대해 추가적으로 작성하기
- 상태 프로퍼티 값이 변경되었다 → 프로퍼티가 선언된 뷰를 다시 렌더링해야 함!!!!!
- 해당 프로퍼티에 의존하는 뷰는 최신 값이 반영되도록 업데이트됨!!
- 상태 프로퍼티를 선언하면, 레이아웃의 뷰와 바인딩 가능
- 바인딩된 뷰에서 변화가 발생 → 해당 상태 프로퍼티에 자동 반영
- 바인딩은 프로퍼티 이름 앞이 $ 를 붙혀 표현.
- $가 붙으면 프로퍼티 래퍼 자체를 받음 -> 자체 변경 가능!!
struct ContentView: View {
@State private var wifiEnabled = true
@State private var userName = ""
var body: some View {
VStack {
TextField("Enter user name", text: $userName)
}
}
}
- TextField에 변화가 발생하면
- → 입력된 값을 userName 프로퍼티에 반영!!
- 상태 프로퍼티 변경 시마다 뷰는 다시 렌더링!!!
- @State는 해당 View 외부로는 사용할 수가 없고 private 형태로 내부에서만 사용가능!!
- 만약 뷰가 나뉘어 (예를들어) 토글에 따라 리스트 값을 바꿔야 하는 경우 -> 두 개의 뷰가 하나의 State를 참조해야하는 경우
- @Binding을 사용하면 됩니당~!
상태 바인딩
- 상태 프로퍼티는 선언된 뷰와 그 하위 뷰에 대한 현재 값임.
- 만약 어떤 뷰가 하나 이상의 하위 뷰를 가지고 있고, 동일한 상태 프로퍼티에 대해 접근해야 한다면?
WifiImageView는 wifiEnable에 접근하고 싶으나 메인 뷰의 범위 밖이라 접근할 수 없다.
- 이때 @Binding 프로퍼티 래퍼를 이용해 선언하면 해결이 가능.
- 요런 코드를
...
VStack {
Text("Wi-Fi Status")
Toggle(isOn: $wifiEnable) {
Text("Enable Wi-Fi")
}
WifiImageView()
}
}
}
struct WifiImageView: View {
var body: some View {
Image(systemName: wifiEnable ? "wifi" : "wifi.slash")
}
}
- 요렇게
import SwiftUI
struct ContentView: View {
@State var wifiEnable: Bool = false
var body: some View {
VStack {
Text("Wi-Fi Status")
Toggle(isOn: $wifiEnable) {
Text("Enable Wi-Fi")
}
WifiImageView(wifiEnable: $wifiEnable) //상태 프로퍼티 바인딩 전달!
// 이러면 WifiImageView 내부에서 wifiEnable이 사용 가능!!!!!!!
}
}
}
struct WifiImageView: View {
@Binding var wifiEnable: Bool //@Binding을 이용해 선언
var body: some View {
Image(systemName: wifiEnable ? "wifi" : "wifi.slash")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}VStack {
Text("Wi-Fi Status")
Toggle(isOn: $wifiEnable) {
Text("Enable Wi-Fi")
}
WifiImageView()
}
}
}
struct WifiImageView: View {
var body: some View {
Image(systemName: wifiEnable ? "wifi" : "wifi.slash")
}
}
- State값을 받는 것만으로 여러 개의 뷰가 동시에 참조할 수 있음.
- 그러면 만약 뷰 밖의 클래스에서 사용하려면 어떻게 해야하징?
- @ObservableObject 를 쓰면 됨~!!!
Observable 객체
- 상태 프로퍼티는 하위 뷰가 아니거나, 상태 바인딩이 구현되어 있지 않은 뷰는 접근할 수 없음.
- 또한 일시적인 것이어서 부모 뷰가 사라지면 상태도 사라짐
Observable 객체
- 시간에 따라 변경되는 하나 이상의 데이터 값을 모으고 관리하는 역할 담당
- 타이머, 알림 … 등 이벤트 처리위해 사용될 수 있음
- 시간이 지남에 따라 반복적으로 변하는 동적 데이터를 래핑하는 데 사용하면 강려크함.
- 다른 뷰들이 외부에서 접근할 수 있는 영구적인 데이터를 표현하기 위해 사용됨
- 뷰 밖의 클래스에서 접근해야 할 경우 사용!
- ObservableObject 프로토콜을 따르는 클래스나 구조체 형태를 취함.
- Observable 객체는 **게시된 프로퍼티(published property)**로서 데이터 값을 게시(publish)함.
- Observer 객체는 게시자를 **구독(subscribe)**하여 업데이트 받음
Combine 프레임워크
- 여러 게시자를 하나의 스트림으로 병합, 게시된 데이터를 병합 등 커스텀 게시자 구축 플랫폼 제공
- Observable객체가 포함되어 있음
- 게시자와 구독자의 관계를 쉽게 구축하게 하기 위해
- 게시된 프로퍼티를 구현하는 쉬운 방법
- → 선언 시 @Published 프로퍼티 래퍼 사용
import SwiftUI
import Combine
class DemoData : ObservableObject { // 별로의 클래스에서 ObservableObject 상속받음
// 값이 변경되는지 확인하는 @Published 어노테이션
@Published var userCount = 0
@Published var currentUser = ""
init() {
// 데이터를 초기화하는 코드
updateData()
}
func updateData() {
// 데이터를 최신 상태로 유지하기 위한 코드
}
}
- 구독자는 observable 객체를 구독하기 위해 @ObservedObject 프로퍼티 래퍼를 사용
- 구독 후 동일한 방식으로 게시된 프로퍼티 접근
import SwiftUI
import Combine
struct ContentView: View {
@ObservedObject var demoData : DemoData
var body: some View {
Text("\\(demoData.currentUser), you are user number \\(demoData.userCount)")
}
}
class DemoData : ObservableObject {
@Published var userCount = 0
@Published var currentUser = "홍길동"
init() {
// 데이터를 초기화하는 코드
updateData()
}
func updateData() {
// 데이터를 최신 상태로 유지하기 위한 코드
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(demoData: DemoData())
}
}
- 만약 **@Published를 지우고 실행**한다면 버튼을 누를 때 값은 바뀌지만 UI에서는 변경이 안됨.
- 이를 이용해 특정한 조건에서 UI를 변경시킬 수 있음.
class CountRepo: ObservableObject {
// count에 @Published 를 없앰
var count:Int = 0 {
willSet(newVal){
print(newVal % 5)
if(newVal % 5 == 0){
objectWillChange.send()
}
}
}
}
struct ContentView: View {
@ObservedObject var countRepo = CountRepo()
var body: some View {
VStack {
Text("\\(self.countRepo.count)").font(.largeTitle)
Button("숫자증가") {
self.countRepo.count += 1
}
}
}
}
Environment 객체
- 어떤 뷰에서 다른 뷰로이동(Navigation)하는데 이동될 뷰에서도 동일한 구독 객체에 접근해야 한다면
- 이동할 때 대상 뷰로 구독 객체에 대한 참조체를 전달해야 함.
...
@ObservedObject var demoData : DemoData = DemoData()
...
NavigationLink(destination: SecondView(demoData)) {
Text("Next Screen")
}
- NavigationLink 는 SecondView 라는 다른 뷰로 이동하기 위해 사용하며,
- demoData 객체에 대한 참조값을 전달한다.
- 이럴 경우 Environment 객체를 사용하는게 합리적!!
- Observable 객체와 같은 방식으로 선언하며, ObservableOjbect 프로토콜을 따라야 함.
- EnviromentObject는 별도로 값을 전달해주지 않아도 상속받는 부모로부터 함께 적용되는 오브젝트임!
- 뷰에서 뷰로 전달할 필요없이 모든 뷰가 접근할 수 있도록 한다!
@EnvironmentObject var demoData: DemoData //다음과 같이 해당 객체 참조
- Environment 객체는 옵저버 내에서 초기화할 수 업슴
- 프리뷰에서도 보려면 다음과 같이 수정해야 함.
- struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView().environmentObject(DemoData()) } }
'iOS > SwiftUI' 카테고리의 다른 글
SwiftUI 내부 동작 원리 (0) | 2023.12.11 |
---|---|
SwiftUI Grid (0) | 2023.07.23 |
SwiftUI 뷰의 변화 감지 (0) | 2023.07.09 |