In SwiftUI, there is a thing called a Menu, and in it you can have Buttons, Dividers, other Menus, etc. Here's an example of one I'm building below:
import SwiftUI
func testing() {
print("Hello")
}
struct ContentView: View {
var body: some View {
VStack {
Menu {
Button(action: testing) {
Label("Button 1", systemImage: "pencil.tip.crop.circle.badge.plus")
}
Button(action: testing) {
Label("Button 2", systemImage: "doc")
}
}
label: {
Label("", systemImage: "ellipsis.circle")
}
}
}
}
So, in the SwiftUI Playgrounds app, they have this menu:
My question is:
How did they make the circled menu option? I’ve found a few other cases of this horizontal set of buttons in a Menu, like this one below:
HStacks and other obvious attempts have all failed. I’ve looked at adding a MenuStyle, but the Apple’s docs on that are very lacking, only showing an example of adding a red border to the menu button. Not sure that’s the right path anyway.
I’ve only been able to get Dividers() and Buttons() to show up in the Menu:
I’ve also only been able to find code examples that show those two, despite seeing examples of other options in Apps out there.
It looks as this is only available in UIKit
at present (and only iOS 16+), by setting
menu.preferredElementSize = .medium
To add this to your app you can add a UIMenu
to UIButton
and then use UIHostingController
to add it to your SwiftUI app.
Here's an example implementation:
Subclass a UIButton
class MenuButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
let inspectAction = self.inspectAction()
let duplicateAction = self.duplicateAction()
let deleteAction = self.deleteAction()
setImage(UIImage(systemName: "ellipsis.circle"), for: .normal)
menu = UIMenu(title: "", children: [inspectAction, duplicateAction, deleteAction])
menu?.preferredElementSize = .medium
showsMenuAsPrimaryAction = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func inspectAction() -> UIAction {
UIAction(title: "Inspect",
image: UIImage(systemName: "arrow.up.square")) { action in
//
}
}
func duplicateAction() -> UIAction {
UIAction(title: "Duplicate",
image: UIImage(systemName: "plus.square.on.square")) { action in
//
}
}
func deleteAction() -> UIAction {
UIAction(title: "Delete",
image: UIImage(systemName: "trash"),
attributes: .destructive) { action in
//
}
}
}
Create a Menu
using UIViewRepresentable
struct Menu: UIViewRepresentable {
func makeUIView(context: Context) -> MenuButton {
MenuButton(frame: .zero)
}
func updateUIView(_ uiView: MenuButton, context: Context) {
}
}
Works like a charm!
struct ContentView: View {
var body: some View {
Menu()
}
}