swiftbuttonmenuuikitpopup

How to force the order of UIKit pop up menu button items?


I have a couple of UIKit pop-up menu buttons with identical menu items on the same screen in a Swift app. The buttons are built by calling a function that uses an array of strings to create the list of menu items.

The problem is that depending on the button's vertical position on the screen, the menu items may appear in the order specified by the function, or reversed. If the button is in the upper half of the screen, the menu items are listed in the correct order. If the button is in the lower half of the screen the menu items are listed in reverse order.

I would prefer the menu items to appear in the same order regardless of the button's position on the screen. I could check the button location and have the menu creation function reverse the order, but that seems kind of clunky. I am hoping there's a cleaner way to override this behaviour.

The code and array used to create the button menus:

let buttonMenuItems = ["Spring","Summer","Autumn","Winter"]

func createAttributeMenu(menuNumber: Int)->UIMenu  {
    
    var menuActions: [UIAction] = []
    
    for attribute in buttonMenuItems {
        let item = UIAction(title: attribute) { action in
            self.updateMenu(menuID: menuNumber, selected: attribute)
        }
        menuActions.append(item)
    }
    
    return UIMenu(title: "", children: menuActions)
}

The result is this:

enter image description here enter image description here enter image description here

Versions I'm using now in testing: Xcode 14.1, iOS 16.1, but I have seen this behaviour on earlier versions as well. (back to iOS 14.x)


Solution

  • Starting with iOS 16, there is a .preferredMenuElementOrder property that can be set on the button:

    Best I can tell (as with many Apple definitions), there is no difference between .automatic and .priority.

    From the .priority docs page:

    Discussion

    This ordering strategy displays the first menu element in the UIMenu closest to the location of the user interaction.

    So, we get "reversed" order based on the position of the menu relative to the button.

    To keep your defined order:

    buttonNearTop.menu = createAttributeMenu(menuNumber: 1)
    buttonNearBottom.menu = createAttributeMenu(menuNumber: 2)
    if #available(iOS 16.0, *) {
        buttonNearBottom.preferredMenuElementOrder = .fixed
        buttonNearTop.preferredMenuElementOrder = .fixed
    } else {
        // out of luck... you get Apple's "priority" ordering
    }