Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- SWIFT
- 스위프트
- 클린 코드
- Xcode
- 리펙토링
- ribs
- ios
- rxswift
- HIG
- clean architecture
- uitableview
- Refactoring
- swiftUI
- Observable
- UICollectionView
- collectionview
- 애니메이션
- uiscrollview
- map
- Protocol
- Human interface guide
- 리펙터링
- swift documentation
- UITextView
- MVVM
- Clean Code
- 리팩토링
- tableView
- RxCocoa
- combine
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - SwiftUI] 튜토리얼 - 10. @State (달러 prefix), ObservableObject, @Published, @EnvironmentObject 본문
iOS 튜토리얼 (SwiftUI)
[iOS - SwiftUI] 튜토리얼 - 10. @State (달러 prefix), ObservableObject, @Published, @EnvironmentObject
jake-kim 2022. 7. 12. 21:46@State
- View를 상속받는 뷰에서, 값을 변경할때 사용하는 프로퍼티 어노테이션
@State var n = 0
- 내부 구현부는 propertyWrapper를 사용한 형태
ex) @State를 붙이지 않은 경우 컴파일 에러 발생
struct ContentView: View {
var n = 0
var body: some View {
VStack {
Text("\(n)")
Button("Tap!!") {
n += 1 // Error: Left side of mutating operator isn't mutable: 'self' is immutable
}
}
}
}
ex) @State를 프로퍼티 앞에 붙일 경우 컴파일 에러 해결
struct ContentView: View {
@State var n = 0 // <-
var body: some View {
VStack {
Text("\(n)")
Button("Tap!!") {
n += 1
}
}
}
}
@State와 달러 prefix
ex) TabView구현 시 @State 프로퍼티와 달러 접두사 이용
struct ExampleView: View {
@State private var selection = Tab.home // <- State 사용
enum Tab {
case home
case setting
}
var body: some View {
TabView(selection: $selection) { // <- 달러 접두사
List {
ForEach([Int](0...20), id: \.self) {
Text("\($0)")
}
}
.tabItem {
Label("Home", systemImage: "house")
}
.tag(Tab.home)
Text("Tab page 2")
.tabItem {
Label("List", systemImage: "list.bullet")
}
.tag(Tab.setting)
}
}
}
- @State는 propertyWrapper로 아래처럼 구현되어 있다는 것을 먼저 이해
- 달러 prefix는 propertyWrapper의 projectedValue 문법에서 사용되는 것을 이해
- propertyWrapper의 projectedValue 관련 포스팅 글 참고
projectedValue이란?)
- propertyWrapper 내부에서 다른 값을 정의하여 사용하는쪽에서 달러 `$` 키워드로 해당 값에 접근할 수 있는 기능
- propertyWrapper에서 부가적인 프로퍼티 접근에 사용
propertyWrapper를 이용하여 RxSwift, RxCocoa를 구현한 @State 예시
import RxSwift
import RxCocoa
@propertyWrapper
struct State<T> {
private var relay: BehaviorRelay<T>
var wrappedValue: T {
get { self.relay.value }
set { self.relay.accept(newValue) }
}
var projectedValue: Observable<T> { self.relay.asObservable() }
init(wrappedValue initialValue: T) {
self.relay = .init(value: initialValue)
}
}
// 사용하는 쪽 - 초기화
@Behavior var someProperty = false
// 사용하는 쪽 - 구독
$someProperty
.subscribe { print($0) }
.disposeBag(disposeBag)
ObservableObject
- ObservableObject와 아래에서 알아볼 @Published는 SwiftUI부터 사용할 수 있는 Combine 프레임워크에서 제공
- AnyObject를 상속받고 있기 때문에 클래스형만 사용이 가능
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ObservableObject : AnyObject {
/// The type of publisher that emits before the object has changed.
associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never
/// A publisher that emits before the object has changed.
var objectWillChange: Self.ObjectWillChangePublisher { get }
}
- 아래에서 알아볼 @Published와 같이 사용되며, ObservableObject를 상속받아서 구현된 클래스의 값이 변경되면 이 값이 변경되었을때 처리를 손쉽게 구현이 가능
ex) ObservableObject를 서브클래싱하여 정의한 Contact 인스턴스는 objectWillChange 메소드 사용이 가능
class Contact: ObservableObject {
@Published var name: String
@Published var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func haveBirthday() -> Int {
age += 1
return age
}
}
let john = Contact(name: "John Appleseed", age: 24)
cancellable = john.objectWillChange
.sink { _ in
print("\(john.age) will change")
}
print(john.haveBirthday())
// Prints "24 will change"
// Prints "25"
@Published
- propertyWrapper이며, projectedValue가 존재하여, 사용하는 쪽에서 dollor '$'기호를 붙여서 사용이 가능
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct Published<Value> {
public init(wrappedValue: Value)
public init(initialValue: Value)
public struct Publisher : Publisher {
public typealias Output = Value
public typealias Failure = Never
public func receive<S>(subscriber: S) where Value == S.Input, S : Subscriber, S.Failure == Published<Value>.Publisher.Failure
}
public var projectedValue: Published<Value>.Publisher { mutating get set }
}
- @Published도 class에서만 사용이 가능
ex) @Published 사용
- 2번 출력되고 첫 번째 출력은 처음 초기화 할때 실행
import Combine
class Person { // <- class 만 사용 가능
@Published var age: Int
init(age: Int) {
self.age = age
}
}
let person = Person(age: 20)
person.$age
.sink { age in
print(age)
}
person.age = 10
// 20
// 10
@EnvironmentObject
- 마치 싱글톤처럼 한 인스턴스를 가지고, 모든 하위 뷰에서 접근이 가능한 인스턴스
- 하위 뷰라는 것은, body부분에 선언한 뷰들을 의미
ex) EnviormentObject
- EnviormentObject를 사용할땐 ObservableObject를 상속받는 클래스만 가능
- 해당 인스턴스를 ContentView에서 선언하고, 하위 뷰에서도 사용이 가능한지 테스트
class Configuration: ObservableObject {
@Published var version = 0 // 구독할 변수
}
- 하위 뷰 정의
- @EnviormentObject를 사용하여, 상위 뷰로부터 동기화될 변수 선언
struct SubView: View {
@EnvironmentObject var config: Configuration
var body: some View {
Text("version \(config.version)")
}
}
- ContentView에서 SubView()를 body안에 선언하면 서브뷰가 되므로, 자동으로 config 변수가 하위 뷰에게도 전달
- 단, 상위 뷰에서 .environmentObject를 선언
struct ContentView: View {
@StateObject var config = Configuration()
var body: some View {
NavigationView {
VStack {
Text("version = \(self.config.version)")
.padding()
Button("Up version") {
self.config.version += 1
}
.padding()
NavigationLink(destination: SubView()) {
Text("Go to SubView")
}
}
}
.environmentObject(self.config)
}
}
* 참고
https://stackoverflow.com/questions/56551131/what-does-the-dollar-sign-do-in-swift-swiftui
'iOS 튜토리얼 (SwiftUI)' 카테고리의 다른 글
Comments