Mar 24, 2023
1

Calling all iOS Devs! Don’t miss out on this exciting blog post that will take your app to the next level! If you thought ContextMenu was impressive, wait until you hear about SwiftUI’s Menu element. Even as a seasoned iOS Dev, I was blown away by its potential after examining the stunning Apple Music App. And now, I want to share my insights with you. Get ready to learn how to implement Menu’s and discover my expert tips for making it seamless and unobtrusive. If you’re serious about making your app more responsive, this is a must-read!

A menu reveals its options when people interact with it, making it a space-efficient way to present commands in your app. Each of the elements of the menu perform an asigned to it action. In SwiftUI, a Menu is a view that displays a list of menu items, that is highly customizable. The most common scenario for using Menus is to provide users with quick access to frequently used features or actions, without cluttering up the screen with too many buttons or controls.
In contrast to the ContextMenu, which focuses on actions that are specific to a particular UI element within the View, the Menu element provides access to actions that span the currently presented View.
In the beginning I would like to warn you on misusing the contextMenus. As per Apple’s UI Guidelines:
Be mindful of the number of items displayed in the Menu. According to Apple’s guidelines, Menus should have no more than six items to avoid overwhelming the user with too many options.
Ensure that the Menu items are organized in a logical and intuitive manner. Group related items together and place the most frequently used items at the top of the list.
Consider the accessibility of the Menu, by providing clear labels for each Menu item.
It is recommended to use standard system icons and colors to ensure consistency with the rest of the user interface.
Now, let’s get our hands in dirty!
Firstly lets create a new project with an empty SwiftUI View. Inside this View paste the code below:
ZStack {
Menu {
//...
} label: {
Circle()
.fill(.gray.opacity(0.15))
.frame(width: 30, height: 30)
.overlay {
Image(systemName: "ellipsis")
.font(.system(size: 13.0, weight: .semibold))
.foregroundColor(.pink)
.padding()
}
}
}
Here we’ve created a label for the menu, that is going to be pressed on to reveal the Menu itself.

Since there are no elements in our Menu modifier even if we press on our Circle, nothing will happen. Now we should populate the rows! There are many ways to give the menu its sections. For example, you could list the elements one by one like this:
Menu {
Button() {
}
Button() {
}
Button() {
}
}
Although it gets the job done, It will make the code unreadable once we set an action and a label for each button. What would be one of the better ways to add a list of buttons is to use ForEach() Instead.
Populate the rows
To populate the rows create an array of String, and use ForEach() loop method.
let elements: [String] = ["First", "Second", "Third", "Fourth", "Fifth"]
Menu {
ForEach(elements.reversed(), id: \.self) { row in
Button() {
//Action
print("Pressed")
} label: {
HStack {
Text(row.title)
.font(.system(size: 14.0, weight: .medium, design: .default))
.foregroundColor(.black)
.multilineTextAlignment(.leading)
Spacer()
}
}
}
} label: {
//...
}
You should now be able to see a Menu with five rows in It

Separate the Menu in sections
To separate the rows in sections use the nested loop (loop in loop). Firstly, declare two new structures and name the “menuRow” and “menuSection”. They will hold the sections, rows in it, and add an icon image to the rows.
New menuRow Structure:
struct menuElements: Identifiable, Hashable {
let id: String
var title: String
var icon: String
init(title: String, icon: String) {
self.id = UUID().uuidString
self.title = title
self.icon = icon
}
}
New menuSection Structure:
struct menuSection: Identifiable, Hashable {
let id: String
let rows: [menuRow]
init(rows: [menuRow]) {
self.id = UUID().uuidString
self.rows = rows
}
}
Modify the elements Array using new structures:
var elements: [menuSection] = [menuSection(rows: [menuRow(title: "First", icon: "1.circle.fill"), menuRow(title: "Second", icon: "2.circle.fill")]), menuSection(rows: [menuRow(title: "Third", icon: "3.circle.fill"), menuRow(title: "Fourth", icon: "4.circle.fill")]), menuSection(rows: [menuRow(title: "Fifth", icon: "5.circle.fill"), menuRow(title: "Sixth", icon: "6.circle.fill")])]
Create a new Menu element function
To declutter the code let’s create a new @ViewBuilder function that will return the Menu’s row Button. The button will take title and icon String values.
@ViewBuilder
func sectionElementButton(title: String, icon: String) -> some View {
Button() {
print("pressed")
} label: {
HStack {
Text(title)
.font(.system(size: 14.0, weight: .medium, design: .default))
.foregroundColor(.white)
.multilineTextAlignment(.leading)
Spacer()
Image(systemName: icon)
.foregroundColor(.white)
.font(.system(size: 14.0, weight: .semibold, design: .default))
}
}
}
Modify the Menu
Use Section() modifier to add a divider between the sections, and use the nested loop.
Menu {
ForEach(elements.reversed(), id: \.self) { section in
Section(content: {
ForEach(section.rows.reversed()) { row in
sectionElementButton(title: row.title, icon: row.icon)
}
})
}
} label: {
Circle()
.fill(.gray.opacity(0.15))
.frame(width: 30, height: 30)
.overlay {
Image(systemName: "ellipsis")
.font(.system(size: 13.0, weight: .semibold))
.foregroundColor(.pink)
.padding()
}
}
Now Let’s Build the App! Here’s what you should see:

