5

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:

enter image description here

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:

enter image description here

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:

enter image description here

I’ve also only been able to find code examples that show those two, despite seeing examples of other options in Apps out there.

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
adammenges
  • 7,848
  • 5
  • 26
  • 33
  • Here’s some random SwiftUI Code with a Menu I’ve been playing around with if it’s helpful: https://gist.github.com/adammenges/a8cc0dd63fe8d13ad5e5819b9ea31057 – adammenges Jan 16 '23 at 23:29
  • 1
    Without a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) it is impossible to help you troubleshoot. All code should be included in the question. – lorem ipsum Jan 16 '23 at 23:33
  • Show us what you've already tried. What result did you see? – Ashley Mills Jan 17 '23 at 10:37
  • Added what I see! – adammenges Jan 17 '23 at 16:06
  • This is a tough question, I tried Sections and Pickers to no avail. The UIKit equivalent is `UIMenuElementSizeSmall` which might help in the search. What SwiftUI apps have you seen it in? We can decompile them and figure it out. – malhal Jan 17 '23 at 16:52

2 Answers2

4

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()
    }
}

enter image description here

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • Oh sweet! Thanks! How would you add more regular buttons below the three you’ve got there? Like in the second screenshot. – adammenges Jan 17 '23 at 17:25
  • 1
    Apple have a whole downloadable project with all sorts of menus. I'd suggest installing that and see what they do. https://developer.apple.com/documentation/uikit/uicontrol/adding_context_menus_in_your_app – Ashley Mills Jan 17 '23 at 17:30
3

Starting with iOS17, it I was able to accomplish this by using a ControlGroup in the Menu.

Menu {
                        
    ControlGroup {

        Button {
                                
        } label: {
            Image(systemName: "1.circle.fill")
        }

        Button {
                                
        } label: {
            Image(systemName: "2.circle.fill")
        }

        Button {
                                
        } label: {
            Image(systemName: "3.circle.fill")
        }

                           
    }
 
 ....

}
zumzum
  • 17,984
  • 26
  • 111
  • 172