ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Swift] SwiftUI 프로퍼티 래퍼 뿌시기 1: @State와 @Binding
    테크 2024. 3. 6. 19:25

     

    후... 기본이 탄탄하지 않으니 코드 작성하는게 어렵다 어려워;

    @Observable이 뭐지?하고 보다가 그냥 제가 프로퍼티 래퍼를 잘 모르는 것 같아가지구요ㅠㅎ

    기초부터 하나씩 하나씩 뜯어보도록 하겠습니다...

    일단 프로퍼티 래퍼가 뭐냐?

     

    프로퍼티 래퍼 (Property Wrapper)

    • SwiftUI에서 @붙은 애들을 지칭함: @propertyWrapper, @State, @Binding, @Bindable, ...
    • 속성(Property, 이하 프로퍼티)과 관련된 행동들을 캡슐화함
    • 결과적으로 코드의 가독성을 높임

    예를 들어서 아래와 같은 코드가 있다고 생각해 봅시다.

    import SwiftUI
    
    struct Car {
        private var _model = ""
       
        var model: String {
            get { self._model.uppercased() }
            set { self._model = newValue }
        }
        
        init(model: String) {
            self.model = model
        }
    }
    
    struct Person {
        private var _name = ""
        
        var name: String {
            get { self._name.uppercased() }
            set { self._name = newValue }
        }
        
        init(name: String) {
            self.name = name
        }
    }
    
    let car = Car(model: "Benz")
    let person = Person(name: "kimdora")
    print(car.model, person.name) // BENZ KIMDORA

     

    뭐 코드가 멀쩡한거 아니냐? 라고 생각할 수 있어요. 네! 멀쩡합니다.

    근데 우리가 또 개발잔데 코드 중복은 참을 수가 없죠?

        var model: String {
            get { self._model.uppercased() }
            set { self._model = newValue }
        }
        var name: String {
            get { self._name.uppercased() }
            set { self._name = newValue }
        }

     

    코드가 중복되어 있으니 중복을 없애고 싶은게 당연한 것 아닙니까!?

    이때 등장하는 것이 바로 @propertyWrapper...

    import SwiftUI
    
    @propertyWrapper
    struct Uppercase {
        private var value: String = ""
    
        var wrappedValue: String { // 구현 필수
            get { self.value.uppercased() }
            set { self.value = newValue }
        }
    }

     

    자, 위 예시처럼 @propertyWrapper를 사용해서 커스텀한 @Uppercase 프로퍼티 래퍼를 만들어주면

    아래처럼 간결하게 코드를 작성할 수 있답니다!

    struct Car {
        @Uppercase var model: String
    
        init(model: String) {
            self.model = model
        }
    }
    
    struct Person {
        @Uppercase var name: String
    
        init(name: String) {
            self.name = name
        }
    }
    
    let car = Car(model: "Benz")
    let person = Person(name: "kimdora")
    print(car.model, person.name) // BENZ KIMDORA

     

    간단하쥬?

     

    자 고럼 이제 프로퍼티 래퍼가 무엇인지 감이 왔을터이니...

    SwiftUI에서 제공하는 여러 프로퍼티 래퍼 중에서

    정말 정말 자주 사용하는 2개의 프로퍼티 래퍼를 알아봅시다.

     

    @State

    @frozen @propertyWrapper
    struct State<Value>
    • 구조체는 프로퍼티 변경이 불가능하지만 @State를 통해서 프로퍼티 변경 가능
    • 보통 App, Scene, View에서 사용 (난 일단 View에서만 사용할거니까 아래에선 View에서 사용한다고 말하겠음^^)
    • Private으로 선언하기 때문에 다른 View에서는 접근이 불가능

    바로 예시 들어갑니다!

    struct PlayButton: View {
        @State private var isPlaying: Bool = false
    
        var body: some View {
            Button(isPlaying ? "Pause" : "Play") {
                isPlaying.toggle()
            }
        }
    }

     

    @Binding

    @frozen @propertyWrapper @dynamicMemberLookup
    struct Binding<Value>

     

    아까 @State를 사용하면 다른 View에서 접근하지 못한다고 이야기 했었는데요.

    코드를 작성하다보면 View를 대량 생산할 수밖에 없고 View 간 변수 공유가 중요할터인데...

    이를 해결하기 위해 @Binding이 등장합니다! (@Bindable 아님 주의)

    • 부모 View에서 자식 View로 값을 전달하거나 자식 View에서 부모 View로 값을 다시 전달하는데 사용
    • @State 프로퍼티 래퍼로 감싸진 변수 이름 앞에 $가 붙으면 Binding 타입으로 바뀜: projectedValue

    예시는 다음과 같습니다.

    struct PlayButton: View {
        @Binding var isPlaying: Bool
        
        var body: some View {
            Button(action: {
                self.isPlaying.toggle()
            }) {
                Image(systemName: isPlaying ? "pause.circle" : "play.circle")
            }
        }
    }
    
    struct PlayerView: View {
        @State private var isPlaying: Bool = false
        
        var body: some View {
            VStack {
                PlayButton(isPlaying: $isPlaying)
            }
        }
    }

     

    먼저 PlayerView는 PlayButton의 부모 View라고 할 수 있겠네요.

    PlyerView에서 @State로 감싸진 isPlaying은 $isPlaying 형태로 PlayButton에 넘겨지게 되는데요.

    $isPlaying이 되면 Binding 타입으로 되기 때문에 PlayButton의 isPlaying의 타입과 딱 맞게 되죠?

     

    이제 PlayerView의 isPlaying과 PlayButton의 isPlaying은 서로 연결되었으니 (동체가 되는거임!)

    둘 중에 하나라도 값이 바뀌면 다른 View에서의 값도 바뀌게 되는 겁니다!!!

     

    일단 여기까지 포스팅을 마무리하고...

    다음 시간에 봅시다...!

     

    Property Wrapper에 대해서 함께 공부하고 싶다면
    #PropertyWrapper 태그로 들어가기!
Designed by Tistory.