I’m confident that you’ve already been impressed by the functionality of the Menu element, but there are additional customization options available. Let’s proceed by altering its color scheme.
Change Menu’s Color Scheme
Changing color scheme in SwiftUI is as easy as just adding one singular modifier colorScheme()
Menu {
//...
} label: {
//...
}
.colorScheme(.dark)
Here is the result.

Drop-down menu in menu
So far we have seen Menu with Button views but it can contain another menu as well. This combination creates a simple but at the same time well animated and stunning view.
Add a new section in the elements array with a “Drop Down” row
var elements: [menuSection] = [menuSection(rows: [menuRow(title: "Drop Down", icon: "chevron.compact.down")]), menuSection(rows: [menuRow(title: "First", icon: "1.circle.fill"), menuRow(title: "Second", icon: "2.circle.fill")]), menuSection(rows: [menuRow(title: "Third", icon: "3.circle.fill"), menuRow(title: "Fourth", icon: "4.circle.fill")]), menuSection(rows: [menuRow(title: "Fifth", icon: "5.circle.fill"), menuRow(title: "Sixth", icon: "6.circle.fill")])]Modify the rows loop with if statement that will check if the title is "Drop Down". If it is then we will add a new Menu instead if a button.
Create a new array “dropRows” that will contains the drop down menus elements
var dropDownElements: [menuRow] = [menuRow(title: "First", icon: "1.circle"), menuRow(title: "Second", icon: "2.circle"), menuRow(title: "Third", icon: "3.circle")]
Use if condition to set a new dedicated Button for the dropdown menu
ZStack {
Menu {
ForEach(elements.reversed(), id: \.self) { section in
Section(content: {
ForEach(section.rows.reversed()) { row in
if row.title == "Drop Down" {
Menu {
ForEach(dropDownElements) { dropRow in
sectionElementButton(title: dropRow.title, icon: dropRow.icon)
}
} label: {
sectionElementButton(title: row.title, icon: "")
}
} else {
sectionElementButton(title: row.title, icon: row.icon)
}
}
})
}
} label: {
Circle()
.fill(.gray.opacity(0.15))
.frame(width: 30, height: 30)
.overlay {
Image(systemName: "ellipsis")
.font(.system(size: 13.0, weight: .semibold))
.foregroundColor(.pink)
.padding()
}
}
.colorScheme(.dark)
Here’s what you should see:

Now buckle your seats, because I’ll teach you how to use Menu’s primary action feature that was introduced in iOS 15
Primary Action in iOS 15
iOS 15 brought a new option to add a default action for Menu. This means our menu can have a primary action and when the user taps on the menu, we will execute that action. Users will have to long-press in order to see other Menu options. While it completely transforms the way user interacts with the Menu, It also is a very important modification. Let’s add this to our Menu.
Menu {
ForEach(elements.reversed(), id: \.self) { section in
Section(content: {
ForEach(section.rows.reversed()) { row in
if row.title == "Drop Down" {
Menu {
ForEach(dropDownElements) { dropRow in
sectionElementButton(title: dropRow.title, icon: dropRow.icon)
}
} label: {
sectionElementButton(title: row.title, icon: "")
}
} else {
sectionElementButton(title: row.title, icon: row.icon)
}
}
})
}
} label: {
Circle()
.fill(.gray.opacity(0.15))
.frame(width: 30, height: 30)
.overlay {
Image(systemName: "ellipsis")
.font(.system(size: 13.0, weight: .semibold))
.foregroundColor(.pink)
.padding()
}
} primaryAction: {
print("Primary Action")
}
.colorScheme(.dark)
Now whenever you just press on the Menu it only performs it’s primaryAction. To show all its available actions you now have to Long-Press on the Menu.

Congrats! Now you know how to implement a Custom ContextView with Preview and Destination in iOS 15.
Thank you for reading this article!
You can find the source code for this project here:
[https://github.com/aisultanios/SwiftUI-Menu-Tutorial]
You can find many more interesting open-source projects on my Github profile