I have created an app that consists of the main view inside of which there are multiple views, one per shape. For the shape, I would like to enable some interaction in order to transform its properties upon tapping. As the transformation depends on the tap location as well, I have implemented it as DragGesture the following way:
.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { gesture in
print("Tap location: \(gesture.startLocation)")
})
Besides the transformation of individual shapes, I wanted to enable the user to drag the whole content and/or resize it. Thus, in the main view, I have implemented a drag gesture and magnification gesture. As tapping and dragging are conflicting, I added an option to toggle the interaction mode between tapping and dragging. The transformation is enabled/disabled by checking the following condition inside shape view:
.allowsHitTesting(dragGestureMode == DragGestureEnum.TransformShape)
Nevertheless, sometimes when I try to resize the image, the gesture is interpreted as a drag gesture on a certain shape that performs the logic for tapping (dragging with minimum distance 0) on that shape instead of resizing (in case of dragging mode this is not a problem).
Below is the logic of the main view and the shape view which instances are laid inside main view:
struct MainView: View {
@EnvironmentObject var mainViewModel : MainViewModel
@State private var offset = CGSize.zero
@State private var draggedSize: CGSize = CGSize.zero
@State private var scale: CGFloat = 1.0
@State private var scaledSize: CGFloat = 1.0
var body: some View {
GeometryReader {
geometry in
ZStack {
ForEach(mainViewModel.shapeItemKeys, id: \.self){ id in
let shapeItem = $mainViewModel.shapeItemsByKey[id]
ShapeView(shapeItem: shapeItem, dragGestureMode: $mainViewModel.dragGestureMode)
}
}
.frame(width: min(geometry.size.width, geometry.size.height), height: min(geometry.size.width, geometry.size.height), alignment: .center)
.contentShape(Rectangle())
.offset(x: self.draggedSize.width, y: self.draggedSize.height)
.scaleEffect(self.scaledSize)
.gesture(
DragGesture()
.onChanged { gesture in
self.draggedSize = gesture.translation
self.draggedSize.width += self.offset.width
self.draggedSize.height += self.offset.height
}
.onEnded { _ in
self.offset = self.draggedSize
}
)
.gesture(MagnificationGesture()
.onChanged({ (scale) in
self.scaledSize = scale.magnitude
self.scaledSize *= self.scale
})
.onEnded({ (scaleFinal) in
self.scale = scaleFinal
print("New scale: \(self.scale)")
self.scale = self.scaledSize
}))
}
}
}
struct ShapeView: View {
@Binding var shapeItem: ShapeItem?
@Binding var dragGestureMode: DragGestureEnum
var layersToRemove: [Int] = []
init(shapeItem: Binding<ShapeItem?>, dragGestureMode: Binding<DragGestureEnum>) {
self._shapeItem = shapeItem
self._dragGestureMode = dragGestureMode
}
var body: some View {
ZStack {
shapeItem!.path
.foregroundColor(Color(shapeItem!.color))
.overlay(
ZStack {
// ... some logic
}
, alignment: .leading)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { gesture in
print("Tap location: \(gesture.startLocation)")
}
)
.allowsHitTesting(dragGestureMode == DragGestureEnum.TransformShape)
}
}
}
Does anyone know what is a good way to enable combinations of gestures ((dragging or resizing) or (tapping or resizing)) in this case (i.e. tap should be detected on shape view, drag or resize should be detected on the main view and tap on the individual shape and drag on the main view are exclusive) in order to get the expected user experience (prevent resizing gesture from being interpreted as tapping or dragging)?