본문 바로가기

iOS/SwiftUI

SwiftUi DataBinding

이 게시글 내용은 공부하면서 점차 수정해 나갈 예정입니당

2023.6.26 13:31 작성

-----------

 

  • SwiftUI → 데이터 주도 방식으로 앱 개발을 강조
  • publlishersubscriber 구축
    • 데이터와 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