I am trying to add a contextmenu to images I display, but when I longpress, the contextmenu opens in a wrong location (see image)
The code I use is:
ForEach(self.document.instruments) { instrument in
Image(instrument.text)
.resizable()
.frame(width: 140, height: 70)
.position(self.position(for: instrument, in: geometry.size))
.gesture(self.singleTapForSelection(for: instrument))
.gesture(self.dragSelectionInstrument(for: instrument))
.shadow(color: self.isInstrumentSelected(instrument) ? .blue : .clear, radius: 10 * self.zoomScale(for: instrument))
.contextMenu {
Button {
print("Deleted selected")
} label: {
Label("Delete", systemImage: "trash")
}
}
}
Update of issue
The issue I still face (as my last comment), is that when I drag an image, there seems to be a delay, as the image remains on its position for a short moment and then moves to the dragged position. I already found out that when I have the contextMenu in the code, the delay dragging happens, when commented out, it works fine.
This is the code I have:
ForEach(self.document.instruments) { instrument in
Image(instrument.text)
.resizable()
.frame(width: 140, height: 70)
.contextMenu {
Button {
print("Deleted selected")
} label: {
Label("Delete", systemImage: "trash")
}
}
.position(self.position(for: instrument, in: geometry.size))
.shadow(color: self.isInstrumentSelected(instrument) ? .blue : .clear, radius: 10 * self.zoomScale(for: instrument))
.gesture(self.singleTapForSelection(for: instrument))
.gesture(self.dragSelectionInstrument(for: instrument))
}
I even tried moving the gestures above the contextmenu, but didn't work.
Please help
Latest Update
Nm, it was only on the simulator, on my physical iPad it works fine
The problem is that your code applies the contextMenu
modifier after the position
modifier.
Let's consider this slightly modified example:
ZStack {
GeometryReader { geometry in
ForEach(self.document.instruments, id: \.id) { instrument in
Image(instrument.text)
.frame(width: 140, height: 70)
.position(self.position(for: instrument, in: geometry.size))
.contextMenu { ... }
}
}
}
In the SwiftUI layout system, a parent view is responsible for assigning positions to its child views. A view modifier acts as the parent of the view it modifies. So in the example code:
ZStack
is the parent of GeometryReader
.GeometryReader
is the parent of ForEach
.ForEach
is the parent of contextMenu
.contextMenu
is the parent of position
.position
is the parent of frame
.frame
is the parent of Image
.Image
is not a parent. It has no children.(Sometimes the parent is called the “superview” and the child is called the “subview”.)
When contextMenu
needs to know where to draw the menu on the screen, it looks at the position given to it by its parent, the ForEach
, which gets it from the GeometryReader
, which gets it from the ZStack
.
When Image
needs to know where to draw its pixels on the screen, it looks at the position given to it by its parent, which is the frame
modifier, and the frame
modifier gets the position from the position
modifier, and the position
modifier modifies the position given to it by the contextMenu
.
This means that the position
modifier does not affect where the contextMenu
draws the menu.
Now let's rearrange the code so contextMenu
is the child of position
:
ZStack {
GeometryReader { geometry in
ForEach(self.document.instruments, id: \.id) { instrument in
Image(instrument.text)
.frame(width: 140, height: 70)
.contextMenu { ... }
.position(self.position(for: instrument, in: geometry.size))
}
}
}
Now the contextMenu
gets its position from the position
modifier, which modifies the position given to it by the ForEach
. So in this scenario, the position
modifier does affect the where the contextMenu
draws the menu.