Skip to content

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:

swift
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):

swift
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:

swift
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

swift
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+):

swift
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

Create nested menus in UIKit:

swift
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:

swift
.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:

swift
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:

swift
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.

Released under the MIT License.