refactoring: Gesture 객체를 property로 만들기

SteadySlower·2022년 8월 22일
0

이번 포스팅에서는 Gesture 객체를 body 안에 직접 선언하지 않고 View의 property로 선언해서 사용하는 방법을 알아보도록 하겠습니다.

기존의 코드

기존의 코드의 단점은 View에 gesture를 선언하는 부분에 Gesture의 종류와 제스쳐에 연결하는 클로저가 전부 몰려있습니다. 가독성이 매우 떨어지는 상황입니다.

.gesture(DragGesture(minimumDistance: 30, coordinateSpace: .global)
    .onChanged({ value in
        self.dragWidth =  value.translation.width
    })
    .onEnded({ value in
        self.dragWidth = 0
        if value.translation.width > 0 {
            viewModel.updateStudyState(to: .success)
        } else {
            viewModel.updateStudyState(to: .fail)
        }
    })
)
.gesture(TapGesture(count: 2).onEnded {
    viewModel.updateStudyState(to: .undefined)
})
.gesture(TapGesture().onEnded {
    viewModel.wordDisplay.isFront.toggle()
})

Gesture를 property로 선언하는 방법

🚫 그냥 let으로 선언

클로저 안에서 self를 접근하지 않아도 된다면 이렇게 선언해도 문제 없습니다만 우리는 self에 접근해야 합니다. 따라서 이런 방식으로 선언하면 self에 접근할 수가 없습니다.

🚫 lazy var로 선언

그렇다면 lazy var를 통해서 self에 접근할 수 있도록 선언해봅시다.

lazy var tapGesture = TapGesture().onEnded { onTap() }

lazy var tapGesture = {
    TapGesture().onEnded { onTap() }
}()

이번에는 이런 에러를 볼 수 있습니다… self가 immutable인데 mutating getter를 사용한다고 하는군요. 저도 100% 설명을 할 수 없습니다만;;; lazy는 mutating이라고 합니다. 따라서 body의 getter 안에서 사용할 수 없는 것입니다. getter는 immutable이어야 하니까요.

요약하면 mutating인 lazy를 immutable인 body의 getter에서 사용하기 때문에 발생하는 문제입니다.

✅  computed property로 선언

그렇다면 Gesture를 property로 선언하는 방법은 뭘까요? 바로 computed property를 사용하는 것입니다. 참고로 some 키워드는 Gesture 프로토콜을 준수하는 객체가 리턴될 것임을 의미합니다. some은 내부에서는 타입을 명확히 알고 있지만 외부에서는 타입을 모르게할 때 사용합니다.

import SwiftUI

struct MainView: View {
    
    var tapGesture: some Gesture {
        TapGesture().onEnded { onTap() }
    }
    
    var body: some View {
        Image(systemName: "heart")
            .gesture(tapGesture)
    }
    
    func onTap() {
        print("Tapped!")
    }
}

리팩토링

Gesture Properties

body 안에 덕지덕지 들어있던 Gesture들을 전부 바깥으로 빼서 별도의 property로 선언했습니다. 메소드들도 별도로 구현했기 때문에 코드가 한결 간결해진 모습니다.

// MARK: Gestures
private var dragGesture: some Gesture {
    DragGesture(minimumDistance: 30, coordinateSpace: .global)
        .onEnded { onDragEnd($0) }
        .updating($dragAmount) { value, state, _ in state.width = value.translation.width }
}

private var tapGesture: some Gesture {
    TapGesture()
        .onEnded { isFront.toggle() }
}

private var doubleTapGesture: some Gesture {
    TapGesture(count: 2)
        .onEnded { viewModel.updateStudyState(to: .undefined) }
}

body

body 안에서 View에 적용하는 부분도 정말 간결해졌습니다. 그냥 변수만 전달하면 끝입니다!

.gesture(dragGesture)
.gesture(doubleTapGesture)
.gesture(tapGesture)
profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글