많은 앱에서 사진 관련 플로우가 있을 때 많이들 적용하는 기능이다. 많은 유저들이 기본적으로 작동할 것이라고 생각하는 기능이기도 하다.
SwiftUI에서는 확대하는 제스쳐를 MagnificationGesture를 사용하여 적용하였는데, 이게 Deprecate될 예정이라서 추천하는 MagnifyGesture을 사용하여 구현해보고자 한다.
| MagnificationGesture | MagnifyGesture |
|---|---|
![]() | ![]() ![]() |
공식문서에서 차이를 보려고 했는데 그냥 닉변인거같다 ㅡㅡ
iOS17부터는 MagnifyGesture을 써야 하지만, 개발 버전에 따라 MagnificationGesture를 사용하는게 맞을 것 같긴하다 ( 해본결과 내가 해본 기능은 전부 호환된다 )
많은 Gesture들은 Gesture 프로토콜을 준수한다. Gesture를 사용할 때 쓰는 함수들을 알아보면
// Update되는 동안 GestureState를 바인딩한다. func updating<State>( _ state: GestureState<State>, body: @escaping (Self.Value, inout State, inout Transaction) -> Void ) -> GestureStateGesture<Self, State> // State를 바인딩한다.// 값이 변경되었을 때 Value를 매개변수로 주는 escaping closure를 제공한다. func onChanged(_ action: @escaping (Self.Value) -> Void) -> _ChangedGesture<Self>// 값 변경이 끝났을 때 Value를 매개변수로 주는 escaping closure를 제공한다. func onEnded(_ action: @escaping (Self.Value) -> Void) -> _EndedGesture<Self>
Value : 제스쳐의 값을 표현하는 associatedType이다. MagnifiyGesture에서는
/// The time associated with the gesture's current event.
public var time: Date
/// The relative amount that the gesture has magnified by.
///
/// A value of 2.0 means that the user has interacted with the gesture
/// to increase the magnification by a factor of 2 more than before the
/// gesture.
public var magnification: CGFloat
/// The current magnification velocity.
public var velocity: CGFloat
/// The initial anchor point of the gesture in the modified view's
/// coordinate space.
public var startAnchor: UnitPoint
/// The initial center of the gesture in the modified view's coordinate
/// space.
public var startLocation: CGPoint
과 같은 값들이 있는데, magnification 값을 사용하여 scale을 바꿔야 한다.
Gesture 프로토콜에서 updating함수를 적용하기 위해서는 @GestureState를 바인딩해야 한다. 처음 보는 프로퍼티 래퍼라서 간단히 들여다보았다.
GestureState : gesture의 updating 함수에서 바인딩 할 때 사용되는 DynamicProperty
특징으로는 gesture가 inactive가 되었을 때 초깃값으로 reset된다는 점이 있다. 그리고 get Only라서 인위적 값 변경이 불가능하다!
요구사항에 따라서 값이 유지되도록 하기 위해서는 @State를 사용해야 할 것 같다.
그럼에도 알아보는 @GestureState를 활용해 본 예제
@GestureState private var scale: CGFloat = 1.0 private var magnification: some Gesture { MagnifyGesture() .updating($scalee) {value, gestureState, transaction in gestureState = value.magnification } } // 확대 축소는 되는데, 끝나면 reset됨 var body: some View { Image .scaleEffect(scale) .gesture(magnification) }
개발 방법 : Scale 값을 CGFloat으로 가지고 있어야 한다. 그리고 Gesture의 onChanged 함수일 때 scale 값을 변경해준다
@State private var scale: CGFloat = 1.0 private var magnification: some Gesture { MagnifyGesture() .onChanged { value in // 제스처가 변경될 때마다 상태 업데이트 scale = value.magnification } .onEnded { _ in // 제스처가 끝날 때 아무 작업도 하지 않음 } }
@inlinable public func scaleEffect(_ s: CGFloat, anchor: UnitPoint = .center) -> some View
말 그대로 뷰의 Scale을 변경한다. UnitPoint를 활용해서 기준점을 잡을 수 있나보다
func gesture<T>( _ gesture: T, including mask: GestureMask = .all ) -> some View where T : Gesture
뷰에 제스쳐를 추가해주는 함수이다.
var body: some View { Image .scaleEffect(scale) .gesture(magnification) }