1
votes

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

1

1 Answers

1
votes

You can use simultaneous gesture modifier, like

    .simultaneousGesture(MagnificationGesture()
      ...