They look like this:
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.
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.
In Human Interface Guidelines Apple recommends following several rules while prototyping context menus.
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.
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, so it would be easier for a user to find necessary command. Apply simple and clear names to commands.
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.
People mostly focus on the top of a menu, so it is wise to keep the most important commands at the top.
Use grouping to combine similar actions.
There might be a conflict between them, because both of them are being triggered with a long tap.
Users can open the element with a single tap. “Open” button will be redundant in this case.
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.actionProvider
.It’s very easy to create a menu’s items: you should provide a name, optional icon and a handler. That’s all!
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:
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.
UICollectionView
Let’s add a context menu into UICollectionView
. A user will see a menu with “Archive” button, like this:
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.
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:
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 } }
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.