SwiftUI Image 줌 하기

태훈김·2023년 12월 1일

SwiftUI Image 줌 하기

이미지를 두 손가락으로 벌리면 확대되게 하고 싶습니다!

많은 앱에서 사진 관련 플로우가 있을 때 많이들 적용하는 기능이다. 많은 유저들이 기본적으로 작동할 것이라고 생각하는 기능이기도 하다.

요구사항

  • 이미지를 확대 하면 확대가 되고, 손을 뗐을 때도 그게 유지가 되었으면 함
  • 다시 이미지의 크기를 변화시킬 때 현재 크기에서 변환되었으면 함

MagnifyGesture

SwiftUI에서는 확대하는 제스쳐를 MagnificationGesture를 사용하여 적용하였는데, 이게 Deprecate될 예정이라서 추천하는 MagnifyGesture을 사용하여 구현해보고자 한다.

MagnificationGesture와 MagnifyGesture의 차이는?

MagnificationGestureMagnifyGesture
image-20231201105519665image-20231201105538932image-20231201105558327

공식문서에서 차이를 보려고 했는데 그냥 닉변인거같다 ㅡㅡ

iOS17부터는 MagnifyGesture을 써야 하지만, 개발 버전에 따라 MagnificationGesture를 사용하는게 맞을 것 같긴하다 ( 해본결과 내가 해본 기능은 전부 호환된다 )

Gesture

많은 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을 바꿔야 한다.

@State? @GestureState?

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)
 }

MagnifyGesture 구현하기

개발 방법 : 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
                // 제스처가 끝날 때 아무 작업도 하지 않음
            }
}

사용법

.scaleEffect() 함수

@inlinable public func scaleEffect(_ s: CGFloat, anchor: UnitPoint = .center) -> some View

말 그대로 뷰의 Scale을 변경한다. UnitPoint를 활용해서 기준점을 잡을 수 있나보다

.gesture() 함수

func gesture<T>(
    _ gesture: T,
    including mask: GestureMask = .all
) -> some View where T : Gesture

뷰에 제스쳐를 추가해주는 함수이다.

뷰에 바인딩

var body: some View {
    Image
        .scaleEffect(scale)
        .gesture(magnification)
}

더 공부해야 할 사항

  • DragGesture도 추가해서 두 제스쳐를 합쳐보는 기능을 해 보고 싶다.
profile
궁금한걸 알아보는 사람

0개의 댓글