What’s new in Apphud: Paywall Analytics, Stripe Web Payments Support and Xiaomi SDKLet’s see
Apphud
Why Apphud?PricingContact
Ren
Ren
July 09, 2019
11 min read

How to implement context menus in iOS 13

In WWDC 2019 and iOS 13 Apple introduced a new way to interact with your app: context menu.

How to implement context menus in iOS 13

They look like this:

iOS Context Menus, ApphudiOS Context Menus, Apphud

After iOS 13 was released, iOS contextual menus (also called iOS context menus) replaced the previous function called Peek and Pop. Why? Well, Apple context menus proved to be better in a couple of ways. 

The Peek and Pop feature was launched with the release of iOS 9 along with the introduction of the iPhone 6S and a cool feature known as 3D Touch. 3D Touch is a tool that is able to perceive the level of force applied to the touch screen and activate various functions.  It allows people to take other actions without having to navigate away from the screen they were on. 

But back to our topic, the name “Peek and Pop” comes from its main goal — which is to preview or “peek” at content prior to transitioning or “popping” to the destination view controller. It made it possible to get access to actions by swiping up from the preview. Sadly, it turned out later that 3D Touch was a bit of a hardware dead-end. Even though it is a bit sad to say goodbye to 3D Touch Peek and Pop, UIContextMenu Api is an excellent replacement for developers.

This API provides access to powerful tools for customizing previews and creating context menus in Swift. So it is a very convenient replacement.


Difference Between Context Menus and Peek and Pop

The Peek and Pop feature shares a lot in common with context menus; however, they do differ in a couple of ways: 

First, context menus can be used on any  iOS13 device and newer ones; Peek and Pop, on the other hand, is only compatible with platforms that have 3D Touch.

Furthermore, with Peek and Pop, it is necessary to make a swipe-up gesture to show commands; context menus can show contextually relevant commands at once without needing to make any swiping gestures.  

In order to call for a context menu, you should perform a touch & hold gesture/3D Touch (it reduces the context menu opening time). The context menu allows you to take a look at the item preview and make a list of the commands that act on it. It makes it possible for users to choose a command and drag the item to another area.

How to use context menus?

In Human Interface Guidelines Apple recommends following several rules while prototyping context menus.

Adopt context menus consistently

It won’t be good if you add context menu for some elements somewhere and – don’t for other similar elements elsewhere. User may think that your app works incorrectly.

Include only necessary buttons to a context menu

The iOS 13 context menu is a great place for the most frequently used commands. “Most frequently used” is a key phrase. Don’t add everything to the menu. Otherwise, your iOS long press menu won’t be as convenient as it could be.

Use submenus

Use submenus, so it would be easier for a user to find necessary command. Apply simple and clear names to commands.

Don’t use more than one level of submenus

Despite the fact that the submenus can make navigation easier, they can easily complicate it. Apple doesn’t recommend to use more than one level of submenus.

Put the most frequently used commands to the top of context menu

People mostly focus on the top of a menu, so it is wise to keep the most important commands at the top.

Group menu items

Use grouping to combine similar actions.

Don’t use context and edit menus for one UI element at the same time

There might be a conflict between them, because both of them are being triggered with a long tap.

Edit menuEdit menu

Don’t add “Open” button to the menu

Users can open the element with a single tap. “Open” button will be redundant in this case.

How to Implement a Simple Context Menu for UIView

Now, let’s move to programming; we’ll show you how to make a custom context menu with Swift. Context menus are available for iOS 13+ and can be created with the help of the UiContextMenuConfiguration and UIContextMenuInteraction classes. You will also need Xcode 11.

You may download a full source code here.

Let's add a context menu for UIImageView, like in animation shown at the top of this article.

You should put UIImageView object to the controller’s view and write a few lines of code in viewDidLoad method:

class SingleViewController: UIViewController {
    @IBOutlet var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        imageView.isUserInteractionEnabled = true

        let interaction = UIContextMenuInteraction(delegate: self)
        imageView.addInteraction(interaction)
    }
}

Firstly, we create an object of UIContextMenuInteraction class. We provide a context menu’s delegate while initializing. We’ll look into it later. Using `addInteraction` method we add our menu to the image.

It’s left to implement UIContextMenuInteractionDelegate protocol now. There is only one required method, where we build our context menu:

extension SingleViewController: UIContextMenuInteractionDelegate {
    func contextMenuInteraction(\_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu<UIAction>? in
            let save = UIAction(\_\_title: "My Button", image: nil, options: \[\]) { action in
                // Put button handler here
            }
        return configuration
    }
}

If we returned nil, context menu wouldn’t be created. We create an object of UIContextMenuConfiguration class inside this method. We pass the following parameters during initialization:

  • identifier  – context menu’s identifier.
  • previewProvider – custom controller that may be optionally shown instead of selected element. We’ll look into it later.
  • we pass elements of a menu into actionProvider.

