[SwiftUI] Property Wrapper 알아보기

2024. 7. 29. 17:39·Apple/SwiftUI
728x90
반응형

안녕하세요! 피피아노입니다 🎵

 

이번 포스팅에서는 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)
            }
        }
    }
}
  1. CounterView라는 구조체를 정의하며, 이는 View 프로토콜을 준수합니다.
  2. @State 속성 래퍼를 사용하여 count라는 변경 가능한 상태 변수를 선언합니다.
  3. body 프로퍼티에서 뷰의 내용을 정의합니다.
  4. VStack을 사용하여 요소들을 수직으로 배치합니다.
  5. Text 뷰로 현재 카운트 값을 표시합니다.
  6. Button을 생성하여 카운트를 증가시키는 기능을 제공합니다.
  7. 버튼의 외관을 커스터마이즈하여 파란색 배경과 흰색 텍스트, 둥근 모서리를 가지도록 합니다.

이 뷰는 화면에 카운트 값을 표시하고, 버튼을 누를 때마다 카운트가 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를 표시하고, 각 모드에 맞는 배경색과 글자색을 적용합니다.

감사합니다.

 

잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!

궁금하신 부분은 댓글로 질문 부탁드립니다!

728x90
반응형

'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
'Apple/SwiftUI' 카테고리의 다른 글
  • [SwiftUI] SwiftUI와 UIKit 통합하기 (1/2)
  • [SwiftUI] @AppStorage와 @SceneStorage 프로퍼티 래퍼 이해하기
  • [SwiftUI] AVFoundation 톺아보기
  • [SwiftUI] Core Data를 사용해보자
P_Piano
P_Piano
Apple 생태계 개발자가 되기 위한 학습과 경험의 기록
    반응형
    250x250
  • P_Piano
    피피아노의 개발 일지
    P_Piano
  • 전체
    오늘
    어제
    • 분류 전체보기 (202) N
      • Apple (120) N
        • iOS (22)
        • visionOS (4)
        • Swift (65)
        • UIKit (2)
        • SwiftUI (21) N
        • RxSwift (2)
        • Xcode (4)
      • C언어 (5)
      • C++ (8)
      • Dart (1)
      • Python (3)
      • JavaScript (17)
      • Git (1)
      • CS (39)
        • 디자인 패턴 (6)
        • 네트워크 (20)
        • 운영체제 (8)
        • Database (5)
        • 자료구조 (0)
      • IT 지식 (2)
      • IT 뉴스 (4)
      • 출처 표기 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    프로퍼티 래퍼
    네트워크
    메서드
    combine
    함수
    디자인패턴
    Xcode
    이니셜라이저
    제어문
    연산자
    프로세스
    티스토리챌린지
    자바스크립트
    스위프트
    운영체제
    Optional
    visionOS
    UIKit
    옵셔널
    오블완
    Vision Pro
    Initializers
    코딩테스트
    SWIFT
    ios
    클래스
    비동기
    swiftUI
    변수
    배열
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
P_Piano
[SwiftUI] Property Wrapper 알아보기
상단으로

티스토리툴바