Easier and Better Looking Context Menus

This is a quick follow-up to my previous post, which focused specifically on using context menus in Swift Lists.
Over time, you start to go deeper with view customisation, and that often means moving beyond stock components into fully custom UI layouts.
Take this example:
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 35) {
ForEach(0..<10, id: \.self) { _ in
Rectangle()
.clipShape(.rect(cornerRadius: 30))
.containerRelativeFrame(.horizontal, alignment: .center)
.scrollTransition { content, phase in
content
.opacity(phase.isIdentity ? 1 : 0.5)
.scaleEffect(y: phase.isIdentity ? 1 : 0.7)
}
.contextMenu {
Button("View in Photos") {}
Button("Edit Registration") {}
Button("Delete Image", role: .destructive) {}
}
}
}
.scrollTargetLayout()
.frame(height: 250)
}
.contentMargins(.horizontal, 50, for: .scrollContent)
.scrollTargetBehavior(.viewAligned)
}
}
This renders a horizontally scrolling row of rounded rectangles:
However, when you long-press to trigger the .contextMenu
, you might notice that the preview clips awkwardly:
The fix
Thankfully, the fix is simple: just use .contentShape
, but with a specific parameter. You need to define the shape specifically for .contextMenuPreview
.
You'll want it to match your clipped shape. Since we're using .clipShape(.rect(cornerRadius: 30))
, we should match that in the content shape too:
.contentShape(.contextMenuPreview, .rect(cornerRadius: 30))
To fix the clipping, you add the following to the ScrollView
:
.scrollClipDisabled()
This ensures that the context menu preview respects the rounded corners and doesn't show any white background or clipping issues.
Here's how it looks after applying the fix:
Working Code
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 35) {
ForEach(0..<10, id: \.self) { _ in
Rectangle()
.clipShape(.rect(cornerRadius: 30))
.contentShape(.contextMenuPreview, .rect(cornerRadius: 30)) // <-- Here
.containerRelativeFrame(.horizontal, alignment: .center)
.scrollTransition { content, phase in
content
.opacity(phase.isIdentity ? 1 : 0.5)
.scaleEffect(y: phase.isIdentity ? 1 : 0.7)
}
.contextMenu {
Button("View in Photos") {}
Button("Edit Registration") {}
Button("Delete Image", role: .destructive) {}
}
}
}
.scrollTargetLayout()
.frame(height: 250)
}
.contentMargins(.horizontal, 50, for: .scrollContent)
.scrollTargetBehavior(.viewAligned)
.scrollClipDisabled() // <-- Here
}
}
If you found this article helpful, you can keep the ideas flowing by supporting me. Buy me a coffee or check out my apps to help me create more content like this!
Coffee Check out my apps