10

I want to add an UIMenu to my application, I was practicing with it and now have the question if is possible to set the location of the UIMenu a little higher than the button is currently displaying it:

Menu above button

as you can see in this photo the menu currently overlay the tab bar and I wanted to set it a little higher than than the tab bar. here is my code:

let menu = UIMenu(title: "", children: [
  UIAction(title: NSLocalizedString("Gallery", comment: ""), image: UIImage(systemName: "folder"), handler: {
    (_) in
    self.loadPhotoGallery()
  })
])

btnMenuExtras.menu = menu
aheze
  • 24,434
  • 8
  • 68
  • 125
Ricardo Guerrero
  • 433
  • 1
  • 5
  • 21
  • 2
    Is it possible that we can provide x/y relative offset, or x/y absolute screen position to pop up UIMenu? – Cheok Yan Cheng Oct 03 '21 at 05:53
  • 1
    @CheokYanCheng probably not possible, unless you clone it - check out https://github.com/christianselig/ChidoriMenu – aheze Oct 03 '21 at 07:07

3 Answers3

13

iOS 14+

Sinse iOS 14 UIControl has method that provides point to which attach a menu

/// Return a point in this control's coordinate space to which to attach the given configuration's menu.
@available(iOS 14.0, *)
open func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint

so you can override UIButton to provide desired location for a menu (calculated or hardcoded) relatively to the button itself (`cause it is in button's coordinate space) and use that button either in storyboard (as a class for control) or created programmatically (if you need to inject it somewhere):

class MyButton: UIButton {
    var offset = CGPoint.zero
    override func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint {
        // hardcoded variant
//      return CGPoint(x: 0, y: -50)

        // or relative to orginal
        let original = super.menuAttachmentPoint(for: configuration)
        return CGPoint(x: original.x + offset.x, y: original.y + offset.y)
    }
}

class ViewController: UIViewController {

    @IBOutlet weak var btnMenuExtras: MyButton!   // << from storyboard

    override func viewDidLoad() {
        super.viewDidLoad()

        let menu = UIMenu(title: "", children: [
            UIAction(title: NSLocalizedString("Gallery", comment: ""), image: UIImage(systemName: "folder"), handler: {
                (_) in
//              self.loadPhotoGallery()
            })
        ])

        // offset is hardcoded for demo simplicity
        btnMenuExtras.offset = CGPoint(x: 0, y: -50)    // << here !!
        btnMenuExtras.menu = menu
    }
}

demo

Result:

demo1

Demo prepared & tested with Xcode 13 / iOS 15

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks for the valuable input. However, for button in toolbar, we usually use `UIBarButtonItem` which is not inherit from `UIControl`. (I found it is possible to add UIButton to UIToolbar. But I not not sure what is the possible side effect of using UIButton as opposed to UIBarButtonItem) – Cheok Yan Cheng Oct 03 '21 at 15:19
  • Ok. I figure out what is the unwanted side effect of using `UIButton` as opposed to `UIBarButtonItem`. Using `UIButton` instead of `UIBarButtonItem`, will cause toolbar items sizing issue. – Cheok Yan Cheng Oct 03 '21 at 15:25
1

You can use menuAttachmentPoint method of UIControl for UIButton to locate menu and convert that UIButton Into UIBarButtonItem By using bellow extension

@available(iOS 14.0, *)
open func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint

extension UIButton {
  func toBarButtonItem() -> UIBarButtonItem? {
     return UIBarButtonItem(customView: self)
  }
}
Jayesh Patel
  • 938
  • 5
  • 16
1

I would like to extend the answer of @Asperi and @Jayesh Patel, on how we can apply the technique on UIBarButtonItem

//
// Technique provided by @Asperi
//
class MyButton: UIButton {
    var offset = CGPoint.zero
    override func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint {
        // hardcoded variant
//      return CGPoint(x: 0, y: -50)

        // or relative to orginal
        let original = super.menuAttachmentPoint(for: configuration)
        return CGPoint(x: original.x + offset.x, y: original.y + offset.y)
    }
}

let image = UIImage(systemName: "ellipsis.circle", withConfiguration: UIImage.SymbolConfiguration(scale: .default))
let button = MyButton()
button.setImage(image, for: .normal)

let menu = UIMenu(title: "", children: [
    UIAction(title: NSLocalizedString("Gallery", comment: ""), image: UIImage(systemName: "folder"), handler: {
        (_) in
    })
])
// offset is hardcoded for demo simplicity
button.offset = CGPoint(x: 0, y: 50)    // << here !!

//
// This is how we can make UIButton as UIBarButtonItem
//
button.menu = menu
button.showsMenuAsPrimaryAction = true

//
// Technique provided by @Jayesh Patel
//
let threeDotsBarButtonItem = UIBarButtonItem(customView: button)
var items = toolbar.items
items?.append(threeDotsBarButtonItem)
toolbar.items = items
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875