There is a lot of great updates this year to SwiftUI (especially the new App / Scene protocols enabling full app development in 100% SwiftUI 😍) but there is one area I had really hoped would get some much needed improvements, and that is better Gesture support for multi-touch.
At the moment there is no way to implement the very common interaction of pinch-zooming a photo/view while also panning it at the same time. MagnificationGesture supports pinching, but it’s Value is just a single float, while DragGesture supports panning but doesn’t have the needed fidelity it its Value to give you information about multiple fingers. Trying to combine these gestures in a SimultaneousGesture doesn’t seem to work either as one or the other fails (one requires two fingers and the other a single finger).
Implementing with the available API results in a poor UX as first a user must pinch the photo to their preferred size (with a locked center anchor), then switch to a single finger to pan the image to the desired point of interest.
I assumed this might be improved with this year’s release of SwiftUI but I haven’t heard anything about it in the talks nor do the docs seem to indicate any meaningful update to Gesture support. Is there a way to achieve this that I’m missing, or if not, consider this my humble request for improved Gesture API before the public release this fall. Thanks!
Sample Playground code showcasing the problem below. Expected behavior: a pinch to zoom and pan move the image while it is enlarged (like zooming a photo in the Photos app). Actual behavior: magnification and drag occur exclusively.
At the moment there is no way to implement the very common interaction of pinch-zooming a photo/view while also panning it at the same time. MagnificationGesture supports pinching, but it’s Value is just a single float, while DragGesture supports panning but doesn’t have the needed fidelity it its Value to give you information about multiple fingers. Trying to combine these gestures in a SimultaneousGesture doesn’t seem to work either as one or the other fails (one requires two fingers and the other a single finger).
Implementing with the available API results in a poor UX as first a user must pinch the photo to their preferred size (with a locked center anchor), then switch to a single finger to pan the image to the desired point of interest.
I assumed this might be improved with this year’s release of SwiftUI but I haven’t heard anything about it in the talks nor do the docs seem to indicate any meaningful update to Gesture support. Is there a way to achieve this that I’m missing, or if not, consider this my humble request for improved Gesture API before the public release this fall. Thanks!
Sample Playground code showcasing the problem below. Expected behavior: a pinch to zoom and pan move the image while it is enlarged (like zooming a photo in the Photos app). Actual behavior: magnification and drag occur exclusively.
Code Block swift import SwiftUI import PlaygroundSupport struct DragPoorUX: View { private struct DragState { var translation = CGSize.zero var zoom = CGFloat(1.0) } @GestureState(resetTransaction: Transaction(animation: .spring())) private var dragState1 = DragState() @GestureState(resetTransaction: Transaction(animation: .spring())) private var dragState2 = DragState() private func dragGesture(updating gestureState: GestureState<DragState>) -> some Gesture { DragGesture().updating(gestureState) { (value, state, transaction) in state.translation = value.translation transaction = Transaction(animation: .interactiveSpring()) } } private func pinchGesture(updating gestureState: GestureState<DragState>) -> some Gesture { MagnificationGesture().updating(gestureState) { (value, state, transaction) in state.zoom = value transaction = Transaction(animation: .interactiveSpring()) } } var body: some View { ZStack { Rectangle() .fill(Color.gray) .frame(maxWidth: .infinity, maxHeight: .infinity) ColoredCircle(color: .orange) .offset(dragState1.translation) .scaleEffect(dragState1.zoom) .offset(y: -80) .gesture( SimultaneousGesture( pinchGesture(updating: $dragState1), dragGesture(updating: $dragState1) )) ColoredCircle(color: .blue) .offset(dragState2.translation) .scaleEffect(dragState2.zoom) .offset(y: 80) .gesture(dragGesture(updating: $dragState2)) .gesture(pinchGesture(updating: $dragState2)) } } struct ColoredCircle: View { var color: Color var body: some View { Circle() .fill(color) .frame(width: 140, height: 140) .shadow(radius: 2) } } } PlaygroundPage.current.setLiveView(DragPoorUX())