Context Menus Tutorial for iOS: Getting Started
Learn to enhance your app with context menus, including configuring actions, adding images, nesting submenus, adding custom previews and more. By Keegan Rush.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Context Menus Tutorial for iOS: Getting Started
20 mins
Adding Submenus
Submenus are a great way to keep the context menu clean and organized. Use them to group related actions.
Add the following to the UIContextMenuInteractionDelegate
extension at the bottom of SpotInfoViewController.swift:
func updateRating(from action: UIAction) {
guard let number = Int(action.identifier.rawValue) else {
return
}
currentUserRating = number
}
This method uses the identifier
of a UIAction
to update the user’s rating.
Like a UIContextMenuConfiguration
, a UIAction
can have an identifier. updateRating(from:)
attempts to convert the action’s identifier into an Int
and sets the currentUserRating
accordingly.
Add the following method below updateRating(from:)
:
func makeRateMenu() -> UIMenu {
let ratingButtonTitles = ["Boring", "Meh", "It's OK", "Like It", "Fantastic!"]
let rateActions = ratingButtonTitles
.enumerated()
.map { index, title in
return UIAction(
title: title,
identifier: UIAction.Identifier("\(index + 1)"),
handler: updateRating)
}
return UIMenu(
title: "Rate...",
image: UIImage(systemName: "star.circle"),
children: rateActions)
}
This method creates UIAction
s with identifiers matching each user rating: One through five. Remember, the handler for a UIAction
is a closure that fires when tapping the item. This sets updateRating(from:)
which you wrote previously as each action’s handler. Then, it returns a UIMenu
with all the actions as the menu’s children.
If you look at the declaration of UIAction
and UIMenu
, they’re both child classes of UIMenuElement
. The children
parameter is of type [UIMenuElement]
. That means that when you configure the context menu, you can add both actions or entire child menus.
Head back to contextMenuInteraction(_:configurationForMenuAtLocation:)
, find the line declaring children
and replace it with this:
let rateMenu = self.makeRateMenu()
let children = [rateMenu, removeRating]
Rather than adding five new items for each possible rating, you add the rateMenu
as a submenu.
Build and run the app and test out your context menu by setting the user rating.
Inline Menus
The nested submenu cleans things up, but it means the user needs an extra tap to reach the actions.
To make things a little easier, you can display the menu inline. The displayInline
menu option will show all the items in the root menu, but separated with a dividing line.
To achieve that, replace the end of makeRateMenu()
that creates and returns a UIMenu
with this:
return UIMenu(
title: "Rate...",
image: UIImage(systemName: "star.circle"),
options: .displayInline,
children: rateActions)
Except for adding the .displayInline
menu option, it’s the same as what you had before.
Build and run the app to see the result:
Custom Previews
Context menus generally show a preview of the content. Right now, tapping and holding the Submit Rating button shows the Submit Rating button itself, or its Update Rating alter ego, as shown in the previous screenshot. It’s not exactly eye-appealing.
Next, you’ll set your own preview. Add the following method at the bottom of your UIContextMenuInteractionDelegate
extension:
func makeRatePreview() -> UIViewController {
let viewController = UIViewController()
// 1
let imageView = UIImageView(image: UIImage(named: "rating_star"))
viewController.view = imageView
// 2
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
imageView.translatesAutoresizingMaskIntoConstraints = false
// 3
viewController.preferredContentSize = imageView.frame.size
return viewController
}
This makes a simple UIViewController
that shows a rating star.
Here’s what’s happening, step-by-step:
- The app already has a
rating_star
image that’s used for stars in the app. Create aUIImageView
with that image and set it as a blankUIViewController
‘s view. - Set the frame of the image to specify its size. Setting the frame is enough, you don’t need any Auto Layout constraints set for you. Set
translatesAutoresizingMaskIntoConstraints
tofalse
. - You need to specify a
preferredContentSize
to show the view controller as a preview. If you don’t, it’ll take all the space available to it.
Back in contextMenuInteraction(_:configurationForMenuAtLocation:)
, find the previewProvider
parameter of your UIContextMenuConfiguration
initializer. Replace the line with the following to pass in makeRatePreview
as the preview provider:
previewProvider: makeRatePreview) { _ in
Build and run. When you tap and hold on the Submit Rating button, you should see the preview of the rating star:
Great work! That wraps everything up for this context menu. Now you’re ready for something completely different.
Context Menus in Table Views
Wouldn’t it be useful if tapping a vacation spot in the main table view showed a list of common actions? When viewing a vacation spot, the View Map button opens a map view on the vacation spot’s location:
By adding a View Map action in a context menu on the list of vacation spots, users can open the map before opening the spot info view controller. You’ll also add an action that makes it easy to share your favorite vacation spot with a friend. So far, you’ve learned the necessary steps to add a context menu to a view:
- Add a
UIContextMenuInteraction
to a view. - Implement
contextMenuInteraction(_:configurationForMenuAtLocation:)
, the one required method ofUIContextMenuInteractionDelegate
. - Build a
UIContextMenuConfiguration
with all your menu items.
The list of vacation spots exists in SpotsViewController
as a table view. Each row in a table view is a view itself, or more specifically, a UITableViewCell
.
To add a context menu to each row, you could do as you did it before — but there’s a more straightforward approach.
Open SpotsViewController.swift and add the following code at the bottom of the class:
// MARK: - UITableViewDelegate
override func tableView(
_ tableView: UITableView,
contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint)
-> UIContextMenuConfiguration? {
// 1
let index = indexPath.row
let vacationSpot = vacationSpots[index]
// 2
let identifier = "\(index)" as NSString
return UIContextMenuConfiguration(
identifier: identifier,
previewProvider: nil) { _ in
// 3
let mapAction = UIAction(
title: "View map",
image: UIImage(systemName: "map")) { _ in
self.showMap(vacationSpot: vacationSpot)
}
// 4
let shareAction = UIAction(
title: "Share",
image: UIImage(systemName: "square.and.arrow.up")) { _ in
VacationSharer.share(vacationSpot: vacationSpot, in: self)
}
// 5
return UIMenu(title: "", image: nil, children: [mapAction, shareAction])
}
}
With a table view, adding a UIContextMenu
to each row is as easy as implementing this method on UITableViewDelegate
.
Tapping and holding any row will call tableView(_:contextMenuConfigurationForRowAt:point:)
, allowing it to provide a context menu for the specific row.
You’re building a useful, functional menu for each row of the table view, so there’s a lot going on. In the above code, you:
- Get the vacation spot for the current row.
- Add an identifier to the context menu which you’ll use momentarily. You have to convert it to
NSString
, as the identifier needs to conform toNSCopying
. - The first action in the menu is for the map. Tapping this item calls
showMap(vacationSpot:)
, which opens the map view for this spot. - Add another action to share the spot.
VacationSharer.share(vacationSpot:in:)
is a helper method that opens a share sheet. - Finally, construct and return a
UIMenu
with both items.
And that’s all. Build and run the app, then tap and hold a vacation spot to try your new context menu.