Home > Software engineering >  Inline Horizontal Buttons in SwiftUI Menu
Inline Horizontal Buttons in SwiftUI Menu

Time:01-18

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.

CodePudding user response:

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

  • Related