Context Menus
Context menus, introduced in iOS 13, provide a way to offer users quick access to actions related to a specific UI element through a long press or right-click (on supported devices). They enhance user interaction by presenting relevant options in a concise, touch-friendly menu. Context menus are available in UIKit and SwiftUI, integrating seamlessly with the iOS interaction model.
Purpose
- Quick Actions: Allow users to perform actions on UI elements without navigating elsewhere.
- Intuitive Interaction: Triggered by long press (or right-click with a mouse/trackpad on iPad), aligning with iOS gesture conventions.
- Customizability: Support custom actions, submenus, and dynamic menu generation.
UIKit Implementation
In UIKit, context menus are implemented using UIContextMenuInteraction
for views or by configuring a UIContextMenuConfiguration
for collection/table view cells.
Key Components
- UIContextMenuInteraction: A UIKit interaction object added to a view to handle context menu gestures.
- UIContextMenuConfiguration: Defines the menu’s content, including actions and submenus.
- UIMenu: Represents the menu, containing actions (
UIAction
) or nested menus. - UIAction: Defines individual menu items with titles, images, and handlers.
Basic Example
Add a context menu to a UIView
:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var sampleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Create context menu interaction
let interaction = UIContextMenuInteraction(delegate: self)
sampleView.addInteraction(interaction)
}
}
extension ViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
let copyAction = UIAction(title: "Copy", image: UIImage(systemName: "doc.on.doc")) { _ in
print("Copy action triggered")
}
let shareAction = UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { _ in
print("Share action triggered")
}
return UIMenu(title: "", children: [copyAction, shareAction])
}
}
}
Preview Provider
Optionally provide a preview for the context menu (e.g., a zoomed-in view of the content):
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: {
let previewController = UIViewController()
previewController.view.backgroundColor = .white
let label = UILabel()
label.text = "Preview"
label.frame = previewController.view.bounds
previewController.view.addSubview(label)
return previewController
}, actionProvider: { _ in
return UIMenu(title: "", children: [
UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in
print("Edit action")
}
])
})
}
Collection/Table Views
For UICollectionView
or UITableView
, implement the delegate method:
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: indexPath as NSCopying, previewProvider: nil) { _ in
return UIMenu(title: "", children: [
UIAction(title: "Delete", attributes: .destructive) { _ in
print("Delete item at \(indexPath)")
}
])
}
}
SwiftUI Implementation
In SwiftUI, context menus are simpler, using the .contextMenu
modifier on a view.
Basic Example
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Long Press Me")
.contextMenu {
Button(action: { print("Copy action") }) {
Label("Copy", systemImage: "doc.on.doc")
}
Button(action: { print("Share action") }) {
Label("Share", systemImage: "square.and.arrow.up")
}
}
}
}
Preview Provider
Add a preview using ContextMenu
with a preview
parameter (iOS 15+):
Text("Long Press Me")
.contextMenu(ContextMenu(menuItems: {
Button("Copy") { print("Copy action") }
}, preview: {
VStack {
Text("Preview Content")
.padding()
.background(Color.white)
.cornerRadius(8)
}
}))
Advanced Features
Submenus
Create nested menus in UIKit:
let submenu = UIMenu(title: "More", children: [
UIAction(title: "Option 1") { _ in print("Option 1") },
UIAction(title: "Option 2") { _ in print("Option 2") }
])
let mainMenu = UIMenu(title: "", children: [
UIAction(title: "Copy") { _ in print("Copy") },
submenu
])
In SwiftUI:
.contextMenu {
Button("Copy") { print("Copy") }
Menu("More") {
Button("Option 1") { print("Option 1") }
Button("Option 2") { print("Option 2") }
}
}
Dynamic Menus
Generate menus dynamically based on context:
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
var actions: [UIMenuElement] = []
if isEditable {
actions.append(UIAction(title: "Edit") { _ in print("Edit") })
}
actions.append(UIAction(title: "Delete", attributes: .destructive) { _ in print("Delete") })
return UIMenu(title: "", children: actions)
}
}
Attributes
Use UIAction
attributes like .destructive
or .disabled
to style or disable actions:
UIAction(title: "Delete", attributes: [.destructive, .disabled]) { _ in }
Best Practices
- Relevance: Include only contextually relevant actions to keep menus concise.
- Consistency: Follow iOS conventions (e.g., use system images, standard terms like “Copy”).
- Accessibility: Ensure actions are accessible via VoiceOver; provide meaningful titles and images.
- Testing: Test on iPhone and iPad, with and without external input devices (e.g., mouse/trackpad).
- Performance: Avoid heavy computations in menu generation to maintain responsiveness.
Limitations
- iOS 13+: Context menus are only available on iOS 13 and later.
- Gesture Conflicts: Long press gestures may conflict with other interactions; configure
UIContextMenuInteraction
carefully. - Preview Limitations: Previews must be lightweight to avoid performance issues.
- Discoverability: Users may not know to long press unless guided by UI cues.
Summary
Context menus in iOS provide a powerful way to enhance user interaction by offering quick, context-sensitive actions. UIKit’s UIContextMenuInteraction
and SwiftUI’s .contextMenu
modifier make implementation straightforward, with support for previews, submenus, and dynamic content. By adhering to best practices, developers can create intuitive and efficient context menus that improve the user experience.