How to use ContextMenu in SwiftUI | SwiftUI Bootcamp #34
Long-pressing a photo in the iOS Photos app shows a preview with quick actions like "Share", "Favorite", or "Delete". This is a context menu — and in SwiftUI, adding one to any view takes only a few lines. This lesson shows you how to build context menus with multiple action items and how to use Label with an icon for polished menu entries.
What You'll Learn
- How
.contextMenu(menuItems:)adds a long-press-triggered menu to any SwiftUI view - How to build menu buttons using
Button,Label, and plainHStackwith an icon - How context menu actions can mutate state — demonstrated with live color changes on the card
Mental Model
A context menu is like a magician's hidden deck — it's not visible, but it's always there. When you long-press the right spot (the magician's hand), the hidden deck appears in a popup. Context menus in SwiftUI work the same way: the menu is invisible until the user triggers it with a long press. The view underneath is temporarily zoomed and blurred, and the menu pops up alongside it.
Unlike an action sheet (which slides up from the bottom and covers the screen), a context menu appears inline, next to the element the user pressed, maintaining spatial context. This makes context menus ideal for content items in a list or grid where the action is tightly coupled to a specific element.
Detailed Explanation
.contextMenu(menuItems: { ... }) is a modifier you attach to any view. The content closure builds the menu using Button views. When the user long-presses the view, iOS presents the menu with a preview of the view on iOS 16+ (older iOS versions show the view but no preview).
Each Button in the context menu appears as a menu item. The button's label determines what appears in the row. You have three label styles available:
Label("Share post", systemImage: "flame.fill")— text plus an SF Symbol icon, the recommended approachText("Report post")— text onlyHStack { Text("Like post"); Image(systemName: "heart.fill") }— custom layout (note: this works but is less idiomatic thanLabel)
Actions in context menu buttons work identically to regular button actions — you can mutate @State, call functions, or trigger navigation. In this example, each button changes backgroundColor, demonstrating that state mutations from menu buttons cause immediate view re-renders.
Context menus are best suited for content items (cards, list rows, images) where the actions are about the item the user pressed. For actions unrelated to a specific content item (like "create new" or "open settings"), prefer a toolbar button or a navigation bar item.
Code Structure
ContextMenuBootcamp.swift displays a styled card with a house icon, title, and subtitle. The card's background color is driven by @State var backgroundColor. The context menu has three buttons — "Share post" (with a Label), "Report post" (text only), and "Like post" (HStack with icon) — each changing the background color to demonstrate live state interaction.
Complete Code
ContextMenuBootcamp.swift
import SwiftUI
struct ContextMenuBootcamp: View {
@State var backgroundColor: Color = Color(#colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1)) // deep purple starting color
var body: some View {
VStack(alignment: .leading, spacing: 10.0) {
Image(systemName: "house.fill")
.font(.title)
Text("Swiftful Thinking")
.font(.headline)
Text("How to use Context Menu")
.font(.subheadline)
}
.foregroundColor(.white)
.padding(30)
.background(backgroundColor.cornerRadius(30)) // the card background reacts to menu selections
.contextMenu(menuItems: {
Button(action: {
backgroundColor = .yellow // tapping "Share post" turns the card yellow
}, label: {
Label("Share post", systemImage: "flame.fill") // recommended: Label with text and SF Symbol icon
})
Button(action: {
backgroundColor = .red // tapping "Report post" turns the card red
}, label: {
Text("Report post") // text-only label — simpler but no icon
})
Button(action: {
backgroundColor = .green // tapping "Like post" turns the card green
}, label: {
HStack { // custom label layout — works but Label is the cleaner choice
Text("Like post")
Image(systemName: "heart.fill")
}
})
})
}
}
struct ContextMenuBootcamp_Previews: PreviewProvider {
static var previews: some View {
ContextMenuBootcamp()
}
}Code Walkthrough
@State var backgroundColor— The card's background reacts to menu actions. This demonstrates the key point: context menu button actions are normal Swift closures that can mutate state, trigger network calls, or do anything a regular button action does..background(backgroundColor.cornerRadius(30))— The.backgroundmodifier usesbackgroundColordirectly. When any menu button changes this state, the card's background re-renders immediately with the new color..contextMenu(menuItems: { ... })— Attached to the entireVStackcard. Long-pressing anywhere on the card triggers the menu. The modifier works on any view — including individual list rows, images, or text elements.Label("Share post", systemImage: "flame.fill")— This is the idiomatic way to add an icon to a context menu button.Labelautomatically handles layout, accessibility, and system styling. The SF Symbol name goes insystemImage:.Text("Report post")label — A text-only menu item. Valid and functional, but the icon-less version is less visually informative. In a real app, consider which actions are important enough to warrant an icon.HStack { Text(...); Image(...) }label — A custom layout approach that also works. However,Label("Like post", systemImage: "heart.fill")would produce the same result more cleanly and with correct dynamic type support.
Common Mistakes
Mistake: Expecting the context menu to appear on a single tap
Context menus require a long press (hold) to activate — they are not triggered by a regular tap. If you want a tap-triggered action sheet, use .actionSheet (lesson 33) instead.
Mistake: Attaching .contextMenu to a container that's too large
If you attach .contextMenu to a ScrollView or full-screen ZStack, the long press anywhere on the screen triggers it. Be intentional about which view receives the modifier — usually a card, a list row, or a specific image.
Mistake: Using HStack { Text(...); Image(...) } instead of LabelLabel is accessibility-aware, supports Dynamic Type correctly, and will correctly adapt in compact environments. The HStack approach works visually but misses accessibility annotations that Label provides automatically.
Key Takeaways
.contextMenuadds a long-press-triggered action menu to any SwiftUI view, keeping actions contextually associated with the content they affect.- Use
Label("Title", systemImage: "sfSymbolName")for menu buttons to get icon + text with proper accessibility support. - Context menu button actions are plain Swift closures — they can mutate
@State, call functions, or trigger any behavior just like regular button actions.
Last updated: June 27, 2026