안녕하세요! 피피아노입니다 🎵
이번 포스팅에서는 SwiftUI에서 사용하는 Property Wrapper들의 개념과 각 특징들에 대해서 정리를 해보려고 합니다.
그럼 바로 시작하겠습니다!
프로퍼티 래퍼(Property Wrapper)란?
프로퍼티 래퍼는 Swift 5.1에서 도입된 기능으로, 프로퍼티의 동작을 캡슐화하고 재사용할 수 있도록 해줍니다. 프로퍼티 래퍼를 사용하면 코드 중복을 줄이고, 프로퍼티에 대한 접근 방식이나 값 변환 로직을 중앙 집중화할 수 있습니다. 이를 통해 프로퍼티에 대한 공통 동작을 일관되게 적용할 수 있습니다.
프로퍼티 래퍼의 기본 구조
프로퍼티 래퍼는 @propertyWrapper 애트리뷰트로 정의된 구조체나 클래스를 통해 구현됩니다. 일반적으로 wrappedValue라는 이름의 프로퍼티를 포함하며, 이 프로퍼티가 실제 값을 저장합니다.
자 그럼 이제 프로퍼티 래퍼들의 종류에 대해서 알아보겠습니다!
@State
@State는 뷰의 내부 상태를 관리하는 데 사용됩니다. 뷰의 상태가 변경되면 뷰가 다시 렌더링됩니다. 이는 간단한 값(예: 정수, 문자열)을 상태로 유지해야 할 때 유용합니다. @State는 주로 사용자 인터페이스의 일부로써 상태를 유지하고 관리하는 데 사용됩니다.
특징
- @State로 선언된 변수는 뷰의 내부 상태로, 해당 뷰에서만 유효합니다.
- 상태 값이 변경되면 뷰가 자동으로 다시 렌더링됩니다.
- 뷰의 생명주기 동안 상태가 유지됩니다.
struct CounterView: View {
@State private var count: Int = 0
var body: some View {
VStack {
Text("Count: \(count)")
.font(.largeTitle)
.padding()
Button(action: {
count += 1
}) {
Text("Increment")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
}
- CounterView라는 구조체를 정의하며, 이는 View 프로토콜을 준수합니다.
- @State 속성 래퍼를 사용하여 count라는 변경 가능한 상태 변수를 선언합니다.
- body 프로퍼티에서 뷰의 내용을 정의합니다.
- VStack을 사용하여 요소들을 수직으로 배치합니다.
- Text 뷰로 현재 카운트 값을 표시합니다.
- Button을 생성하여 카운트를 증가시키는 기능을 제공합니다.
- 버튼의 외관을 커스터마이즈하여 파란색 배경과 흰색 텍스트, 둥근 모서리를 가지도록 합니다.
이 뷰는 화면에 카운트 값을 표시하고, 버튼을 누를 때마다 카운트가 1씩 증가하는 간단한 인터랙티브 요소를 제공합니다.
@Published
@Published는 ObservableObject 프로토콜을 준수하는 클래스 내에서 사용되는 프로퍼티 래퍼입니다. 이 래퍼를 사용하면 프로퍼티 값이 변경될 때 자동으로 변경 사항을 알립니다. SwiftUI와 결합하면, 해당 값을 관찰하는 뷰가 자동으로 업데이트됩니다.
특징
- 클래스 전용: @Published는 ObservableObject 프로토콜을 준수하는 클래스에서만 사용할 수 있습니다. 구조체나 열거형에서는 사용할 수 없습니다.
- 프로퍼티 변경 감지: @Published로 선언된 프로퍼티가 변경될 때마다 자동으로 변경 사항을 알립니다. 이 변경 사항은 해당 객체를 관찰하는 뷰나 구독자에게 전달됩니다.
- Combine 프레임워크와의 결합: @Published는 내부적으로 Combine 프레임워크를 사용하여 변경 사항을 알립니다. Publisher를 통해 프로퍼티의 변경 사항을 구독할 수 있습니다.
- 자동 업데이트: @Published 프로퍼티를 포함하는 ObservableObject를 @ObservedObject나 @StateObject로 선언하여 뷰에서 사용하면, 해당 프로퍼티가 변경될 때마다 뷰가 자동으로 업데이트됩니다.
import SwiftUI
import Combine
class TimerModel: ObservableObject {
@Published var time: String = "00:00"
init() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
self.time = dateFormatter.string(from: Date())
}
}
}
struct TimerView: View {
@ObservedObject var timerModel = TimerModel()
var body: some View {
Text("Current Time: \(timerModel.time)")
.font(.largeTitle)
.padding()
}
}
- ObservableObject 프로토콜: TimerModel 클래스가 ObservableObject 프로토콜을 준수합니다. 이 프로토콜은 객체가 변경되었음을 알릴 수 있도록 합니다.
- @Published 프로퍼티 래퍼: @Published var time: String = "00:00": time 프로퍼티가 변경될 때마다 자동으로 변경 사항을 알립니다. 이를 통해 해당 프로퍼티를 관찰하는 뷰가 업데이트됩니다.
- 이니셜라이저: Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true): 1초마다 실행되는 타이머를 설정하여 현재 시간을 업데이트합니다.
- 뷰: @ObservedObject var timerModel = TimerModel(): TimerModel 객체를 관찰하여, time 프로퍼티가 변경될 때마다 뷰가 자동으로 업데이트됩니다.
- Text("Current Time: \(timerModel.time)"): time 값을 표시하는 텍스트 뷰로, time이 변경될 때마다 텍스트가 업데이트됩니다.
@Binding
@Binding은 부모 뷰와 자식 뷰 간의 데이터 공유를 가능하게 합니다. 자식 뷰에서 상태를 직접 관리하지 않고, 부모 뷰로부터 전달받아 사용합니다. @Binding은 상태 변경을 부모 뷰에 전달하는 데 유용합니다.
특징
- @Binding으로 선언된 변수는 부모 뷰에서 관리되는 상태를 자식 뷰에서 사용할 수 있게 합니다.
- 자식 뷰에서 상태를 변경하면 부모 뷰의 상태도 함께 변경됩니다.
- 부모-자식 뷰 간의 데이터 동기화를 용이하게 합니다.
struct ParentView: View {
@State private var isOn: Bool = false
var body: some View {
VStack {
ToggleView(isOn: $isOn)
Text(isOn ? "Switch is ON" : "Switch is OFF")
.padding()
}
.padding()
}
}
struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle(isOn: $isOn) {
Text("Toggle Switch")
.padding()
}
.padding()
}
}
ParentView 구조체
- @State 속성 래퍼를 사용하여 isOn 불리언 상태 변수를 선언합니다.
- VStack을 사용하여 요소들을 수직으로 배치합니다.
- ToggleView를 자식 뷰로 포함하며, $isOn을 바인딩으로 전달합니다.
- 토글 상태에 따라 "Switch is ON" 또는 "Switch is OFF"를 표시하는 Text 뷰를 포함합니다.
ToggleView 구조체
- @Binding 속성 래퍼를 사용하여 isOn 변수를 부모 뷰와 연결합니다.
- Toggle 뷰를 사용하여 실제 토글 스위치를 생성합니다.
- 토글 옆에 "Toggle Switch" 레이블을 표시합니다.
@ObservedObject
@ObservedObject는 외부에서 상태를 관리하는 객체를 뷰에서 관찰합니다. ObservableObject 프로토콜을 준수하는 객체와 함께 사용됩니다. 주로 뷰 모델(ViewModel)과 함께 사용하여 뷰의 상태를 관리합니다.
특징
- @ObservedObject로 선언된 객체는 ObservableObject 프로토콜을 준수해야 합니다.
- 객체 내의 상태가 변경되면 이를 관찰하는 뷰가 자동으로 다시 렌더링됩니다.
- 여러 뷰에서 동일한 객체를 관찰할 수 있습니다.
class TimerModel: ObservableObject {
@Published var time: String = "00:00"
init() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
self.time = dateFormatter.string(from: Date())
}
}
}
struct TimerView: View {
@ObservedObject var timerModel: TimerModel
var body: some View {
Text("Current Time: \(timerModel.time)")
.font(.largeTitle)
.padding()
}
}
이 코드는 SwiftUI와 Combine 프레임워크를 사용하여 실시간으로 현재 시간을 표시하는 타이머 기능을 구현합니다.
이 구조는 다음과 같은 특징을 가집니다.
- MVVM (Model-View-ViewModel) 패턴을 따릅니다. TimerModel이 ViewModel 역할을 합니다.
- ObservableObject와 @Published를 사용하여 반응형 프로그래밍을 구현합니다.
- 뷰와 모델을 분리하여 코드의 재사용성과 유지보수성을 향상시킵니다.
- Timer 클래스를 사용하여 주기적인 작업을 수행합니다.
이 코드를 사용하면, 뷰가 자동으로 1초마다 업데이트되어 현재 시간을 실시간으로 표시합니다.
@StateObject
@StateObject는 뷰의 생명 주기 동안 상태를 관리하는 객체를 선언할 때 사용됩니다. ObservableObject 프로토콜을 준수하는 객체와 함께 사용되며, 뷰가 처음 생성될 때 한 번만 초기화됩니다.
특징
- 뷰의 생명 주기와 함께 유지: @StateObject로 선언된 객체는 뷰의 생명 주기 동안 유지됩니다. 뷰가 다시 생성될 때 객체가 초기화되지 않고 동일한 인스턴스가 유지됩니다.
- 초기화 한 번만: @StateObject로 선언된 객체는 뷰가 처음 생성될 때 한 번만 초기화됩니다. 따라서 객체의 초기화 로직이 여러 번 실행되지 않습니다.
- 상태 소유: 뷰가 상태 객체를 소유하고, 상태 객체가 뷰의 상태를 관리합니다. 이는 뷰의 상태를 중앙 집중화하고, 상태 객체를 뷰의 일부로 만듭니다.
- 자동 업데이트: @StateObject로 선언된 객체의 프로퍼티가 @Published로 선언되어 있다면, 프로퍼티가 변경될 때마다 뷰가 자동으로 업데이트됩니다.
class CounterModel: ObservableObject {
@Published var count: Int = 0
}
struct ContentView: View {
@StateObject private var counterModel = CounterModel()
var body: some View {
VStack {
Text("Count: \(counterModel.count)")
.font(.largeTitle)
.padding()
Button(action: {
counterModel.count += 1
}) {
Text("Increment")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
}
- ObservableObject 프로토콜: CounterModel 클래스가 ObservableObject 프로토콜을 준수합니다.
- @Published 프로퍼티 래퍼: count 프로퍼티가 변경될 때 이를 관찰하는 뷰가 업데이트되도록 합니다.
- 뷰: @StateObject를 사용하여 CounterModel 객체를 선언하고, 뷰의 생명 주기 동안 객체를 관리합니다.
@EnvironmentObject
@EnvironmentObject는 뷰 계층 구조 전체에서 공유되는 데이터 객체를 사용합니다. 주로 애플리케이션 전체에 걸쳐 데이터를 전달할 때 사용됩니다. @EnvironmentObject는 앱의 여러 뷰에서 동일한 객체를 참조하고 데이터 변경 사항을 자동으로 반영합니다.
특징
- @EnvironmentObject로 선언된 객체는 뷰 계층 구조 전체에서 공유됩니다.
- 객체 내의 상태가 변경되면 이를 사용하는 모든 뷰가 자동으로 다시 렌더링됩니다.
- 앱의 전역 상태를 관리하는 데 유용합니다.
class UserSettings: ObservableObject {
@Published var username: String = "Guest"
}
struct ContentView: View {
@EnvironmentObject var userSettings: UserSettings
var body: some View {
VStack {
Text("Username: \(userSettings.username)")
.font(.title)
.padding()
EditView()
.padding()
}
}
}
struct EditView: View {
@EnvironmentObject var userSettings: UserSettings
var body: some View {
TextField("Username", text: $userSettings.username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
}
이 코드는 SwiftUI에서 환경 객체(Environment Object)를 사용하여 여러 뷰 간에 데이터를 공유하는 방법을 보여줍니다. 주요 구성 요소는 다음과 같습니다:
UserSettings 클래스
- ObservableObject프로토콜을 채택합니다.
- @Published` 속성 래퍼를 사용하여 `username` 변수를 선언합니다.
ContentView 구조체
- @EnvironmentObject 속성 래퍼를 사용하여 UserSettings 객체에 접근합니다.
- 현재 사용자 이름을 표시하는 Text 뷰를 포함합니다.
- EditView를 하위 뷰로 포함합니다.
EditView 구조체
- @EnvironmentObject를 사용하여 동일한 UserSettings 객체에 접근합니다.
- TextField를 사용하여 사용자 이름을 편집할 수 있게 합니다.
이러한 코드를 사용하면 환경 객체를 사용하여 앱의 여러 부분에서 동일한 데이터에 쉽게 접근할 수 있습니다. 또한 @EnvironmentObject를 사용하면 뷰 계층 구조의 어느 곳에서나 데이터에 접근할 수 있어, 프로퍼티를 일일이 전달할 필요가 없습니다.
그리고 EditView에서 사용자 이름을 변경하면, ContentView에서도 자동으로 업데이트됩니다.
@Environment
@Environment는 뷰의 환경 값을 읽기 위해 사용됩니다. 주로 시스템 제공 값이나 상위 뷰에서 제공하는 값을 읽을 때 사용됩니다. 예를 들어, 색상 모드, 로케일, 접근성 설정 등을 읽는 데 유용합니다.
특징
- @Environment로 선언된 변수는 시스템 또는 상위 뷰에서 제공하는 값을 참조합니다.
- 읽기 전용이며, 값이 변경되면 이를 사용하는 뷰가 자동으로 다시 렌더링됩니다.
- 시스템 및 사용자 설정에 따라 동적으로 뷰를 조정할 수 있습니다.
struct EnvironmentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
Text("Current color scheme: \(colorScheme == .dark ? "Dark" : "Light")")
.font(.title)
.padding()
if colorScheme == .dark {
Text("Dark mode is enabled.")
.padding()
.background(Color.black)
.foregroundColor(.white)
} else {
Text("Light mode is enabled.")
.padding()
.background(Color.white)
.foregroundColor(.black)
}
}
.padding()
}
}
이 뷰는 현재 시스템의 색상 모드(라이트 모드 또는 다크 모드)에 따라 다른 텍스트와 스타일을 보여줍니다.
- @Environment(\.colorScheme) var colorScheme는 현재 시스템의 색상 모드를 가져옵니다.
- body 속성 내에서 VStack은 수직 스택을 생성합니다.
- 첫 번째 Text는 현재 색상 모드를 “Dark” 또는 “Light”로 표시합니다.
- 색상 모드가 다크 모드일 때와 라이트 모드일 때 각각 다른 Text를 표시하고, 각 모드에 맞는 배경색과 글자색을 적용합니다.
감사합니다.
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > SwiftUI' 카테고리의 다른 글
[SwiftUI] SwiftUI와 UIKit 통합하기 (1/2) (5) | 2024.09.14 |
---|---|
[SwiftUI] @AppStorage와 @SceneStorage 프로퍼티 래퍼 이해하기 (6) | 2024.09.11 |
[SwiftUI] AVFoundation 톺아보기 (2) | 2024.07.15 |
[SwiftUI] Core Data를 사용해보자 (0) | 2024.07.07 |
[SwiftUI] ObservedObject가 뭘까?? (0) | 2024.06.06 |