It’s very easy to create a menu’s items: you should provide a name, optional icon and a handler. That’s all!

Adding a submenu

Let’s complicate a task. I want to add a context menu with 2 items: “Save” and “Edit…”. If user taps the “Edit…” button, a submenu with “Rotate” and “Delete” items should be shown. The result looks like this:

Submenu in a context menuSubmenu in a context menu

To implement this we should rewrite UIContextMenuInteractionDelegate in this way:

func contextMenuInteraction(\_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
    let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu<UIAction>? in
        // Creating Save button
        let save = UIAction(\_\_title: "Save", image: UIImage(systemName: "tray.and.arrow.down.fill"), options: \[\]) { action in
            // Just showing some alert
            self.showAlert(title: action.title)
        }

        // Creating Rotate button
        let rotate = UIAction(\_\_title: "Rotate", image: UIImage(systemName: "arrow.counterclockwise"), options: \[\]) { action in
            self.showAlert(title: action.title)
        }
        // Creating Delete button
        let delete = UIAction(\_\_title: "Delete", image: UIImage(systemName: "trash.fill"), options: .destructive) { action in
            self.showAlert(title: action.title)
        }
        // Creating Edit, which will open Submenu
        let edit = UIMenu<UIAction>.create(title: "Edit...", children: \[rotate, delete\])

        // Creating main context menu
        return UIMenu<UIAction>.create(title: "Menu", children: \[save, edit\])
    }
    return configuration
}

Here we create “Save”, “Rotate” and “Delete” buttons, add the last two buttons into “Edit…” submenu and put everything to the main context menu.

Creating a context menu for UICollectionView

Let’s add a context menu into UICollectionView. A user will see a menu with “Archive” button, like this: 

Context menu in `UICollectionView`Context menu in `UICollectionView`

To implement this you should implement optional method func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? of UICollectionViewDelegate protocol. Like this:

override func collectionView(\_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
    let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu<UIAction>? in
        let action = UIAction(\_\_title: "Archive", image: UIImage(systemName: "archivebox.fill"), options: .destructive) { action in
            // Put button handler here
        }
        return UIMenu<UIAction>.create(title: "Menu", children: \[action\])
    }
    return configuration
}

Here we create an action and a menu. Now a user will see a context menu after a long tap.

Adding a menu to UITableView

UITableView and the context menu can also work together well. It’s almost the same as UICollectionView. You should implement contextMenuConfigurationForRowAt method of UITableViewDelegate protocol:

It’s almost the same as `UICollectionView.` You should implement `contextMenuConfigurationForRowAt` method of `UITableViewDelegate` protocol:

override func tableView(\_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
    let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu<UIAction>? in
        let action = UIAction(\_\_title: "Custom action", image: nil, options: \[\]) { action in
            // Put button handler here
        }
        return UIMenu<UIAction>.create(title: "Menu", children: \[action\])
    }
    return configuration
}

But what if we want to show custom preview screen, like this:

Context menu in `UITableView`Context menu in `UITableView`

To do this, we should pass necessary UIViewController into previewProvider while creating UIContextMenuConfiguration:

class PreviewViewController: UIViewController {
    static func controller() -> PreviewViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "PreviewViewController") as! PreviewViewController
        return controller
    }
}

extension TableViewController: UITableViewDelegate {
    override func tableView(\_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in
            // Return Preview View Controller here
            return PreviewViewController.controller()
        }) { \_ -> UIMenu<UIAction>? in
            let action = UIAction(\_\_title: "Custom action", image: nil, options: \[\]) { action in
                // Put button handler here
            }
            return UIMenu<UIAction>.create(title: "Menu", children: \[action\])
        }
        return configuration
    }
}

In this example PreviewViewController is being initialized from a storyboard.

Let’s implement a tap onto this ViewController. We should implement willCommitMenuWithAnimator method of UITableViewDelegate protocol. We will put the handler inside animator.addCompletion:

override func tableView(\_ tableView: UITableView, willCommitMenuWithAnimator animator: UIContextMenuInteractionCommitAnimating) {
    animator.addCompletion {
        // Put handler here
    }
}

Conclusion

The context menu is a new and powerful way to interact with your app. Due to Swift, the context menu shown on tap is a reality. And, as you can see, it doesn’t take much time to implement it. But don’t forget that the implementation of context menus may be changed with future iOS releases.

To get more insights regarding subscription app revenue growth read the Apphud Blog.

Ren
Ren
Co-founder at Apphud
Ex iOS app and game developer. 11 years in the industry since iOS 3. More than 50 apps are in the background with 4 exits. Entrepreneur and traveler.